diff --git a/frontend/src/lib/components/job_args.test.ts b/frontend/src/lib/components/job_args.test.ts index edd5688506..733de6c909 100644 --- a/frontend/src/lib/components/job_args.test.ts +++ b/frontend/src/lib/components/job_args.test.ts @@ -80,13 +80,17 @@ describe('coerceArgsToSchema', () => { }) // Not merely unreadable: `MultiSelect` maps over the value as it renders, so anything - // else throws and takes the whole card down, Cancel with it. - it('empties a non-array in a dyn-multiselect slot', () => { + // else throws and takes the whole card down, Cancel with it. A reference is no + // exception — the widget draws before anything resolves — so this slot is the one + // place the reference rule above does not hold. + it('empties a non-array in a dyn-multiselect slot, reference included', () => { const schema = { properties: { tags: { type: 'object', format: 'dynmultiselect-list' } } } expect(coerceArgsToSchema({ tags: ['a'] }, schema).args).toEqual({ tags: ['a'] }) - const { args, clearedKeys } = coerceArgsToSchema({ tags: { a: 1 } }, schema) - expect(args).toEqual({}) - expect(clearedKeys).toEqual(['tags']) + for (const bad of [{ a: 1 }, '$var:u/admin/watchlist']) { + const { args, clearedKeys } = coerceArgsToSchema({ tags: bad }, schema) + expect(args).toEqual({}) + expect(clearedKeys).toEqual(['tags']) + } }) // Below the top the form has the same limitations as everywhere else in the product, diff --git a/frontend/src/lib/components/job_args.ts b/frontend/src/lib/components/job_args.ts index e1b446ed82..575fa0b7d5 100644 --- a/frontend/src/lib/components/job_args.ts +++ b/frontend/src/lib/components/job_args.ts @@ -50,9 +50,10 @@ export const resetKeysToast = (resetKeys: string[]): string => const SCALAR_TYPES = new Set(['string', 'number', 'integer', 'boolean']) /** - * Declares an array even though its `type` says `object`, and the only slot where a - * mismatch is worse than unreadable: `MultiSelect` maps over the value as it renders, - * so anything else throws and takes the whole form down — Cancel with it. + * Declares an array even though its `type` says `object`, and a mismatch here throws + * rather than merely reading wrong: `MultiSelect` maps over the value as it renders, so + * anything else takes the whole form down — Cancel with it, and a reference included, + * since the widget draws before anything resolves. */ const declaresDynMultiselect = (prop: any) => typeof prop?.format === 'string' && prop.format.startsWith('dynmultiselect-') @@ -125,7 +126,11 @@ export function coerceArgsToSchema( const kept: Record = Object.create(null) for (const [key, value] of Object.entries(args ?? {})) { const prop = Object.hasOwn(properties, key) ? properties[key] : undefined - if (prop === undefined || value == null || isReference(value)) { + if ( + prop === undefined || + value == null || + (isReference(value) && !declaresDynMultiselect(prop)) + ) { kept[key] = value continue }