From c3e23a6b3a674435134a0c267ab8162ab8ddba8f Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Thu, 11 Jun 2026 19:05:07 -0400 Subject: [PATCH] fix: schema configuration (#29000) --- .../lib/components/SchemaConfiguration.svelte | 23 +++- .../lib/modals/WorkflowAddStepModal.svelte | 3 +- .../lib/modals/WorkflowEditStepModal.svelte | 3 +- web/src/lib/utils/workflow.spec.ts | 112 ++++++++++++++++++ web/src/lib/utils/workflow.ts | 54 +++++++++ 5 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 web/src/lib/utils/workflow.spec.ts diff --git a/web/src/lib/components/SchemaConfiguration.svelte b/web/src/lib/components/SchemaConfiguration.svelte index 2cc0b68089..37e799984f 100644 --- a/web/src/lib/components/SchemaConfiguration.svelte +++ b/web/src/lib/components/SchemaConfiguration.svelte @@ -2,7 +2,19 @@ import SchemaAlbumPicker from '$lib/components/SchemaAlbumPicker.svelte'; import Self from '$lib/components/SchemaConfiguration.svelte'; import type { JSONSchemaProperty, SchemaConfig } from '$lib/types'; - import { CodeBlock, Field, Input, Label, MultiSelect, NumberInput, Select, Switch, Text } from '@immich/ui'; + import { + CodeBlock, + Field, + HelperText, + Input, + Label, + MultiSelect, + NumberInput, + Select, + Switch, + Text, + } from '@immich/ui'; + import { t } from 'svelte-i18n'; type Props = { schema: JSONSchemaProperty; @@ -75,12 +87,11 @@ + {$t('unsupported_field_type')} + {:else if schema.type === 'boolean'} getBoolean(schema.default ?? false), setValue} /> diff --git a/web/src/lib/modals/WorkflowAddStepModal.svelte b/web/src/lib/modals/WorkflowAddStepModal.svelte index d347a1feb3..c2a4d658a8 100644 --- a/web/src/lib/modals/WorkflowAddStepModal.svelte +++ b/web/src/lib/modals/WorkflowAddStepModal.svelte @@ -2,6 +2,7 @@ import SchemaConfiguration from '$lib/components/SchemaConfiguration.svelte'; import PluginMethodPicker from '$lib/modals/PluginMethodPicker.svelte'; import { type JSONSchemaProperty, type SchemaConfig } from '$lib/types'; + import { getWorkflowDefaultConfig } from '$lib/utils/workflow'; import { WorkflowTrigger, type PluginMethodResponseDto, type WorkflowStepDto } from '@immich/sdk'; import { FormModal, IconButton, modalManager, Stack, Text, Textarea } from '@immich/ui'; import { mdiPencilOutline } from '@mdi/js'; @@ -31,7 +32,7 @@ } method = selected; - config = selected.schema ? {} : null; + config = selected.schema ? getWorkflowDefaultConfig(selected.schema as JSONSchemaProperty) : null; }; void onPickMethod(); diff --git a/web/src/lib/modals/WorkflowEditStepModal.svelte b/web/src/lib/modals/WorkflowEditStepModal.svelte index 5375edc7d3..a2adbba090 100644 --- a/web/src/lib/modals/WorkflowEditStepModal.svelte +++ b/web/src/lib/modals/WorkflowEditStepModal.svelte @@ -3,6 +3,7 @@ import { pluginManager } from '$lib/managers/plugin-manager.svelte'; import PluginMethodPicker from '$lib/modals/PluginMethodPicker.svelte'; import { type JSONSchemaProperty, type SchemaConfig } from '$lib/types'; + import { getWorkflowDefaultConfig } from '$lib/utils/workflow'; import { WorkflowTrigger, type WorkflowStepDto } from '@immich/sdk'; import { Button, FormModal, modalManager, Stack, Text, Textarea } from '@immich/ui'; import { mdiPencilOutline } from '@mdi/js'; @@ -30,7 +31,7 @@ } method = selected; - config = selected.schema ? {} : null; + config = selected.schema ? getWorkflowDefaultConfig(selected.schema as JSONSchemaProperty) : null; }; diff --git a/web/src/lib/utils/workflow.spec.ts b/web/src/lib/utils/workflow.spec.ts new file mode 100644 index 0000000000..b6492ae32a --- /dev/null +++ b/web/src/lib/utils/workflow.spec.ts @@ -0,0 +1,112 @@ +import { getWorkflowDefaultConfig } from '$lib/utils/workflow'; + +describe(getWorkflowDefaultConfig.name, () => { + describe('required properties', () => { + it('should use a default value', () => { + expect( + getWorkflowDefaultConfig({ + type: 'object', + properties: { + test: { + type: 'boolean', + default: true, + }, + }, + required: ['test'], + }), + ).toEqual({ test: true }); + }); + + it('should default to an empty array', () => { + expect( + getWorkflowDefaultConfig({ + type: 'object', + properties: { + test: { + type: 'string', + array: true, + }, + }, + required: ['test'], + }), + ).toEqual({ test: [] }); + }); + + it('should default to false', () => { + expect( + getWorkflowDefaultConfig({ + type: 'object', + properties: { + test: { + type: 'boolean', + }, + }, + required: ['test'], + }), + ).toEqual({ test: false }); + }); + + it('should default to 0 (integer)', () => { + expect( + getWorkflowDefaultConfig({ + type: 'object', + properties: { + test: { + type: 'integer', + }, + }, + required: ['test'], + }), + ).toEqual({ test: 0 }); + }); + + it('should default to 0 (number)', () => { + expect( + getWorkflowDefaultConfig({ + type: 'object', + properties: { + test: { + type: 'number', + }, + }, + required: ['test'], + }), + ).toEqual({ test: 0 }); + }); + + it('should default to an empty string', () => { + expect( + getWorkflowDefaultConfig({ + type: 'object', + properties: { + test: { + type: 'string', + }, + }, + required: ['test'], + }), + ).toEqual({ test: '' }); + }); + + it('should default recursively', () => { + expect( + getWorkflowDefaultConfig({ + type: 'object', + properties: { + parent: { + type: 'object', + properties: { + test: { + type: 'string', + array: true, + }, + }, + required: ['test'], + }, + }, + required: ['parent'], + }), + ).toEqual({ parent: { test: [] } }); + }); + }); +}); diff --git a/web/src/lib/utils/workflow.ts b/web/src/lib/utils/workflow.ts index cb8d856cb9..61702f4264 100644 --- a/web/src/lib/utils/workflow.ts +++ b/web/src/lib/utils/workflow.ts @@ -1,5 +1,6 @@ import { WorkflowTrigger } from '@immich/sdk'; import type { MessageFormatter } from 'svelte-i18n'; +import type { JSONSchemaProperty } from '$lib/types'; export const getTriggerName = ($t: MessageFormatter, type: WorkflowTrigger) => { switch (type) { @@ -34,3 +35,56 @@ export const getTriggerDescription = ($t: MessageFormatter, type: WorkflowTrigge } } }; + +export const getWorkflowDefaultConfig = (schema: JSONSchemaProperty) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const config: any = {}; + + const requiredProperties = schema.required ?? []; + + for (const [key, property] of Object.entries(schema.properties ?? {})) { + // default values + if (property.default) { + config[key] = property.default; + break; + } + + if (!requiredProperties.includes(key)) { + continue; + } + + if (property.array) { + config[key] = []; + continue; + } + + switch (property.type) { + case 'string': { + config[key] = ''; + break; + } + + case 'integer': + case 'number': { + config[key] = 0; + break; + } + + case 'boolean': { + config[key] = false; + break; + } + + case 'object': { + config[key] = property.properties ? getWorkflowDefaultConfig(property) : {}; + break; + } + + default: { + console.log(`Unknown configuration type: ${property.type}`); + } + } + } + + return config; +};