fix: run workflow steps in their configured order (#32241)

fix: run workflow steps in their configured order (#32240)



(cherry picked from commit 56319b76fc60d7e3bd57a2275ac908da42665e5f)

Co-authored-by: infocus13 <74890563+infocus13@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
This commit is contained in:
immich-push-o-matic[bot]
2026-10-08 19:48:52 +00:00
committed by GitHub
co-authored by infocus13 Claude Fable 5.1 Daniel Dietzler
parent f607b886f0
commit 5b2a3a52be
3 changed files with 39 additions and 1 deletions
@@ -95,6 +95,8 @@ select
where
"workflow_step"."workflowId" = "workflow"."id"
and "workflow_step"."enabled" = $1
order by
"workflow_step"."order" asc
) as agg
) as "steps"
from
@@ -76,6 +76,7 @@ export class WorkflowRepository {
.innerJoin('plugin_method', 'plugin_method.id', 'workflow_step.pluginMethodId')
.whereRef('workflow_step.workflowId', '=', 'workflow.id')
.where('workflow_step.enabled', '=', true)
.orderBy('workflow_step.order', 'asc')
.select([
'workflow_step.id',
'workflow_step.config',
@@ -109,8 +109,10 @@ const createWorkflow = async (template: WorkflowTemplate) => {
let ctx: WorkflowTestContext;
let db: Kysely<DB>;
beforeAll(async () => {
const db = await getKyselyDB();
db = await getKyselyDB();
ctx = new WorkflowTestContext(db);
await ctx.init();
}, 30_000);
@@ -750,4 +752,37 @@ describe('core plugin', () => {
vi.unstubAllGlobals();
});
});
describe('step order', () => {
it('should run steps by their order, not by the order the rows come back', async () => {
const { user } = await ctx.newUser();
const { asset } = await ctx.newAsset({ ownerId: user.id, originalFileName: 'nomatch.jpg' });
// Insert the action BEFORE the filter so a plain scan returns it first, then give the filter order 0.
const workflow = await createWorkflow({
ownerId: user.id,
trigger: WorkflowTrigger.AssetCreate,
steps: [
{ method: 'immich-plugin-core#assetFavorite' },
{ method: 'immich-plugin-core#assetFileFilter', config: { matchType: 'contains', pattern: 'screenshot' } },
],
});
const steps = await db
.selectFrom('workflow_step')
.innerJoin('plugin_method', 'plugin_method.id', 'workflow_step.pluginMethodId')
.select(['workflow_step.id', 'plugin_method.name'])
.where('workflow_step.workflowId', '=', workflow.id)
.execute();
for (const step of steps) {
await db
.updateTable('workflow_step')
.set({ order: step.name === 'assetFileFilter' ? 0 : 1 })
.where('id', '=', step.id)
.execute();
}
await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined();
await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ isFavorite: false });
});
});
});