From 6e5a4fdf0093ea59360b64e81082c6d08f955bb2 Mon Sep 17 00:00:00 2001 From: "immich-push-o-matic[bot]" <179150890+immich-push-o-matic[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 15:50:49 +0000 Subject: [PATCH] fix: assetTagFilter any matching (#32027) fix: assetTagFilter any matching (#32008) (cherry picked from commit 28e375c0585f8ee69f80257dc0be073a2b114cdf) Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> --- packages/plugin-core/src/index.ts | 4 +- .../workflow/workflow-core-plugin.spec.ts | 121 ++++++++++++++++++ 2 files changed, 123 insertions(+), 2 deletions(-) diff --git a/packages/plugin-core/src/index.ts b/packages/plugin-core/src/index.ts index 251393ace..ec6a3bb69 100644 --- a/packages/plugin-core/src/index.ts +++ b/packages/plugin-core/src/index.ts @@ -187,7 +187,7 @@ const methods = wrapper({ for (const tag of config.tags) { if (assetTags.includes(tag)) { if (config.matching === 'any') { - break; + return { workflow: { continue: true } }; } else if (config.matching === 'none') { return { workflow: { continue: false } }; } @@ -196,7 +196,7 @@ const methods = wrapper({ } } - return { workflow: { continue: true } }; + return { workflow: { continue: config.matching !== 'any' } }; }, assetTypeFilter: ({ config, data }) => { diff --git a/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts b/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts index 6ae7a8ba7..77a8cdce7 100644 --- a/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts +++ b/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts @@ -21,6 +21,7 @@ import { WorkflowExecutionService } from 'src/services/workflow-execution.servic import { resolveMethod } from 'src/utils/workflow.js'; import { MediumTestContext } from 'test/medium.factory.js'; import { mockEnvData } from 'test/repositories/config.repository.mock.js'; +import { newUuid } from 'test/small.factory.js'; import { getKyselyDB } from 'test/utils.js'; let isInitialized = false; @@ -602,6 +603,126 @@ describe('core plugin', () => { }); }); + describe('assetTagFilter', () => { + it('should favorite asset if it matches any tag', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + const { tag } = await ctx.newTag({ userId: user.id, value: 'foo' }); + await ctx.newTagAsset({ tagIds: [tag.id], assetIds: [asset.id] }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [ + { + method: 'immich-plugin-core#assetTagFilter', + config: { matching: 'any', tags: [tag.id, newUuid()] }, + }, + { + method: 'immich-plugin-core#assetFavorite', + }, + ], + }); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ isFavorite: true }); + }); + + it('should not favorite asset if it has no tags with any filter', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + const { tag } = await ctx.newTag({ userId: user.id, value: 'foo' }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [ + { + method: 'immich-plugin-core#assetTagFilter', + config: { matching: 'any', tags: [tag.id, newUuid()] }, + }, + { + method: 'immich-plugin-core#assetFavorite', + }, + ], + }); + + 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 }); + }); + + it('should favorite asset if it matches all tags', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + const { tag } = await ctx.newTag({ userId: user.id, value: 'foo' }); + await ctx.newTagAsset({ tagIds: [tag.id], assetIds: [asset.id] }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [ + { + method: 'immich-plugin-core#assetTagFilter', + config: { matching: 'all', tags: [tag.id] }, + }, + { + method: 'immich-plugin-core#assetFavorite', + }, + ], + }); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ isFavorite: true }); + }); + + it('should not favorite asset if it has no tags with all filter', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + const { tag } = await ctx.newTag({ userId: user.id, value: 'foo' }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [ + { + method: 'immich-plugin-core#assetTagFilter', + config: { matching: 'all', tags: [tag.id, newUuid()] }, + }, + { + method: 'immich-plugin-core#assetFavorite', + }, + ], + }); + + 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 }); + }); + + it('should favorite asset if it has none of the tags', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + const { tag } = await ctx.newTag({ userId: user.id, value: 'foo' }); + await ctx.newTagAsset({ tagIds: [tag.id], assetIds: [asset.id] }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [ + { + method: 'immich-plugin-core#assetTagFilter', + config: { matching: 'none', tags: [newUuid()] }, + }, + { + method: 'immich-plugin-core#assetFavorite', + }, + ], + }); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ isFavorite: true }); + }); + }); + describe('webhook', () => { it('should trigger a webhook on asset upload', async () => { const { user } = await ctx.newUser();