mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
fix(editor): Fix the last updated label in the migration report (no-changelog) (#40889)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2a51f38d55
commit
7ceb84f06c
|
|
@ -156,7 +156,8 @@ const breakingChangeReportDataSchema = {
|
|||
const breakingChangeReportSchema = z.object(breakingChangeReportDataSchema).strict();
|
||||
|
||||
const breakingChangeLightReportDataSchema = {
|
||||
generatedAt: z.date(),
|
||||
// Absent until the first sync of the target version completes.
|
||||
generatedAt: z.date().optional(),
|
||||
targetVersion: z.string(),
|
||||
currentVersion: z.string(),
|
||||
instanceResults: z.array(instanceRuleResultsSchema),
|
||||
|
|
|
|||
|
|
@ -33,8 +33,9 @@ export const MIGRATION_REPORT_TELEMETRY = defineTelemetryEvents({
|
|||
.describe('One entry per workflow rule with at least one open finding'),
|
||||
synced_at: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'ISO time the finding table was last filled, which the overview shows as the report date',
|
||||
'ISO time the finding table was last filled, which the overview shows as the report date. Absent before the first sync',
|
||||
),
|
||||
}),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4672,6 +4672,30 @@ describe('TelemetryEventRelay', () => {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('omits synced_at when the report has no sync time', () => {
|
||||
eventService.emit('migration-report-viewed', {
|
||||
user: { id: 'user-1' },
|
||||
targetVersion: 'v3',
|
||||
refreshed: false,
|
||||
report: {
|
||||
report: {
|
||||
targetVersion: 'v3',
|
||||
currentVersion: '2.0.0',
|
||||
instanceResults: [],
|
||||
workflowResults: [],
|
||||
},
|
||||
totalWorkflows: 0,
|
||||
totalAffectedWorkflows: 0,
|
||||
shouldCache: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(telemetry.track).toHaveBeenCalledWith(
|
||||
TELEMETRY_EVENT.MIGRATION_REPORT.USER_VIEWED_MIGRATION_REPORT,
|
||||
expect.objectContaining({ synced_at: undefined }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('workflow history compaction events', () => {
|
||||
|
|
|
|||
|
|
@ -2408,7 +2408,9 @@ export class TelemetryEventRelay extends EventRelay {
|
|||
impact: rule.ruleImpact,
|
||||
affected_workflows: rule.nbAffectedWorkflows,
|
||||
})),
|
||||
synced_at: new Date(report.report.generatedAt).toISOString(),
|
||||
synced_at: report.report.generatedAt
|
||||
? new Date(report.report.generatedAt).toISOString()
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -325,16 +325,10 @@ describe('MigrationFindingQueryService', () => {
|
|||
expect(workflowRepository.count).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('falls back to the current time as generatedAt when no sync ran yet', async () => {
|
||||
const now = new Date('2026-06-01T12:00:00.000Z');
|
||||
vi.useFakeTimers({ now });
|
||||
try {
|
||||
const result = await service.getLightReport(TARGET_VERSION, INSTANCE);
|
||||
it('reports no generatedAt when no sync ran yet', async () => {
|
||||
const result = await service.getLightReport(TARGET_VERSION, INSTANCE);
|
||||
|
||||
expect(result.report.generatedAt).toEqual(now);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
expect(result.report.generatedAt).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -134,8 +134,7 @@ export class MigrationFindingQueryService {
|
|||
|
||||
return {
|
||||
report: {
|
||||
// Before the first sync there is nothing to date, so the read time stands in.
|
||||
generatedAt: sync?.syncedAt ?? new Date(),
|
||||
generatedAt: sync?.syncedAt,
|
||||
targetVersion,
|
||||
currentVersion: N8N_VERSION,
|
||||
instanceResults,
|
||||
|
|
|
|||
|
|
@ -4613,7 +4613,8 @@
|
|||
"settings.migrationReport.progress.label": "{compatibleCount} of {totalCount} compatible",
|
||||
"settings.migrationReport.refreshButton": "Refresh",
|
||||
"settings.migrationReport.scopeNote": "This report only includes the workflows you can edit.",
|
||||
"settings.migrationReport.lastSynced": "Last synced {time}",
|
||||
"settings.migrationReport.lastUpdated": "Last updated {time}",
|
||||
"settings.migrationReport.neverUpdated": "Never updated",
|
||||
"settings.migrationReport.tabs.workflowIssues": "Workflow issues",
|
||||
"settings.migrationReport.tabs.instanceIssues": "Instance issues",
|
||||
"settings.migrationReport.impact.upgradeBlocked": "Upgrade blocked",
|
||||
|
|
|
|||
|
|
@ -545,7 +545,7 @@ describe('MigrationRules', () => {
|
|||
},
|
||||
);
|
||||
|
||||
it('should show when the report was last synced, from generatedAt', async () => {
|
||||
it('should show when the report was last updated, from generatedAt', async () => {
|
||||
// One year in the past, so the relative label is stable whatever the test date.
|
||||
const generatedAt = new Date(Date.now() - 365 * 24 * 60 * 60 * 1000);
|
||||
vi.mocked(breakingChangesApi.getReport).mockResolvedValue(
|
||||
|
|
@ -563,12 +563,26 @@ describe('MigrationRules', () => {
|
|||
renderComponent();
|
||||
|
||||
await waitFor(() => {
|
||||
const lastSynced = screen.getByTestId('migration-report-last-synced');
|
||||
expect(lastSynced).toHaveTextContent(/Last synced\s+1 year ago/);
|
||||
const lastUpdated = screen.getByTestId('migration-report-last-updated');
|
||||
expect(lastUpdated).toHaveTextContent(/Last updated\s+1 year ago/);
|
||||
});
|
||||
});
|
||||
|
||||
it('should update the last synced time after Refresh', async () => {
|
||||
it('should show "Never updated" when the report has no generatedAt', async () => {
|
||||
vi.mocked(breakingChangesApi.getReport).mockResolvedValue(
|
||||
createMockReport({ report: { ...mockReport.report, generatedAt: undefined } }),
|
||||
);
|
||||
|
||||
renderComponent();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('migration-report-last-updated')).toHaveTextContent(
|
||||
'Never updated',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should update the last updated time after Refresh', async () => {
|
||||
const oldDate = new Date(Date.now() - 365 * 24 * 60 * 60 * 1000);
|
||||
const newDate = new Date(Date.now() - 60 * 1000);
|
||||
vi.mocked(breakingChangesApi.getReport).mockResolvedValue(
|
||||
|
|
@ -581,13 +595,13 @@ describe('MigrationRules', () => {
|
|||
renderComponent();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('migration-report-last-synced')).toHaveTextContent(/1 year ago/);
|
||||
expect(screen.getByTestId('migration-report-last-updated')).toHaveTextContent(/1 year ago/);
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Refresh'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('migration-report-last-synced')).toHaveTextContent(
|
||||
expect(screen.getByTestId('migration-report-last-updated')).toHaveTextContent(
|
||||
/1 minute ago/,
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -218,14 +218,24 @@ const sortedInstanceResults = computed(() => {
|
|||
v-if="state?.report.generatedAt"
|
||||
size="small"
|
||||
color="text-light"
|
||||
data-test-id="migration-report-last-synced"
|
||||
:class="$style.NoLineBreak"
|
||||
data-test-id="migration-report-last-updated"
|
||||
>
|
||||
<I18nT keypath="settings.migrationReport.lastSynced" tag="span" scope="global">
|
||||
<I18nT keypath="settings.migrationReport.lastUpdated" tag="span" scope="global">
|
||||
<template #time>
|
||||
<TimeAgo :date="state.report.generatedAt.toString()" />
|
||||
</template>
|
||||
</I18nT>
|
||||
</N8nText>
|
||||
<N8nText
|
||||
v-else-if="state"
|
||||
size="small"
|
||||
color="text-light"
|
||||
:class="$style.NoLineBreak"
|
||||
data-test-id="migration-report-last-updated"
|
||||
>
|
||||
{{ i18n.baseText('settings.migrationReport.neverUpdated') }}
|
||||
</N8nText>
|
||||
<N8nButton
|
||||
v-if="canManageReport"
|
||||
variant="subtle"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user