Skip to content

Commit f8c1ad5

Browse files
authored
Filtered upgrade logs when stopping before starting next instance segment (#20078)
# Introduction In a nutshell, added a more readable logs that relates that when performing a workspace fitlered workspace you can only browser the workspace commands sequence This PR is not a fix, but a log improvement As before when cross-upgrading a single workspace you would be facing a `instance` sync barrier error as not all your workspaces would have been updated Now logging and early returning instead of letting the guard hard throw ## Tests Created a dedicated test for instance prevention on filtered upgrade Standardized `migrationRecordToKey` usage across all upgrade integration tests suites
1 parent a90895e commit f8c1ad5

5 files changed

Lines changed: 216 additions & 218 deletions

File tree

packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-sequence-runner.service.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,19 @@ export class UpgradeSequenceRunnerService {
6868
const step = sequence[cursor];
6969

7070
if (step.kind === 'fast-instance' || step.kind === 'slow-instance') {
71+
if (
72+
isDefined(options.workspaceIds) &&
73+
options.workspaceIds.length > 0
74+
) {
75+
this.logger.log(
76+
`Stopping before instance step "${step.name}": ` +
77+
'upgrade was run with workspace filter (-w). ' +
78+
'Instance commands require all workspaces to be aligned.',
79+
);
80+
81+
break;
82+
}
83+
7184
const previousStep = cursor > 0 ? sequence[cursor - 1] : undefined;
7285

7386
if (previousStep?.kind === 'workspace') {

packages/twenty-server/test/integration/upgrade/suites/sequence-runner/failing-sequence-runner.integration-spec.ts

Lines changed: 42 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ import {
1010
makeSlowInstance,
1111
makeStep,
1212
makeWorkspace,
13+
migrationRecordToKey,
1314
resetSeedSequenceCounter,
1415
seedInstanceMigration,
1516
seedWorkspaceMigration,
1617
setMockActiveWorkspaceIds,
17-
testGetLatestMigrationForCommand,
18+
testGetExecutedMigrationsInOrder,
1819
WS_1,
1920
WS_2,
2021
} from 'test/integration/upgrade/utils/upgrade-sequence-runner-integration-test.util';
@@ -198,13 +199,15 @@ describe('UpgradeSequenceRunnerService — failing sequence (integration)', () =
198199
}),
199200
).rejects.toThrow('fast command exploded');
200201

201-
const ic2 = await testGetLatestMigrationForCommand(context.dataSource, {
202-
name: 'Ic2',
203-
});
202+
const executed = await testGetExecutedMigrationsInOrder(context.dataSource);
204203

205-
expect(ic2).toEqual(
206-
expect.objectContaining({ name: 'Ic2', status: 'failed' }),
207-
);
204+
expect(executed.map(migrationRecordToKey)).toStrictEqual([
205+
// Seeds
206+
'Ic1:instance:completed:1',
207+
208+
// Ic2 attempted and failed
209+
'Ic2:instance:failed:1',
210+
]);
208211
});
209212

210213
it('should record failure in DB when a slow instance command fails', async () => {
@@ -238,13 +241,17 @@ describe('UpgradeSequenceRunnerService — failing sequence (integration)', () =
238241
}),
239242
).rejects.toThrow('slow data migration exploded');
240243

241-
const ic2 = await testGetLatestMigrationForCommand(context.dataSource, {
242-
name: 'Ic2',
243-
});
244+
const executed = await testGetExecutedMigrationsInOrder(context.dataSource);
244245

245-
expect(ic2).toEqual(
246-
expect.objectContaining({ name: 'Ic2', status: 'failed' }),
247-
);
246+
expect(executed.map(migrationRecordToKey)).toStrictEqual([
247+
// Seeds
248+
'Ic1:instance:completed:1',
249+
`Ic1:${WS_1}:completed:1`,
250+
251+
// Ic2 attempted and failed
252+
'Ic2:instance:failed:1',
253+
`Ic2:${WS_1}:failed:1`,
254+
]);
248255
});
249256

250257
it('should abort and report failures when workspace commands fail, without running subsequent instance steps', async () => {
@@ -270,20 +277,15 @@ describe('UpgradeSequenceRunnerService — failing sequence (integration)', () =
270277
expect(report.totalFailures).toBe(1);
271278
expect(report.totalSuccesses).toBe(0);
272279

273-
const ic1 = await testGetLatestMigrationForCommand(context.dataSource, {
274-
name: 'Ic1',
275-
});
280+
const executed = await testGetExecutedMigrationsInOrder(context.dataSource);
276281

277-
expect(ic1).toBeNull();
282+
expect(executed.map(migrationRecordToKey)).toStrictEqual([
283+
// Seeds
284+
`Wc1:${WS_1}:failed:1`,
278285

279-
const wc1 = await testGetLatestMigrationForCommand(context.dataSource, {
280-
name: 'Wc1',
281-
workspaceId: WS_1,
282-
});
283-
284-
expect(wc1).toEqual(
285-
expect.objectContaining({ status: 'failed', attempt: 2 }),
286-
);
286+
// Runner retries Wc1, still fails — Ic1 never reached
287+
`Wc1:${WS_1}:failed:2`,
288+
]);
287289
});
288290

289291
it('should abort at workspace failure in a multi-segment sequence with two workspaces starting aligned', async () => {
@@ -337,39 +339,23 @@ describe('UpgradeSequenceRunnerService — failing sequence (integration)', () =
337339
expect(report.totalSuccesses).toBe(1);
338340
expect(report.totalFailures).toBe(1);
339341

340-
// WS_1 succeeded Wc2
341-
const ws1Wc2 = await testGetLatestMigrationForCommand(context.dataSource, {
342-
name: 'Wc2',
343-
workspaceId: WS_1,
344-
});
345-
346-
expect(ws1Wc2).toEqual(
347-
expect.objectContaining({ name: 'Wc2', status: 'completed' }),
348-
);
349-
350-
// WS_2 failed Wc2
351-
const ws2Wc2 = await testGetLatestMigrationForCommand(context.dataSource, {
352-
name: 'Wc2',
353-
workspaceId: WS_2,
354-
});
342+
const executed = await testGetExecutedMigrationsInOrder(context.dataSource);
355343

356-
expect(ws2Wc2).toEqual(
357-
expect.objectContaining({ name: 'Wc2', status: 'failed' }),
358-
);
359-
360-
// Ic2 never ran — runner aborted at the workspace segment failure
361-
const ic2 = await testGetLatestMigrationForCommand(context.dataSource, {
362-
name: 'Ic2',
363-
});
364-
365-
expect(ic2).toBeNull();
344+
expect(executed.map(migrationRecordToKey)).toStrictEqual([
345+
// Seeds
346+
`Wc0:${WS_1}:completed:1`,
347+
`Wc0:${WS_2}:completed:1`,
348+
'Ic1:instance:completed:1',
349+
`Ic1:${WS_1}:completed:1`,
350+
`Ic1:${WS_2}:completed:1`,
351+
`Wc1:${WS_1}:completed:1`,
352+
`Wc1:${WS_2}:completed:1`,
366353

367-
// Wc3 never ran either
368-
const ws1Wc3 = await testGetLatestMigrationForCommand(context.dataSource, {
369-
name: 'Wc3',
370-
workspaceId: WS_1,
371-
});
354+
// WS_1 succeeds Wc2, WS_2 fails Wc2
355+
`Wc2:${WS_1}:completed:1`,
356+
`Wc2:${WS_2}:failed:1`,
372357

373-
expect(ws1Wc3).toBeNull();
358+
// Ic2 and Wc3 never reached — runner aborted at workspace failure
359+
]);
374360
});
375361
});

0 commit comments

Comments
 (0)