diff --git a/.changeset/young-panthers-mix.md b/.changeset/young-panthers-mix.md new file mode 100644 index 0000000000..cbaf3b3704 --- /dev/null +++ b/.changeset/young-panthers-mix.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-tech-insights-backend': patch +'@backstage/plugin-microsoft-calendar': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/backend-tasks': patch +'@backstage/plugin-cloudbuild': patch +'@backstage/plugin-gcalendar': patch +'@backstage/plugin-xcmetrics': patch +'@backstage/plugin-ilert': patch +--- + +Update to handle invalid luxon values diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 8a0bbb0d69..a94851108d 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -2950,9 +2950,9 @@ __metadata: linkType: hard "@types/luxon@npm:^3.0.0": - version: 3.3.0 - resolution: "@types/luxon@npm:3.3.0" - checksum: f7e3a89fc3ca404fbc3ea538653ed6860bc28f570a8c4d6d24449b89b9b553b7d6ad6cc94a9e129c5b8c9a2b97f0c365b3017f811e59c4a859a9c219a1c918e0 + version: 3.3.2 + resolution: "@types/luxon@npm:3.3.2" + checksum: b9111132720eae0269538872a5a496b29587ecfc8edc3b0ff7d269aa93a5ff00a131b23d1e9d1f12ec39f2c779ad21bd8d9f90b122c85a182771aabde7f676b8 languageName: node linkType: hard @@ -7366,9 +7366,9 @@ __metadata: linkType: hard "luxon@npm:^3.0.0": - version: 3.3.0 - resolution: "luxon@npm:3.3.0" - checksum: 50cf17a0dc155c3dcacbeae8c0b7e80db425e0ba97b9cbdf12a7fc142d841ff1ab1560919f033af46240ed44e2f70c49f76e3422524c7fc8bb8d81ca47c66187 + version: 3.4.2 + resolution: "luxon@npm:3.4.2" + checksum: efefdfaea90f4c8e502db8eb255385e6dac6733b6a9e1372eaf1a866cc1118363e235a3f6df505b837abc8bbcdfd8f0718a8de387b80cef9ee624d8791ca0844 languageName: node linkType: hard diff --git a/packages/backend-tasks/src/setupTests.ts b/packages/backend-tasks/src/setupTests.ts index d3232290a7..5245d37c26 100644 --- a/packages/backend-tasks/src/setupTests.ts +++ b/packages/backend-tasks/src/setupTests.ts @@ -14,4 +14,7 @@ * limitations under the License. */ -export {}; +import { Settings } from 'luxon'; + +// TS still thinks that methods can return null / placeholders, but we still want to throw as soon as possible when things go wrong +Settings.throwOnInvalid = true; diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts index 253d7869df..b9ac80cb47 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts @@ -112,9 +112,15 @@ export function parseDuration( return frequency.cron; } - if (Duration.isDuration(frequency)) { - return frequency.toISO(); + const parsed = Duration.isDuration(frequency) + ? frequency + : Duration.fromObject(frequency); + + if (!parsed.isValid) { + throw new Error( + `Invalid duration, ${parsed.invalidReason}: ${parsed.invalidExplanation}`, + ); } - return Duration.fromObject(frequency).toISO(); + return parsed.toISO()!; } diff --git a/packages/backend-tasks/src/tasks/TaskWorker.test.ts b/packages/backend-tasks/src/tasks/TaskWorker.test.ts index b8b24e045a..70be2bf415 100644 --- a/packages/backend-tasks/src/tasks/TaskWorker.test.ts +++ b/packages/backend-tasks/src/tasks/TaskWorker.test.ts @@ -47,8 +47,8 @@ describe('TaskWorker', () => { const settings: TaskSettingsV2 = { version: 2, cadence: '*/2 * * * * *', - initialDelayDuration: Duration.fromObject({ seconds: 1 }).toISO(), - timeoutAfterDuration: Duration.fromObject({ minutes: 1 }).toISO(), + initialDelayDuration: Duration.fromObject({ seconds: 1 }).toISO()!, + timeoutAfterDuration: Duration.fromObject({ minutes: 1 }).toISO()!, }; const worker = new TaskWorker('task1', fn, knex, logger); @@ -131,7 +131,7 @@ describe('TaskWorker', () => { version: 2, initialDelayDuration: undefined, cadence: '* * * * * *', - timeoutAfterDuration: Duration.fromMillis(60000).toISO(), + timeoutAfterDuration: Duration.fromMillis(60000).toISO()!, }; const checkFrequency = Duration.fromObject({ milliseconds: 100 }); const worker = new TaskWorker('task1', fn, knex, logger, checkFrequency); @@ -154,7 +154,7 @@ describe('TaskWorker', () => { version: 2, initialDelayDuration: undefined, cadence: '* * * * * *', - timeoutAfterDuration: Duration.fromMillis(60000).toISO(), + timeoutAfterDuration: Duration.fromMillis(60000).toISO()!, }; const checkFrequency = Duration.fromObject({ milliseconds: 100 }); const worker = new TaskWorker('task1', fn, knex, logger, checkFrequency); @@ -179,7 +179,7 @@ describe('TaskWorker', () => { version: 2, initialDelayDuration: undefined, cadence: '* * * * * *', - timeoutAfterDuration: Duration.fromMillis(60000).toISO(), + timeoutAfterDuration: Duration.fromMillis(60000).toISO()!, }; const worker = new TaskWorker('task1', fn, knex, logger); @@ -235,7 +235,7 @@ describe('TaskWorker', () => { version: 2, initialDelayDuration: undefined, cadence: '* * * * * *', - timeoutAfterDuration: Duration.fromMillis(60000).toISO(), + timeoutAfterDuration: Duration.fromMillis(60000).toISO()!, }; const worker1 = new TaskWorker('task1', fn, knex, logger); diff --git a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts index 61a062bc4a..01025f73dd 100644 --- a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts +++ b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts @@ -375,7 +375,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { entityRef, newLocationKey: locationKey, existingLocationKey: conflictingKey, - lastConflictAt: DateTime.now().toISO(), + lastConflictAt: DateTime.now().toISO()!, }, }; await this.options.eventBroker?.publish(eventParams); diff --git a/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index 736e641dbd..294847a700 100644 --- a/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx +++ b/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -87,7 +87,7 @@ const generatedColumns: TableColumn[] = [ title: 'Created', render: (row: Partial) => ( - {DateTime.fromISO(row.createTime ?? DateTime.now().toISO()).toFormat( + {DateTime.fromISO(row.createTime ?? DateTime.now().toISO()!).toFormat( 'dd-MM-yyyy hh:mm:ss', )} diff --git a/plugins/gcalendar/dev/mocks.ts b/plugins/gcalendar/dev/mocks.ts index c566527590..b6a9f6793c 100644 --- a/plugins/gcalendar/dev/mocks.ts +++ b/plugins/gcalendar/dev/mocks.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { DateTime } from 'luxon'; import { GCalendar, GCalendarEvent } from '../src/api'; @@ -38,11 +39,11 @@ export const eventsMock: GCalendarEvent[] = [...Array(3).keys()].map(i => ({ start: { dateTime: DateTime.now() .minus({ hour: i + 1 }) - .toISO(), + .toISO()!, timeZone: 'Europe/London', }, end: { - dateTime: DateTime.now().minus({ hour: i }).toISO(), + dateTime: DateTime.now().minus({ hour: i }).toISO()!, timeZone: 'Europe/London', }, description: '

Dummy title

Dummy description

', diff --git a/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx b/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx index 0a9b517293..26d6f1eed0 100644 --- a/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx +++ b/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { sortBy } from 'lodash'; import { DateTime } from 'luxon'; import React, { useState } from 'react'; @@ -67,9 +68,9 @@ export const CalendarCard = () => { calendars, selectedCalendars: storedCalendars, enabled: isSignedIn && calendars.length > 0, - timeMin: date.startOf('day').toISO(), - timeMax: date.endOf('day').toISO(), - timeZone: date.zoneName, + timeMin: date.startOf('day').toISO()!, + timeMax: date.endOf('day').toISO()!, + timeZone: date.zoneName ?? 'UTC', // TODO: Use browser timezone? This probably never happens anyway }); const showLoader = diff --git a/plugins/gcalendar/src/hooks/useEventsQuery.ts b/plugins/gcalendar/src/hooks/useEventsQuery.ts index 5dd5fb9b32..59de67a1a9 100644 --- a/plugins/gcalendar/src/hooks/useEventsQuery.ts +++ b/plugins/gcalendar/src/hooks/useEventsQuery.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { compact, unescape } from 'lodash'; import { useMemo } from 'react'; import { useQueries } from '@tanstack/react-query'; diff --git a/plugins/ilert/src/components/Shift/ShiftOverrideModal.tsx b/plugins/ilert/src/components/Shift/ShiftOverrideModal.tsx index 618c35dbc3..63af27c725 100644 --- a/plugins/ilert/src/components/Shift/ShiftOverrideModal.tsx +++ b/plugins/ilert/src/components/Shift/ShiftOverrideModal.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; @@ -161,7 +162,7 @@ export const ShiftOverrideModal = ({ value={start} className={classes.formControl} onChange={date => { - setStart(date ? date.toISO() : ''); + setStart(date?.toISO() ?? ''); }} /> { - setEnd(date ? date.toISO() : ''); + setEnd(date?.toISO() ?? ''); }} /> diff --git a/plugins/kubernetes/src/components/Pods/PodDrawer/PendingPodContent.test.tsx b/plugins/kubernetes/src/components/Pods/PodDrawer/PendingPodContent.test.tsx index e4d042217b..8896654696 100644 --- a/plugins/kubernetes/src/components/Pods/PodDrawer/PendingPodContent.test.tsx +++ b/plugins/kubernetes/src/components/Pods/PodDrawer/PendingPodContent.test.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { render } from '@testing-library/react'; @@ -44,7 +45,7 @@ const podWithConditions = (conditions: IPodCondition[]): any => { describe('PendingPodContent', () => { it('show startup conditions - all healthy', async () => { - const oneDayAgo = DateTime.now().minus({ days: 1 }).toISO(); + const oneDayAgo = DateTime.now().minus({ days: 1 }).toISO()!; const { getByText, queryByLabelText, queryAllByLabelText } = render( wrapInTestApp( { expect(queryByLabelText('Status error')).not.toBeInTheDocument(); }); it('show startup conditions - all fail', async () => { - const oneHourAgo = DateTime.now().minus({ hours: 1 }).toISO(); + const oneHourAgo = DateTime.now().minus({ hours: 1 }).toISO()!; const { getByText, queryByLabelText, queryAllByLabelText } = render( wrapInTestApp( { expect(queryAllByLabelText('Status error')).toHaveLength(4); }); it('show startup conditions - show unknown', async () => { - const oneHourAgo = DateTime.now().minus({ hours: 1 }).toISO(); + const oneHourAgo = DateTime.now().minus({ hours: 1 }).toISO()!; const { getByText, queryByLabelText, getByLabelText } = render( wrapInTestApp( { const { data: events, isLoading: isEventLoading } = useEventsQuery({ calendarId: selectedCalendarId || defaultCalendarId || '', enabled: isSignedIn && calendars.length > 0, - timeMin: date.startOf('day').toISO(), - timeMax: date.endOf('day').toISO(), - timeZone: date.zoneName, + timeMin: date.startOf('day').toISO()!, + timeMax: date.endOf('day').toISO()!, + timeZone: date.zoneName ?? undefined, }); const showLoader = diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index c96bd6b9cd..40ffdb17d4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -69,7 +69,7 @@ export type DatabaseTaskStoreOptions = { }; /** - * Typeguard to help DatabaseTaskStore understand when database is PluginDatabaseManager vs. when database is a Knex instance. + * Type guard to help DatabaseTaskStore understand when database is PluginDatabaseManager vs. when database is a Knex instance. * * * @public */ @@ -81,7 +81,13 @@ function isPluginDatabaseManager( const parseSqlDateToIsoString = (input: T): T | string => { if (typeof input === 'string') { - return DateTime.fromSQL(input, { zone: 'UTC' }).toISO(); + const parsed = DateTime.fromSQL(input, { zone: 'UTC' }); + if (!parsed.isValid) { + throw new Error( + `Failed to parse database timestamp '${input}', ${parsed.invalidReason}: ${parsed.invalidExplanation}`, + ); + } + return parsed.toISO()!; } return input; diff --git a/plugins/scaffolder/src/components/ListTasksPage/columns/CreatedAtColumn.test.tsx b/plugins/scaffolder/src/components/ListTasksPage/columns/CreatedAtColumn.test.tsx index a30c1d370b..9cd1353ae1 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/columns/CreatedAtColumn.test.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/columns/CreatedAtColumn.test.tsx @@ -23,7 +23,7 @@ import { DateTime } from 'luxon'; describe('', () => { it('should render the column with the time', async () => { const props = { - createdAt: DateTime.now().toISO(), + createdAt: DateTime.now().toISO()!, }; const { getByText } = await renderInTestApp(); diff --git a/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx index 5bf6b1e92b..6e04e65ef0 100644 --- a/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx +++ b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx @@ -63,7 +63,7 @@ describe('SentryIssuesTable', () => { }, count: '101', userCount: 202, - lastSeen: DateTime.now().toISO(), + lastSeen: DateTime.now().toISO()!, }, ]; const table = await renderInTestApp( diff --git a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.test.ts b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.test.ts index 3cf6d08c65..d778e84c79 100644 --- a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.test.ts +++ b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.test.ts @@ -74,13 +74,13 @@ const secondSchema = { }), }; -const now = DateTime.now().toISO(); +const now = DateTime.now().toISO()!; const shortlyInTheFuture = DateTime.now() .plus(Duration.fromMillis(555)) - .toISO(); + .toISO()!; const farInTheFuture = DateTime.now() .plus(Duration.fromMillis(555666777)) - .toISO(); + .toISO()!; const facts = [ { @@ -127,13 +127,13 @@ const sameFactsDiffDateSchema = { }), }; -const sameFactsDiffDateNow = DateTime.now().toISO(); +const sameFactsDiffDateNow = DateTime.now().toISO()!; const sameFactsDiffDateNearFuture = DateTime.now() .plus(Duration.fromMillis(555)) - .toISO(); + .toISO()!; const sameFactsDiffDateFuture = DateTime.now() .plus(Duration.fromMillis(1000)) - .toISO(); + .toISO()!; const multipleSameFacts = [ { diff --git a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts index c8f098a895..761a6bd273 100644 --- a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts +++ b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { Knex } from 'knex'; import { FactLifecycle, @@ -105,12 +106,13 @@ export class TechInsightsDatabase implements TechInsightsStore { if (facts.length === 0) return; const currentSchema = await this.getLatestSchema(id); const factRows = facts.map(it => { + const ts = it.timestamp?.toISO(); return { id, version: currentSchema.version, entity: stringifyEntityRef(it.entity), facts: JSON.stringify(it.facts), - ...(it.timestamp && { timestamp: it.timestamp.toISO() }), + ...(ts && { timestamp: ts }), }; }); diff --git a/plugins/xcmetrics/src/components/BuildList/BuildList.test.tsx b/plugins/xcmetrics/src/components/BuildList/BuildList.test.tsx index 94178d0388..ee22647b2b 100644 --- a/plugins/xcmetrics/src/components/BuildList/BuildList.test.tsx +++ b/plugins/xcmetrics/src/components/BuildList/BuildList.test.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { BuildList } from './BuildList'; diff --git a/plugins/xcmetrics/src/components/BuildList/BuildList.tsx b/plugins/xcmetrics/src/components/BuildList/BuildList.tsx index 5a6e1bd322..20924b181b 100644 --- a/plugins/xcmetrics/src/components/BuildList/BuildList.tsx +++ b/plugins/xcmetrics/src/components/BuildList/BuildList.tsx @@ -39,8 +39,8 @@ export const BuildList = () => { const tableRef = useRef(); const initialFilters = { - from: DateTime.now().minus({ years: 1 }).toISODate(), - to: DateTime.now().toISODate(), + from: DateTime.now().minus({ years: 1 }).toISODate()!, + to: DateTime.now().toISODate()!, }; const [filters, setFilters] = useState(initialFilters); diff --git a/yarn.lock b/yarn.lock index 7437bf7049..5aefa381ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17819,9 +17819,9 @@ __metadata: linkType: hard "@types/luxon@npm:*, @types/luxon@npm:^3.0.0": - version: 3.2.0 - resolution: "@types/luxon@npm:3.2.0" - checksum: 051bfbf841c6ce98728df6538342ce4aaddfcf0ec6e8b473e0195dcfc3ba6b1fe4b3ce17f2ba6d25344f41cb70032c1debe96da9e384e5f6e639665c1e6d368c + version: 3.3.2 + resolution: "@types/luxon@npm:3.3.2" + checksum: b9111132720eae0269538872a5a496b29587ecfc8edc3b0ff7d269aa93a5ff00a131b23d1e9d1f12ec39f2c779ad21bd8d9f90b122c85a182771aabde7f676b8 languageName: node linkType: hard @@ -32024,9 +32024,9 @@ __metadata: linkType: hard "luxon@npm:^3.0.0, luxon@npm:^3.2.1, luxon@npm:^3.3.0": - version: 3.3.0 - resolution: "luxon@npm:3.3.0" - checksum: 50cf17a0dc155c3dcacbeae8c0b7e80db425e0ba97b9cbdf12a7fc142d841ff1ab1560919f033af46240ed44e2f70c49f76e3422524c7fc8bb8d81ca47c66187 + version: 3.4.2 + resolution: "luxon@npm:3.4.2" + checksum: efefdfaea90f4c8e502db8eb255385e6dac6733b6a9e1372eaf1a866cc1118363e235a3f6df505b837abc8bbcdfd8f0718a8de387b80cef9ee624d8791ca0844 languageName: node linkType: hard