From 82723542941c52e9b82fbca568481a095a7ef0d1 Mon Sep 17 00:00:00 2001 From: Jaalah Ramos Date: Wed, 12 Nov 2025 12:57:10 -0500 Subject: [PATCH 1/4] upcoming: [M3-10708] - Enhance Linode Migration Event Messages with Maintenance Type and Config Information --- packages/api-v4/src/account/events.ts | 6 +- .../components/ExtraPresetEvents.tsx | 59 ++++++++ .../components/ExtraPresetMaintenance.tsx | 37 ++++- .../src/dev-tools/components/JsonTextArea.tsx | 19 ++- .../Maintenance/MaintenanceTableRow.tsx | 4 +- .../src/features/Events/factories/linode.tsx | 141 +++++++++++++++--- 6 files changed, 235 insertions(+), 31 deletions(-) diff --git a/packages/api-v4/src/account/events.ts b/packages/api-v4/src/account/events.ts index 4f8c8d21e4b..9f2359d42c3 100644 --- a/packages/api-v4/src/account/events.ts +++ b/packages/api-v4/src/account/events.ts @@ -1,4 +1,4 @@ -import { API_ROOT } from '../constants'; +import { API_ROOT, BETA_API_ROOT } from '../constants'; import Request, { setMethod, setParams, setURL, setXFilter } from '../request'; import type { Filter, Params, ResourcePage } from '../types'; @@ -12,7 +12,7 @@ import type { Event, Notification } from './types'; */ export const getEvents = (params: Params = {}, filter: Filter = {}) => Request>( - setURL(`${API_ROOT}/account/events`), + setURL(`${BETA_API_ROOT}/account/events`), setMethod('GET'), setXFilter(filter), setParams(params), @@ -26,7 +26,7 @@ export const getEvents = (params: Params = {}, filter: Filter = {}) => */ export const getEvent = (eventId: number) => Request( - setURL(`${API_ROOT}/account/events/${encodeURIComponent(eventId)}`), + setURL(`${BETA_API_ROOT}/account/events/${encodeURIComponent(eventId)}`), setMethod('GET'), ); diff --git a/packages/manager/src/dev-tools/components/ExtraPresetEvents.tsx b/packages/manager/src/dev-tools/components/ExtraPresetEvents.tsx index 6ac26ce24fb..8982d6ea2db 100644 --- a/packages/manager/src/dev-tools/components/ExtraPresetEvents.tsx +++ b/packages/manager/src/dev-tools/components/ExtraPresetEvents.tsx @@ -287,6 +287,65 @@ const eventTemplates = { status: 'finished', }), + 'Linode Migration In Progress': () => + eventFactory.build({ + action: 'linode_migrate', + entity: { + id: 1, + label: 'linode-1', + type: 'linode', + url: '/v4/linode/instances/1', + }, + message: 'Linode migration in progress.', + percent_complete: 45, + status: 'started', + }), + + 'Linode Migration - Emergency': () => + eventFactory.build({ + action: 'linode_migrate', + description: 'emergency', + entity: { + id: 1, + label: 'linode-1', + type: 'linode', + url: '/v4/linode/instances/1', + }, + message: 'Emergency linode migration in progress.', + percent_complete: 30, + status: 'started', + }), + + 'Linode Migration - Scheduled Started': () => + eventFactory.build({ + action: 'linode_migrate', + description: 'scheduled', + entity: { + id: 1, + label: 'linode-1', + type: 'linode', + url: '/v4/linode/instances/1', + }, + message: 'Scheduled linode migration in progress.', + percent_complete: 10, + status: 'started', + }), + + 'Linode Migration - Scheduled': () => + eventFactory.build({ + action: 'linode_migrate', + description: 'scheduled', + entity: { + id: 1, + label: 'linode-1', + type: 'linode', + url: '/v4/linode/instances/1', + }, + message: 'Scheduled linode migration in progress.', + percent_complete: 0, + status: 'scheduled', + }), + 'Completed Event': () => eventFactory.build({ action: 'account_update', diff --git a/packages/manager/src/dev-tools/components/ExtraPresetMaintenance.tsx b/packages/manager/src/dev-tools/components/ExtraPresetMaintenance.tsx index c257d22db49..d1a4eae094b 100644 --- a/packages/manager/src/dev-tools/components/ExtraPresetMaintenance.tsx +++ b/packages/manager/src/dev-tools/components/ExtraPresetMaintenance.tsx @@ -210,7 +210,42 @@ const maintenanceTemplates = { Canceled: () => accountMaintenanceFactory.build({ status: 'canceled' }), Completed: () => accountMaintenanceFactory.build({ status: 'completed' }), 'In Progress': () => - accountMaintenanceFactory.build({ status: 'in_progress' }), + accountMaintenanceFactory.build({ + status: 'in_progress', + entity: { + type: 'linode', + id: 1, + label: 'linode-1', + url: '/v4/linode/instances/1', + }, + type: 'migrate', + }), + 'In Progress - Emergency Migration': () => + accountMaintenanceFactory.build({ + status: 'in_progress', + entity: { + type: 'linode', + id: 1, + label: 'linode-1', + url: '/v4/linode/instances/1', + }, + type: 'migrate', + description: 'emergency', + reason: 'Emergency maintenance migration', + }), + 'In Progress - Scheduled Migration': () => + accountMaintenanceFactory.build({ + status: 'in_progress', + entity: { + type: 'linode', + id: 1, + label: 'linode-1', + url: '/v4/linode/instances/1', + }, + type: 'migrate', + description: 'scheduled', + reason: 'Scheduled maintenance migration', + }), Pending: () => accountMaintenanceFactory.build({ status: 'pending' }), Scheduled: () => accountMaintenanceFactory.build({ status: 'scheduled' }), Started: () => accountMaintenanceFactory.build({ status: 'started' }), diff --git a/packages/manager/src/dev-tools/components/JsonTextArea.tsx b/packages/manager/src/dev-tools/components/JsonTextArea.tsx index 6eb65224f9f..1bc713bb65e 100644 --- a/packages/manager/src/dev-tools/components/JsonTextArea.tsx +++ b/packages/manager/src/dev-tools/components/JsonTextArea.tsx @@ -24,6 +24,23 @@ export const JsonTextArea = ({ const debouncedUpdate = React.useMemo( () => debounce((text: string) => { + // Handle empty/whitespace text as null + if (!text.trim()) { + const event = { + currentTarget: { + name, + value: null, + }, + target: { + name, + value: null, + }, + } as unknown as React.ChangeEvent; + + onChange(event); + return; + } + try { const parsedJson = JSON.parse(text); const event = { @@ -35,7 +52,7 @@ export const JsonTextArea = ({ name, value: parsedJson, }, - } as React.ChangeEvent; + } as unknown as React.ChangeEvent; onChange(event); } catch (err) { diff --git a/packages/manager/src/features/Account/Maintenance/MaintenanceTableRow.tsx b/packages/manager/src/features/Account/Maintenance/MaintenanceTableRow.tsx index d4fc3505a55..96c5191581c 100644 --- a/packages/manager/src/features/Account/Maintenance/MaintenanceTableRow.tsx +++ b/packages/manager/src/features/Account/Maintenance/MaintenanceTableRow.tsx @@ -77,9 +77,9 @@ export const MaintenanceTableRow = (props: MaintenanceTableRowProps) => { const eventProgress = recentEvent && formatProgressEvent(recentEvent); - const truncatedReason = truncate(reason, 93); + const truncatedReason = reason ? truncate(reason, 93) : ''; - const isTruncated = reason !== truncatedReason; + const isTruncated = reason ? reason !== truncatedReason : false; const dateField = getMaintenanceDateField(tableType); const dateValue = props.maintenance[dateField]; diff --git a/packages/manager/src/features/Events/factories/linode.tsx b/packages/manager/src/features/Events/factories/linode.tsx index 2d80ed84d26..f65948586e0 100644 --- a/packages/manager/src/features/Events/factories/linode.tsx +++ b/packages/manager/src/features/Events/factories/linode.tsx @@ -5,6 +5,23 @@ import { Link } from 'src/components/Link'; import { EventLink } from '../EventLink'; import type { PartialEventMap } from '../types'; +import type { AccountMaintenance } from '@linode/api-v4'; + +/** + * Normalizes the event description to a valid maintenance type. + * Only accepts 'emergency' or 'scheduled' from AccountMaintenance.description, + * defaults to 'maintenance' for any other value or null/undefined. + */ +type MaintenanceDescription = 'maintenance' | AccountMaintenance['description']; + +const getMaintenanceDescription = ( + description: null | string | undefined +): MaintenanceDescription => { + if (description === 'emergency' || description === 'scheduled') { + return description; + } + return 'maintenance'; +}; export const linode: PartialEventMap<'linode'> = { linode_addip: { @@ -241,30 +258,106 @@ export const linode: PartialEventMap<'linode'> = { ), }, linode_migrate: { - failed: (e) => ( - <> - Migration failed for Linode{' '} - . - - ), - finished: (e) => ( - <> - Linode has been{' '} - migrated. - - ), - scheduled: (e) => ( - <> - Linode is scheduled to be{' '} - migrated. - - ), - started: (e) => ( - <> - Linode is being{' '} - migrated. - - ), + failed: (e) => { + const maintenanceType = getMaintenanceDescription(e.description); + return ( + <> + Migration failed for Linode{' '} + for{' '} + {maintenanceType === 'maintenance' ? ( + maintenance + ) : ( + <> + {maintenanceType} maintenance + + )} + {e.secondary_entity ? ( + <> + {' '} + with config + + ) : ( + '' + )} + . + + ); + }, + finished: (e) => { + const maintenanceType = getMaintenanceDescription(e.description); + return ( + <> + Linode has been{' '} + migrated for{' '} + {maintenanceType === 'maintenance' ? ( + maintenance + ) : ( + <> + {maintenanceType} maintenance + + )} + {e.secondary_entity ? ( + <> + {' '} + with config + + ) : ( + '' + )} + . + + ); + }, + scheduled: (e) => { + const maintenanceType = getMaintenanceDescription(e.description); + return ( + <> + Linode is scheduled to be{' '} + migrated for{' '} + {maintenanceType === 'maintenance' ? ( + maintenance + ) : ( + <> + {maintenanceType} maintenance + + )} + {e.secondary_entity ? ( + <> + {' '} + with config + + ) : ( + '' + )} + . + + ); + }, + started: (e) => { + const maintenanceType = getMaintenanceDescription(e.description); + return ( + <> + Linode is being{' '} + migrated for{' '} + {maintenanceType === 'maintenance' ? ( + maintenance + ) : ( + <> + {maintenanceType} maintenance + + )} + {e.secondary_entity ? ( + <> + {' '} + with config + + ) : ( + '' + )} + . + + ); + }, }, linode_migrate_datacenter: { failed: (e) => ( From bd72abaebf08ae83cb835d2f4821af32b7cf38c0 Mon Sep 17 00:00:00 2001 From: Jaalah Ramos Date: Wed, 12 Nov 2025 13:08:29 -0500 Subject: [PATCH 2/4] Add two changesets --- packages/api-v4/.changeset/pr-13084-changed-1762970902694.md | 5 +++++ .../.changeset/pr-13084-upcoming-features-1762970850861.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 packages/api-v4/.changeset/pr-13084-changed-1762970902694.md create mode 100644 packages/manager/.changeset/pr-13084-upcoming-features-1762970850861.md diff --git a/packages/api-v4/.changeset/pr-13084-changed-1762970902694.md b/packages/api-v4/.changeset/pr-13084-changed-1762970902694.md new file mode 100644 index 00000000000..b3e1bd0b7c7 --- /dev/null +++ b/packages/api-v4/.changeset/pr-13084-changed-1762970902694.md @@ -0,0 +1,5 @@ +--- +"@linode/api-v4": Changed +--- + +Use v4beta endpoints for /events and /events/ ([#13084](https://github.com/linode/manager/pull/13084)) diff --git a/packages/manager/.changeset/pr-13084-upcoming-features-1762970850861.md b/packages/manager/.changeset/pr-13084-upcoming-features-1762970850861.md new file mode 100644 index 00000000000..894835a1327 --- /dev/null +++ b/packages/manager/.changeset/pr-13084-upcoming-features-1762970850861.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Display maintenance type (emergency/scheduled) and config information in linode_migrate event messages ([#13084](https://github.com/linode/manager/pull/13084)) From abb1f129dc236136e3b001b5375d497d5169262e Mon Sep 17 00:00:00 2001 From: Jaalah Ramos Date: Thu, 13 Nov 2025 09:17:50 -0500 Subject: [PATCH 3/4] Use the field for upcoming tables start date --- packages/manager/src/features/Account/Maintenance/utilities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/manager/src/features/Account/Maintenance/utilities.ts b/packages/manager/src/features/Account/Maintenance/utilities.ts index 9d751506a38..964a8334153 100644 --- a/packages/manager/src/features/Account/Maintenance/utilities.ts +++ b/packages/manager/src/features/Account/Maintenance/utilities.ts @@ -40,7 +40,7 @@ export const maintenanceDateColumnMap: Record< > = { completed: ['complete_time', 'End Date'], 'in progress': ['start_time', 'Start Date'], - upcoming: ['start_time', 'Start Date'], + upcoming: ['when', 'Start Date'], pending: ['when', 'Date'], }; From d3a52f4b7275f5f7ada95bce37b0f610a2d043a0 Mon Sep 17 00:00:00 2001 From: Jaalah Ramos Date: Sun, 23 Nov 2025 19:52:45 -0500 Subject: [PATCH 4/4] add max reason constant --- .../features/Account/Maintenance/MaintenanceTableRow.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/manager/src/features/Account/Maintenance/MaintenanceTableRow.tsx b/packages/manager/src/features/Account/Maintenance/MaintenanceTableRow.tsx index 96c5191581c..956431824b1 100644 --- a/packages/manager/src/features/Account/Maintenance/MaintenanceTableRow.tsx +++ b/packages/manager/src/features/Account/Maintenance/MaintenanceTableRow.tsx @@ -50,6 +50,8 @@ const statusIconMap: Record = { scheduled: 'active', }; +const MAX_REASON_DISPLAY_LENGTH = 93; + interface MaintenanceTableRowProps { maintenance: AccountMaintenance; tableType: MaintenanceTableType; @@ -77,7 +79,9 @@ export const MaintenanceTableRow = (props: MaintenanceTableRowProps) => { const eventProgress = recentEvent && formatProgressEvent(recentEvent); - const truncatedReason = reason ? truncate(reason, 93) : ''; + const truncatedReason = reason + ? truncate(reason, MAX_REASON_DISPLAY_LENGTH) + : ''; const isTruncated = reason ? reason !== truncatedReason : false;