1
- import type { WorkspaceBuildParameter } from "api/typesGenerated" ;
2
1
import type {
3
- Parameter ,
4
- ParameterOption ,
5
- ParameterValidation ,
6
- } from "api/typesParameter " ;
2
+ PreviewParameter ,
3
+ PreviewParameterOption ,
4
+ WorkspaceBuildParameter ,
5
+ } from "api/typesGenerated " ;
7
6
import { Badge } from "components/Badge/Badge" ;
8
7
import { Checkbox } from "components/Checkbox/Checkbox" ;
9
8
import { ExternalImage } from "components/ExternalImage/ExternalImage" ;
@@ -31,10 +30,11 @@ import {
31
30
} from "components/Tooltip/Tooltip" ;
32
31
import { Info , Settings , TriangleAlert } from "lucide-react" ;
33
32
import { type FC , useId } from "react" ;
33
+ import type { AutofillBuildParameter } from "utils/richParameters" ;
34
34
import * as Yup from "yup" ;
35
35
36
36
export interface DynamicParameterProps {
37
- parameter : Parameter ;
37
+ parameter : PreviewParameter ;
38
38
onChange : ( value : string ) => void ;
39
39
disabled ?: boolean ;
40
40
isPreset ?: boolean ;
@@ -68,7 +68,7 @@ export const DynamicParameter: FC<DynamicParameterProps> = ({
68
68
} ;
69
69
70
70
interface ParameterLabelProps {
71
- parameter : Parameter ;
71
+ parameter : PreviewParameter ;
72
72
isPreset ?: boolean ;
73
73
}
74
74
@@ -144,7 +144,7 @@ const ParameterLabel: FC<ParameterLabelProps> = ({ parameter, isPreset }) => {
144
144
} ;
145
145
146
146
interface ParameterFieldProps {
147
- parameter : Parameter ;
147
+ parameter : PreviewParameter ;
148
148
onChange : ( value : string ) => void ;
149
149
disabled ?: boolean ;
150
150
id : string ;
@@ -173,26 +173,35 @@ const ParameterField: FC<ParameterFieldProps> = ({
173
173
< SelectValue placeholder = "Select option" />
174
174
</ SelectTrigger >
175
175
< SelectContent >
176
- { parameter . options . map ( ( option ) => (
177
- < SelectItem key = { option . value . value } value = { option . value . value } >
178
- < OptionDisplay option = { option } />
179
- </ SelectItem >
180
- ) ) }
176
+ { parameter . options
177
+ . filter (
178
+ ( option ) : option is NonNullable < typeof option > =>
179
+ option !== null ,
180
+ )
181
+ . map ( ( option ) => (
182
+ < SelectItem key = { option . value . value } value = { option . value . value } >
183
+ < OptionDisplay option = { option } />
184
+ </ SelectItem >
185
+ ) ) }
181
186
</ SelectContent >
182
187
</ Select >
183
188
) ;
184
189
185
190
case "multi-select" : {
186
191
// Map parameter options to MultiSelectCombobox options format
187
- const comboboxOptions : Option [ ] = parameter . options . map ( ( opt ) => ( {
188
- value : opt . value . value ,
189
- label : opt . name ,
190
- disable : false ,
191
- } ) ) ;
192
+ const comboboxOptions : Option [ ] = parameter . options
193
+ . filter ( ( opt ) : opt is NonNullable < typeof opt > => opt !== null )
194
+ . map ( ( opt ) => ( {
195
+ value : opt . value . value ,
196
+ label : opt . name ,
197
+ disable : false ,
198
+ } ) ) ;
192
199
193
200
const defaultOptions : Option [ ] = JSON . parse ( defaultValue ) . map (
194
201
( val : string ) => {
195
- const option = parameter . options . find ( ( o ) => o . value . value === val ) ;
202
+ const option = parameter . options
203
+ . filter ( ( o ) : o is NonNullable < typeof o > => o !== null )
204
+ . find ( ( o ) => o . value . value === val ) ;
196
205
return {
197
206
value : val ,
198
207
label : option ?. name || val ,
@@ -242,20 +251,24 @@ const ParameterField: FC<ParameterFieldProps> = ({
242
251
disabled = { disabled }
243
252
defaultValue = { defaultValue }
244
253
>
245
- { parameter . options . map ( ( option ) => (
246
- < div
247
- key = { option . value . value }
248
- className = "flex items-center space-x-2"
249
- >
250
- < RadioGroupItem
251
- id = { option . value . value }
252
- value = { option . value . value }
253
- />
254
- < Label htmlFor = { option . value . value } className = "cursor-pointer" >
255
- < OptionDisplay option = { option } />
256
- </ Label >
257
- </ div >
258
- ) ) }
254
+ { parameter . options
255
+ . filter (
256
+ ( option ) : option is NonNullable < typeof option > => option !== null ,
257
+ )
258
+ . map ( ( option ) => (
259
+ < div
260
+ key = { option . value . value }
261
+ className = "flex items-center space-x-2"
262
+ >
263
+ < RadioGroupItem
264
+ id = { option . value . value }
265
+ value = { option . value . value }
266
+ />
267
+ < Label htmlFor = { option . value . value } className = "cursor-pointer" >
268
+ < OptionDisplay option = { option } />
269
+ </ Label >
270
+ </ div >
271
+ ) ) }
259
272
</ RadioGroup >
260
273
) ;
261
274
@@ -281,7 +294,10 @@ const ParameterField: FC<ParameterFieldProps> = ({
281
294
const inputProps : Record < string , unknown > = { } ;
282
295
283
296
if ( parameter . type === "number" ) {
284
- const validations = parameter . validations [ 0 ] || { } ;
297
+ const validations =
298
+ parameter . validations . filter (
299
+ ( v ) : v is NonNullable < typeof v > => v !== null ,
300
+ ) [ 0 ] || { } ;
285
301
const { validation_min, validation_max } = validations ;
286
302
287
303
if ( validation_min !== null ) {
@@ -310,7 +326,7 @@ const ParameterField: FC<ParameterFieldProps> = ({
310
326
} ;
311
327
312
328
interface OptionDisplayProps {
313
- option : ParameterOption ;
329
+ option : PreviewParameterOption ;
314
330
}
315
331
316
332
const OptionDisplay : FC < OptionDisplayProps > = ( { option } ) => {
@@ -341,33 +357,84 @@ const OptionDisplay: FC<OptionDisplayProps> = ({ option }) => {
341
357
} ;
342
358
343
359
interface ParameterDiagnosticsProps {
344
- diagnostics : Parameter [ "diagnostics" ] ;
360
+ diagnostics : PreviewParameter [ "diagnostics" ] ;
345
361
}
346
362
347
363
const ParameterDiagnostics : FC < ParameterDiagnosticsProps > = ( {
348
364
diagnostics,
349
365
} ) => {
350
366
return (
351
367
< div className = "flex flex-col gap-2" >
352
- { diagnostics . map ( ( diagnostic , index ) => (
353
- < div
354
- key = { `diagnostic-${ diagnostic . summary } -${ index } ` }
355
- className = { `text-xs px-1 ${
356
- diagnostic . severity === "error"
357
- ? "text-content-destructive"
358
- : "text-content-warning"
359
- } `}
360
- >
361
- < div className = "font-medium" > { diagnostic . summary } </ div >
362
- { diagnostic . detail && < div > { diagnostic . detail } </ div > }
363
- </ div >
364
- ) ) }
368
+ { diagnostics
369
+ . filter (
370
+ ( diagnostic ) : diagnostic is NonNullable < typeof diagnostic > =>
371
+ diagnostic !== null ,
372
+ )
373
+ . map ( ( diagnostic , index ) => (
374
+ < div
375
+ key = { `diagnostic-${ diagnostic . summary } -${ index } ` }
376
+ className = { `text-xs px-1 ${
377
+ diagnostic . severity === "error"
378
+ ? "text-content-destructive"
379
+ : "text-content-warning"
380
+ } `}
381
+ >
382
+ < div className = "font-medium" > { diagnostic . summary } </ div >
383
+ { diagnostic . detail && < div > { diagnostic . detail } </ div > }
384
+ </ div >
385
+ ) ) }
365
386
</ div >
366
387
) ;
367
388
} ;
368
389
390
+ export const getInitialParameterValues = (
391
+ params : PreviewParameter [ ] ,
392
+ autofillParams ?: AutofillBuildParameter [ ] ,
393
+ ) : WorkspaceBuildParameter [ ] => {
394
+ return params . map ( ( parameter ) => {
395
+ // Short-circuit for ephemeral parameters, which are always reset to
396
+ // the template-defined default.
397
+ if ( parameter . ephemeral ) {
398
+ return {
399
+ name : parameter . name ,
400
+ value : parameter . default_value . valid
401
+ ? parameter . default_value . value
402
+ : "" ,
403
+ } ;
404
+ }
405
+
406
+ const autofillParam = autofillParams ?. find (
407
+ ( { name } ) => name === parameter . name ,
408
+ ) ;
409
+
410
+ return {
411
+ name : parameter . name ,
412
+ value :
413
+ autofillParam &&
414
+ isValidValue ( parameter , autofillParam ) &&
415
+ autofillParam . value
416
+ ? autofillParam . value
417
+ : "" ,
418
+ } ;
419
+ } ) ;
420
+ } ;
421
+
422
+ const isValidValue = (
423
+ previewParam : PreviewParameter ,
424
+ buildParam : WorkspaceBuildParameter ,
425
+ ) => {
426
+ if ( previewParam . options . length > 0 ) {
427
+ const validValues = previewParam . options
428
+ . filter ( ( option ) : option is NonNullable < typeof option > => option !== null )
429
+ . map ( ( option ) => option . value . value ) ;
430
+ return validValues . includes ( buildParam . value ) ;
431
+ }
432
+
433
+ return true ;
434
+ } ;
435
+
369
436
export const useValidationSchemaForDynamicParameters = (
370
- parameters ?: Parameter [ ] ,
437
+ parameters ?: PreviewParameter [ ] ,
371
438
lastBuildParameters ?: WorkspaceBuildParameter [ ] ,
372
439
) : Yup . AnySchema => {
373
440
if ( ! parameters ) {
@@ -387,15 +454,16 @@ export const useValidationSchemaForDynamicParameters = (
387
454
if ( parameter ) {
388
455
switch ( parameter . type ) {
389
456
case "number" : {
390
- const minValidation = parameter . validations . find (
391
- ( v ) => v . validation_min !== null ,
392
- ) ;
393
- const maxValidation = parameter . validations . find (
394
- ( v ) => v . validation_max !== null ,
395
- ) ;
457
+ const minValidation = parameter . validations
458
+ . filter ( ( v ) : v is NonNullable < typeof v > => v !== null )
459
+ . find ( ( v ) => v . validation_min !== null ) ;
460
+ const maxValidation = parameter . validations
461
+ . filter ( ( v ) : v is NonNullable < typeof v > => v !== null )
462
+ . find ( ( v ) => v . validation_max !== null ) ;
396
463
397
464
if (
398
- minValidation ?. validation_min &&
465
+ minValidation &&
466
+ minValidation . validation_min !== null &&
399
467
! maxValidation &&
400
468
Number ( val ) < minValidation . validation_min
401
469
) {
@@ -409,7 +477,8 @@ export const useValidationSchemaForDynamicParameters = (
409
477
410
478
if (
411
479
! minValidation &&
412
- maxValidation ?. validation_max &&
480
+ maxValidation &&
481
+ maxValidation . validation_max !== null &&
413
482
Number ( val ) > maxValidation . validation_max
414
483
) {
415
484
return ctx . createError ( {
@@ -421,8 +490,10 @@ export const useValidationSchemaForDynamicParameters = (
421
490
}
422
491
423
492
if (
424
- minValidation ?. validation_min &&
425
- maxValidation ?. validation_max &&
493
+ minValidation &&
494
+ minValidation . validation_min !== null &&
495
+ maxValidation &&
496
+ maxValidation . validation_max !== null &&
426
497
( Number ( val ) < minValidation . validation_min ||
427
498
Number ( val ) > maxValidation . validation_max )
428
499
) {
@@ -434,18 +505,20 @@ export const useValidationSchemaForDynamicParameters = (
434
505
} ) ;
435
506
}
436
507
437
- const monotonicValidation = parameter . validations . find (
438
- ( v ) => v . validation_monotonic !== null ,
439
- ) ;
440
- if (
441
- monotonicValidation ?. validation_monotonic &&
442
- lastBuildParameters
443
- ) {
508
+ const monotonic = parameter . validations
509
+ . filter ( ( v ) : v is NonNullable < typeof v > => v !== null )
510
+ . find (
511
+ ( v ) =>
512
+ v . validation_monotonic !== null &&
513
+ v . validation_monotonic !== "" ,
514
+ ) ;
515
+
516
+ if ( monotonic && lastBuildParameters ) {
444
517
const lastBuildParameter = lastBuildParameters . find (
445
518
( last : { name : string } ) => last . name === name ,
446
519
) ;
447
520
if ( lastBuildParameter ) {
448
- switch ( monotonicValidation . validation_monotonic ) {
521
+ switch ( monotonic . validation_monotonic ) {
449
522
case "increasing" :
450
523
if ( Number ( lastBuildParameter . value ) > Number ( val ) ) {
451
524
return ctx . createError ( {
@@ -468,17 +541,18 @@ export const useValidationSchemaForDynamicParameters = (
468
541
break ;
469
542
}
470
543
case "string" : {
471
- const regexValidation = parameter . validations . find (
472
- ( v ) => v . validation_regex !== null ,
473
- ) ;
474
- if ( ! regexValidation ?. validation_regex ) {
544
+ const regex = parameter . validations
545
+ . filter ( ( v ) : v is NonNullable < typeof v > => v !== null )
546
+ . find (
547
+ ( v ) =>
548
+ v . validation_regex !== null &&
549
+ v . validation_regex !== "" ,
550
+ ) ;
551
+ if ( ! regex || ! regex . validation_regex ) {
475
552
return true ;
476
553
}
477
554
478
- if (
479
- val &&
480
- ! new RegExp ( regexValidation . validation_regex ) . test ( val )
481
- ) {
555
+ if ( val && ! new RegExp ( regex . validation_regex ) . test ( val ) ) {
482
556
return ctx . createError ( {
483
557
path : ctx . path ,
484
558
message : parameterError ( parameter , val ) ,
@@ -496,18 +570,18 @@ export const useValidationSchemaForDynamicParameters = (
496
570
} ;
497
571
498
572
const parameterError = (
499
- parameter : Parameter ,
573
+ parameter : PreviewParameter ,
500
574
value ?: string ,
501
575
) : string | undefined => {
502
- const validation_error = parameter . validations . find (
503
- ( v ) => v . validation_error !== null ,
504
- ) ;
505
- const minValidation = parameter . validations . find (
506
- ( v ) => v . validation_min !== null ,
507
- ) ;
508
- const maxValidation = parameter . validations . find (
509
- ( v ) => v . validation_max !== null ,
510
- ) ;
576
+ const validation_error = parameter . validations
577
+ . filter ( ( v ) : v is NonNullable < typeof v > => v !== null )
578
+ . find ( ( v ) => v . validation_error !== null ) ;
579
+ const minValidation = parameter . validations
580
+ . filter ( ( v ) : v is NonNullable < typeof v > => v !== null )
581
+ . find ( ( v ) => v . validation_min !== null ) ;
582
+ const maxValidation = parameter . validations
583
+ . filter ( ( v ) : v is NonNullable < typeof v > => v !== null )
584
+ . find ( ( v ) => v . validation_max !== null ) ;
511
585
512
586
if ( ! validation_error || ! value ) {
513
587
return ;
0 commit comments