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/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/.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)) 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 9645722c188..3fc62a6fb2c 100644 --- a/packages/manager/src/features/Account/Maintenance/MaintenanceTableRow.tsx +++ b/packages/manager/src/features/Account/Maintenance/MaintenanceTableRow.tsx @@ -47,6 +47,8 @@ const statusIconMap: Record = { scheduled: 'active', }; +const MAX_REASON_DISPLAY_LENGTH = 93; + interface MaintenanceTableRowProps { maintenance: AccountMaintenance; tableType: MaintenanceTableType; @@ -74,9 +76,11 @@ export const MaintenanceTableRow = (props: MaintenanceTableRowProps) => { const eventProgress = recentEvent && formatProgressEvent(recentEvent); - const truncatedReason = truncate(reason, 93); + const truncatedReason = reason + ? truncate(reason, MAX_REASON_DISPLAY_LENGTH) + : ''; - 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/Account/Maintenance/utilities.ts b/packages/manager/src/features/Account/Maintenance/utilities.ts index bb1c7cbe883..f202f33691d 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'], }; 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) => (