From 98e5d06b28b4daecde6e2fc400677a8f3176e978 Mon Sep 17 00:00:00 2001 From: Arnei Date: Mon, 18 May 2026 14:15:17 +0200 Subject: [PATCH 1/2] Unify workflow select Creates a shared component for the workflow select dropdown, to reduce code duplication and increase code readability. Also fixes an issue for scheduled events, where the workflow config was not properly changing when changing the workflow. --- .../EventDetailsWorkflowSchedulingTab.tsx | 61 ++++-------------- .../ModalTabsAndPages/NewProcessingPage.tsx | 62 ++++--------------- .../StartTaskWorkflowPage.tsx | 62 +++++-------------- .../partials/wizards/NewEventSummary.tsx | 4 +- .../partials/wizards/RenderWorkflowSelect.tsx | 60 ++++++++++++++++++ src/configs/modalConfig.ts | 4 +- src/slices/eventDetailsSlice.ts | 6 +- src/slices/eventSlice.ts | 4 +- src/utils/validate.ts | 2 +- src/utils/workflowPanelUtils.ts | 16 +++++ 10 files changed, 127 insertions(+), 154 deletions(-) create mode 100644 src/components/events/partials/wizards/RenderWorkflowSelect.tsx diff --git a/src/components/events/partials/ModalTabsAndPages/EventDetailsWorkflowSchedulingTab.tsx b/src/components/events/partials/ModalTabsAndPages/EventDetailsWorkflowSchedulingTab.tsx index 20dad74e06..f281795240 100644 --- a/src/components/events/partials/ModalTabsAndPages/EventDetailsWorkflowSchedulingTab.tsx +++ b/src/components/events/partials/ModalTabsAndPages/EventDetailsWorkflowSchedulingTab.tsx @@ -11,7 +11,6 @@ import Notifications from "../../../shared/Notifications"; import RenderWorkflowConfig from "../wizards/RenderWorkflowConfig"; import { getUserInformation } from "../../../../selectors/userInfoSelectors"; import { hasAccess, parseBooleanInObject } from "../../../../utils/utils"; -import DropDown from "../../../shared/DropDown"; import { useAppDispatch, useAppSelector } from "../../../../store"; import { fetchWorkflows, @@ -19,11 +18,11 @@ import { } from "../../../../slices/eventDetailsSlice"; import { removeNotificationWizardForm } from "../../../../slices/notificationSlice"; import { useTranslation } from "react-i18next"; -import { formatWorkflowsForDropdown } from "../../../../utils/dropDownUtils"; import ModalContent from "../../../shared/modals/ModalContent"; +import RenderWorkflowSelect from "../wizards/RenderWorkflowSelect"; type InitialValues = { - workflowDefinition: string; + workflowId: string; configuration: { [key: string]: any; } | undefined; @@ -73,7 +72,7 @@ const EventDetailsWorkflowSchedulingTab = ({ } return { - workflowDefinition: "workflowId" in workflow && !!workflow.workflowId + workflowId: "workflowId" in workflow && !!workflow.workflowId ? workflow.workflowId : baseWorkflow.workflowId, configuration: initialConfig, @@ -81,7 +80,7 @@ const EventDetailsWorkflowSchedulingTab = ({ }; const handleSubmit = (values: { - workflowDefinition: string, + workflowId: string, configuration: { [key: string]: unknown } | undefined }) => { dispatch(saveWorkflowConfig({ values, eventId })); @@ -119,48 +118,14 @@ const EventDetailsWorkflowSchedulingTab = ({
-
- - workflowDef.id === - formik.values.workflowDefinition, - )?.title ?? "" - } - options={ - !!workflowDefinitions && - workflowDefinitions.length > 0 - ? formatWorkflowsForDropdown(workflowDefinitions) - : [] - } - required={true} - handleChange={element => { - if (element) { - formik.setFieldValue("workflowDefinition", element.value); - } - }} - placeholder={ - !!workflowDefinitions && - workflowDefinitions.length > 0 - ? t( - "EVENTS.EVENTS.NEW.PROCESSING.SELECT_WORKFLOW", - ) - : t( - "EVENTS.EVENTS.NEW.PROCESSING.SELECT_WORKFLOW_EMPTY", - ) - } - disabled={ - !hasCurrentAgentAccess() || - !isRoleWorkflowEdit - } - customCSS={{ width: "100%" }} - /> - {/* pre-select-from="workflowDefinitionIds" */} -
+
{workflow.description}
@@ -195,7 +160,7 @@ const EventDetailsWorkflowSchedulingTab = ({ > diff --git a/src/components/events/partials/ModalTabsAndPages/NewProcessingPage.tsx b/src/components/events/partials/ModalTabsAndPages/NewProcessingPage.tsx index 4cf30ad41b..42bc26d791 100644 --- a/src/components/events/partials/ModalTabsAndPages/NewProcessingPage.tsx +++ b/src/components/events/partials/ModalTabsAndPages/NewProcessingPage.tsx @@ -2,21 +2,20 @@ import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import { getWorkflowDef } from "../../../../selectors/workflowSelectors"; import RenderWorkflowConfig from "../wizards/RenderWorkflowConfig"; -import { setDefaultConfig } from "../../../../utils/workflowPanelUtils"; -import DropDown from "../../../shared/DropDown"; +import { setDefaultValues } from "../../../../utils/workflowPanelUtils"; import { useAppDispatch, useAppSelector } from "../../../../store"; import { fetchWorkflowDef } from "../../../../slices/workflowSlice"; import { FormikProps } from "formik"; -import { formatWorkflowsForDropdown } from "../../../../utils/dropDownUtils"; import WizardNavigationButtons from "../../../shared/wizard/WizardNavigationButtons"; import ModalContentTable from "../../../shared/modals/ModalContentTable"; +import RenderWorkflowSelect from "../wizards/RenderWorkflowSelect"; /** * This component renders the processing page for new events in the new event wizard. */ interface RequiredFormProps { sourceMode: string, - processingWorkflow: string, + workflowId: string, } const NewProcessingPage = ({ @@ -31,7 +30,7 @@ const NewProcessingPage = ({ const { t } = useTranslation(); const dispatch = useAppDispatch(); - const workflowDef = useAppSelector(state => getWorkflowDef(state)); + const workflowDefinitions = useAppSelector(state => getWorkflowDef(state)); useEffect(() => { // Load workflow definitions for selecting @@ -44,11 +43,11 @@ const NewProcessingPage = ({ // Preselect the first item useEffect(() => { - if (workflowDef.length === 1) { - setDefaultValues(workflowDef[0].id); + if (workflowDefinitions.length === 1) { + setDefaultValues(formik, workflowDefinitions, workflowDefinitions[0].id); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [workflowDef]); + }, [workflowDefinitions]); const previous = () => { // if not UPLOAD is chosen as source mode, then back to source page @@ -59,17 +58,6 @@ const NewProcessingPage = ({ } }; - const setDefaultValues = (value: string) => { - const workflowId = value; - // fill values with default configuration of chosen workflow - const defaultConfiguration = setDefaultConfig(workflowDef, workflowId); - - // set default configuration in formik - formik.setFieldValue("configuration", defaultConfiguration); - // set chosen workflow in formik - formik.setFieldValue("processingWorkflow", workflowId); - }; - return ( <> @@ -79,34 +67,10 @@ const NewProcessingPage = ({ {t("EVENTS.EVENTS.NEW.PROCESSING.SELECT_WORKFLOW")}
- {workflowDef.length > 0 ? ( -
- - formik.values.processingWorkflow === workflow.id, - )?.title ?? "" - } - options={formatWorkflowsForDropdown(workflowDef)} - required={true} - handleChange={element => { - if (element) { - setDefaultValues(element.value); - } - }} - placeholder={t( - "EVENTS.EVENTS.NEW.PROCESSING.SELECT_WORKFLOW", - )} - customCSS={{ width: "100%" }} - /> -
- ) : ( - - {t("EVENTS.EVENTS.NEW.PROCESSING.SELECT_WORKFLOW_EMPTY")} - - )} + {/* Configuration panel of selected workflow */}
@@ -114,10 +78,10 @@ const NewProcessingPage = ({ id="new-event-workflow-configuration" className="checkbox-container obj-container" > - {formik.values.processingWorkflow ? ( + {formik.values.workflowId ? ( diff --git a/src/components/events/partials/ModalTabsAndPages/StartTaskWorkflowPage.tsx b/src/components/events/partials/ModalTabsAndPages/StartTaskWorkflowPage.tsx index 1701bcf479..6bc8fd52f3 100644 --- a/src/components/events/partials/ModalTabsAndPages/StartTaskWorkflowPage.tsx +++ b/src/components/events/partials/ModalTabsAndPages/StartTaskWorkflowPage.tsx @@ -2,20 +2,19 @@ import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import RenderWorkflowConfig from "../wizards/RenderWorkflowConfig"; import { getWorkflowDef } from "../../../../selectors/workflowSelectors"; -import { setDefaultConfig } from "../../../../utils/workflowPanelUtils"; -import DropDown from "../../../shared/DropDown"; +import { setDefaultValues } from "../../../../utils/workflowPanelUtils"; import { useAppDispatch, useAppSelector } from "../../../../store"; import { fetchWorkflowDef } from "../../../../slices/workflowSlice"; import { FormikProps } from "formik"; -import { formatWorkflowsForDropdown } from "../../../../utils/dropDownUtils"; import WizardNavigationButtons from "../../../shared/wizard/WizardNavigationButtons"; import ModalContentTable from "../../../shared/modals/ModalContentTable"; +import RenderWorkflowSelect from "../wizards/RenderWorkflowSelect"; /** * This component renders the workflow selection for start task bulk action */ interface RequiredFormProps { - workflow: string, + workflowId: string, } const StartTaskWorkflowPage = ({ @@ -32,7 +31,7 @@ const StartTaskWorkflowPage = ({ const { t } = useTranslation(); const dispatch = useAppDispatch(); - const workflowDef = useAppSelector(state => getWorkflowDef(state)); + const workflowDefinitions = useAppSelector(state => getWorkflowDef(state)); useEffect(() => { // Load workflow definitions for selecting @@ -42,22 +41,11 @@ const StartTaskWorkflowPage = ({ // Preselect the first item useEffect(() => { - if (workflowDef.length === 1) { - setDefaultValues(workflowDef[0].id); + if (workflowDefinitions.length === 1) { + setDefaultValues(formik, workflowDefinitions, workflowDefinitions[0].id); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [workflowDef]); - - const setDefaultValues = (value: string) => { - const workflowId = value; - // fill values with default configuration of chosen workflow - const defaultConfiguration = setDefaultConfig(workflowDef, workflowId); - - // set default configuration in formik - formik.setFieldValue("configuration", defaultConfiguration); - // set chosen workflow in formik - formik.setFieldValue("workflow", workflowId); - }; + }, [workflowDefinitions]); return ( <> @@ -66,32 +54,12 @@ const StartTaskWorkflowPage = ({
{t("BULK_ACTIONS.SCHEDULE_TASK.TASKS.SELECT")}
- {workflowDef.length > 0 && ( -
- - workflowDef.id === formik.values.workflow, - )?.title ?? "" - } - options={formatWorkflowsForDropdown(workflowDef)} - required={true} - handleChange={element => { - if (element) { - setDefaultValues(element.value); - } - }} - placeholder={t( - "EVENTS.EVENTS.DETAILS.PUBLICATIONS.SELECT_WORKFLOW", - )} - tabIndex={99} - customCSS={{ width: "100%" }} - /> -
- )} - {formik.values.workflow && ( + + + {formik.values.workflowId && ( <> {/* Configuration panel of selected workflow */}
({ > @@ -122,7 +90,7 @@ const StartTaskWorkflowPage = ({ setPageCompleted([]); } }} - customValidation={!(formik.values.workflow && formik.isValid)} + customValidation={!(formik.values.workflowId && formik.isValid)} /> ); diff --git a/src/components/events/partials/wizards/NewEventSummary.tsx b/src/components/events/partials/wizards/NewEventSummary.tsx index 43baec1036..449b1ca3bd 100644 --- a/src/components/events/partials/wizards/NewEventSummary.tsx +++ b/src/components/events/partials/wizards/NewEventSummary.tsx @@ -22,7 +22,7 @@ import { UploadAssetsTrack } from "../../../../slices/eventSlice"; * This component renders the summary page for new events in the new event wizard. */ interface RequiredFormProps { - processingWorkflow: string + workflowId: string sourceMode: string startDate?: string location: string @@ -92,7 +92,7 @@ const NewEventSummary = ({ // Get additional information about chosen workflow definition const workflowDefinition = workflowDef.find( - workflow => workflow.id === formik.values.processingWorkflow, + workflow => workflow.id === formik.values.workflowId, ); const endsOnSameDay = formik.values.scheduleStartDate === formik.values.scheduleEndDate; diff --git a/src/components/events/partials/wizards/RenderWorkflowSelect.tsx b/src/components/events/partials/wizards/RenderWorkflowSelect.tsx new file mode 100644 index 0000000000..8f63df733c --- /dev/null +++ b/src/components/events/partials/wizards/RenderWorkflowSelect.tsx @@ -0,0 +1,60 @@ +import { FormikProps } from "formik"; +import { Workflow } from "../../../../slices/workflowSlice"; +import { useTranslation } from "react-i18next"; +import DropDown from "../../../shared/DropDown"; +import { setDefaultValues } from "../../../../utils/workflowPanelUtils"; +import { formatWorkflowsForDropdown } from "../../../../utils/dropDownUtils"; + +/** + * This component renders the dropdown for the workflow select tab + */ +interface RequiredFormProps { + workflowId: string +} +const RenderWorkflowSelect = ({ + formik, + workflowDefinitions, + disabled = false, +}: { + formik: FormikProps + workflowDefinitions: Workflow[] + disabled?: boolean +}) => { + const { t } = useTranslation(); + + return ( + <> + {workflowDefinitions.length > 0 ? ( +
+ + formik.values.workflowId === workflow.id, + )?.title ?? "" + } + options={formatWorkflowsForDropdown(workflowDefinitions)} + required={true} + handleChange={element => { + if (element) { + setDefaultValues(formik, workflowDefinitions, element.value); + } + }} + placeholder={t( + "EVENTS.EVENTS.NEW.PROCESSING.SELECT_WORKFLOW", + )} + disabled={disabled} + customCSS={{ width: "100%" }} + /> +
+ ) : ( + + {t("EVENTS.EVENTS.NEW.PROCESSING.SELECT_WORKFLOW_EMPTY")} + + )} + + ); +}; + +export default RenderWorkflowSelect; diff --git a/src/configs/modalConfig.ts b/src/configs/modalConfig.ts index b1c9cf6ed9..768e574392 100644 --- a/src/configs/modalConfig.ts +++ b/src/configs/modalConfig.ts @@ -29,7 +29,7 @@ export const initialFormValuesNewEvents: { scheduleEndMinute: string, repeatOn: ("MO" | "TU" | "WE" | "TH" | "FR" | "SA" | "SU")[], location: string, - processingWorkflow: string, + workflowId: string, configuration: { [key: string]: string }, aclTemplate: string, policies: TransformedAcl[], @@ -48,7 +48,7 @@ export const initialFormValuesNewEvents: { repeatOn: [], location: "", // deviceInputs: [], - processingWorkflow: "", + workflowId: "", configuration: {}, aclTemplate: "", policies: [], diff --git a/src/slices/eventDetailsSlice.ts b/src/slices/eventDetailsSlice.ts index 879ce760d2..8ae42dffde 100644 --- a/src/slices/eventDetailsSlice.ts +++ b/src/slices/eventDetailsSlice.ts @@ -1816,21 +1816,21 @@ export const deleteCommentReply = createAppAsyncThunk("eventDetails/deleteCommen export const saveWorkflowConfig = (params: { values: { - workflowDefinition: string, + workflowId: string, configuration: { [key: string]: unknown } | undefined }, eventId: Event["id"] }): AppThunk => dispatch => { // export const saveWorkflowConfig = createAppAsyncThunk("eventDetails/saveWorkflowConfig", async (params: { // values: { -// workflowDefinition: string, +// workflowId: string, // configuration: { [key: string]: unknown } | undefined // }, // eventId: Event["id"] // }, { dispatch }) => { const { values, eventId } = params; const jsonData = { - id: values.workflowDefinition, + id: values.workflowId, configuration: values.configuration, }; diff --git a/src/slices/eventSlice.ts b/src/slices/eventSlice.ts index d8437e2aeb..998fc9a13a 100644 --- a/src/slices/eventSlice.ts +++ b/src/slices/eventSlice.ts @@ -432,7 +432,7 @@ export const postNewEvent = (params: { policies: TransformedAcl[], configuration: { [key: string]: unknown }, inputs?: string[], - processingWorkflow: string, + workflowId: string, repeatOn: string[], scheduleDurationHours: string, scheduleDurationMinutes: string, @@ -620,7 +620,7 @@ export const postNewEvent = (params: { JSON.stringify({ metadata: metadata, processing: { - workflow: values.processingWorkflow, + workflow: values.workflowId, configuration: configurationPrepared, }, access: access, diff --git a/src/utils/validate.ts b/src/utils/validate.ts index d7b7e7662d..9b0f437ccb 100644 --- a/src/utils/validate.ts +++ b/src/utils/validate.ts @@ -145,7 +145,7 @@ export const NewEventSchema = { }), "upload-asset": Yup.object().shape({}), "processing": Yup.object().shape({ - processingWorkflow: Yup.string().required("Required"), + workflowId: Yup.string().required("Required"), }), "access": Yup.object().shape({}), "summary": Yup.object().shape({}), diff --git a/src/utils/workflowPanelUtils.ts b/src/utils/workflowPanelUtils.ts index 5d6dbf8823..d29a1f1b9e 100644 --- a/src/utils/workflowPanelUtils.ts +++ b/src/utils/workflowPanelUtils.ts @@ -1,5 +1,21 @@ +import { FormikProps } from "formik"; import { Workflow, FieldSetField } from "../slices/workflowSlice"; +// Set workflow id in formik when changing workflow, and initialize configuration +export const setDefaultValues = ( + formik: FormikProps, + workflowDefinitions: Workflow[], + workflowId: string, +) => { + // fill values with default configuration of chosen workflow + const defaultConfiguration = setDefaultConfig(workflowDefinitions, workflowId); + + // set default configuration in formik + formik.setFieldValue("configuration", defaultConfiguration); + // set chosen workflow in formik + formik.setFieldValue("workflowId", workflowId); +}; + // fill values with default configuration of chosen workflow export const setDefaultConfig = (workflowDefinitions: Workflow[], workflowId: string) => { let defaultConfiguration: { [key: string]: unknown } = {}; From ae0b39721b1feddee5738cef7bcdffc3dd7f8abb Mon Sep 17 00:00:00 2001 From: Arnei Date: Mon, 18 May 2026 14:56:48 +0200 Subject: [PATCH 2/2] Fix workflow displayOrder not being respected Workflows can have a "display order" which defines in which order they should be displayed in a dropdown. Our workflow select dropdowns were not respecting this order, and this patch fixes that. --- src/utils/dropDownUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/dropDownUtils.ts b/src/utils/dropDownUtils.ts index bad70de163..b17999cf84 100644 --- a/src/utils/dropDownUtils.ts +++ b/src/utils/dropDownUtils.ts @@ -13,7 +13,7 @@ export const formatCaptureAgentForDropdown = (captureAgents: Recording[]) => { }; export const formatWorkflowsForDropdown = (workflows: Workflow[]) => { - return workflows.map(workflow => ({ label: workflow.title, value: workflow.id })); + return workflows.map(workflow => ({ label: workflow.title, value: workflow.id, order: workflow.displayOrder })); }; export const formatAclTemplatesForDropdown = (templates: { id: string, value: string }[]) => {