diff --git a/pipeline/src/client/CreatePipelineTrigger/CreatePipelineTrigger.tsx b/pipeline/src/client/CreatePipelineTrigger/CreatePipelineTrigger.tsx index 818caa8cb85..473c9d80f19 100644 --- a/pipeline/src/client/CreatePipelineTrigger/CreatePipelineTrigger.tsx +++ b/pipeline/src/client/CreatePipelineTrigger/CreatePipelineTrigger.tsx @@ -7,7 +7,6 @@ import React, { ChangeEvent, Dispatch, FC, Reducer, useCallback, useEffect, useR import { ActionURL, Ajax, Utils } from '@labkey/api'; import { Alert, AutoForm, cancelEvent, FormSchema, naturalSort, redirect } from '@labkey/components'; -// eslint-disable-next-line import/no-unassigned-import import './CreatePipelineTrigger.scss'; const HELP_TEXT = 'Fields marked with an asterisk * are required. '; @@ -36,7 +35,7 @@ interface TriggerConfiguration { rowId: number; } -type CustomConfiguration = Record; +type CustomConfiguration = Record; interface CustomParameterModel { id: number; // used internally as a key for react @@ -74,13 +73,13 @@ interface FormState { // The omitted fields are computed by initializeFormState type InitialState = Omit< FormState, - | 'customParameters' | 'customConfigValid' + | 'customParameters' | 'detailsValid' | 'isDirty' | 'saveError' - | 'saving' | 'saveSuccessful' + | 'saving' | 'triggerConfigValid' >; @@ -98,7 +97,7 @@ enum ActionType { // eslint-disable-next-line @typescript-eslint/no-explicit-any interface FieldAction { field: string; - type: ActionType.UPDATE_DETAILS | ActionType.UPDATE_TRIGGER_CONFIG | ActionType.UPDATE_CUSTOM_CONFIG; + type: ActionType.UPDATE_CUSTOM_CONFIG | ActionType.UPDATE_DETAILS | ActionType.UPDATE_TRIGGER_CONFIG; value: T; } @@ -131,12 +130,12 @@ interface SetSavingAction { } type FormStateAction = - | FieldAction - | ViewAction | AddCustomParamAction + | FieldAction | RemoveCustomParamAction + | SetSavingAction | UpdateCustomParamAction - | SetSavingAction; + | ViewAction; const validateValues = (formSchema: FormSchema, values: Record): boolean => { // Not every trigger type has a custom form schema, so this can be null/undefined. @@ -162,6 +161,29 @@ const validateValues = (formSchema: FormSchema, values: Record): bo const formStateReducer = (state: FormState, action: FormStateAction): FormState => { switch (action.type) { + case ActionType.ADD_CUSTOM_PARAM: { + const { customParameters } = state; + const integerIds = Object.keys(customParameters).map(k => parseInt(k, 10)); + const id = integerIds.length > 0 ? Math.max(...integerIds) + 1 : 0; + return { + ...state, + customParameters: { + ...customParameters, + [id]: { key: '', value: '', id }, + }, + isDirty: true, + }; + } + case ActionType.REMOVE_CUSTOM_PARAM: { + const { id } = action; + const customParameters = { ...state.customParameters }; + delete customParameters[id]; + return { ...state, customParameters, isDirty: true }; + } + case ActionType.SET_SAVING: { + const { saving, saveError, saveSuccessful } = action; + return { ...state, saving, saveError, saveSuccessful }; + } case ActionType.SET_VIEW: { // Don't navigate to the Configuration form if the user hasn't filled out the required fields in Details. if (!state.detailsValid) { @@ -170,6 +192,36 @@ const formStateReducer = (state: FormState, action: FormStateAction): FormState return { ...state, view: action.view }; } + case ActionType.UPDATE_CUSTOM_CONFIG: { + const customConfig = { ...state.customConfig, [action.field]: action.value }; + const formSchema = state.customFieldFormSchemas[state.details.pipelineId]; + return { + ...state, + isDirty: true, + customConfig, + customConfigValid: validateValues(formSchema, customConfig), + }; + } + case ActionType.UPDATE_CUSTOM_PARAM: { + const { customParameters } = state; + const { id, key, value } = action; + const keyIsSame = customParameters[id].key === key; + const valueIsSame = customParameters[id].value === value; + + if (keyIsSame && valueIsSame) { + // If nothing changed, no-op. + return state; + } + + return { + ...state, + customParameters: { + ...customParameters, + [id]: { key, value, id }, + }, + isDirty: true, + }; + } case ActionType.UPDATE_DETAILS: { const { customConfig, customFieldFormSchemas, details, taskFormSchemas, triggerConfig } = state; const { field, value } = action; @@ -180,7 +232,6 @@ const formStateReducer = (state: FormState, action: FormStateAction): FormState if (field === 'pipelineId') { // Set default values on the customConfig based on the appropriate FormSchema. resetCustomConfig = {}; - // eslint-disable-next-line no-unused-expressions customFieldFormSchemas[value]?.fields.forEach(f => { if (f.defaultValue !== null) { resetCustomConfig[f.name] = f.defaultValue; @@ -193,6 +244,10 @@ const formStateReducer = (state: FormState, action: FormStateAction): FormState if (taskFormSchema) { Object.keys(resetTriggerConfig).forEach(key => { + // parameterFunction isn't task-specific, and users without permission must save it back + // unchanged + if (key === 'parameterFunction') return; + if (taskFormSchema.fields.find(f => f.name === key) === undefined) { delete resetTriggerConfig[key]; } @@ -221,59 +276,6 @@ const formStateReducer = (state: FormState, action: FormStateAction): FormState triggerConfigValid: validateValues(formSchema, triggerConfig), }; } - case ActionType.UPDATE_CUSTOM_CONFIG: { - const customConfig = { ...state.customConfig, [action.field]: action.value }; - const formSchema = state.customFieldFormSchemas[state.details.pipelineId]; - return { - ...state, - isDirty: true, - customConfig, - customConfigValid: validateValues(formSchema, customConfig), - }; - } - case ActionType.ADD_CUSTOM_PARAM: { - const { customParameters } = state; - const integerIds = Object.keys(customParameters).map(k => parseInt(k, 10)); - const id = integerIds.length > 0 ? Math.max(...integerIds) + 1 : 0; - return { - ...state, - customParameters: { - ...customParameters, - [id]: { key: '', value: '', id }, - }, - isDirty: true, - }; - } - case ActionType.REMOVE_CUSTOM_PARAM: { - const { id } = action; - const customParameters = { ...state.customParameters }; - delete customParameters[id]; - return { ...state, customParameters, isDirty: true }; - } - case ActionType.UPDATE_CUSTOM_PARAM: { - const { customParameters } = state; - const { id, key, value } = action; - const keyIsSame = customParameters[id].key === key; - const valueIsSame = customParameters[id].value === value; - - if (keyIsSame && valueIsSame) { - // If nothing changed, no-op. - return state; - } - - return { - ...state, - customParameters: { - ...customParameters, - [id]: { key, value, id }, - }, - isDirty: true, - }; - } - case ActionType.SET_SAVING: { - const { saving, saveError, saveSuccessful } = action; - return { ...state, saving, saveError, saveSuccessful }; - } default: { // Throw error for unhandled actions. // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -425,10 +427,10 @@ const DetailsForm: FC = ({ dispatch, formState, onNext, return
- - + cancel
@@ -476,7 +478,7 @@ const CustomParameter: FC = ({ customParameter, remove, up
-
@@ -507,13 +509,14 @@ const CustomParameters: FC = ({ customParameters, dispatc {Object.keys(customParameters) .sort(naturalSort) .map(id => ( - + ))} ); }; interface ConfigurationFormProps { + canEditParameterFunction: boolean; dispatch: Dispatch; formState: FormState; onBack: () => void; @@ -522,7 +525,7 @@ interface ConfigurationFormProps { } const ConfigurationForm: FC = props => { - const { formState, dispatch, onBack, onSubmit, returnUrl } = props; + const { canEditParameterFunction, formState, dispatch, onBack, onSubmit, returnUrl } = props; const { customConfig, customConfigValid, @@ -569,19 +572,21 @@ const ConfigurationForm: FC = props => { {showAdvanced && (
-
- -
-