diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index f5e0420646a..9d59b317a67 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -444,6 +444,8 @@ export type ClientAiModelConfig = { export type ComputeStepOutputSchemaInput = { /** Step JSON format */ step: Scalars['JSON']; + /** Workflow version ID */ + workflowVersionId: Scalars['UUID']; }; export enum ConfigSource { diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index a23ae1ff0e6..6265c1a1003 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -444,6 +444,8 @@ export type ClientAiModelConfig = { export type ComputeStepOutputSchemaInput = { /** Step JSON format */ step: Scalars['JSON']; + /** Workflow version ID */ + workflowVersionId: Scalars['UUID']; }; export enum ConfigSource { diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx index b5eda6aef9c..84fa6d96c5d 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx @@ -12,10 +12,12 @@ import { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/Da import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/OtherTriggerTypes'; import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger'; import { getTriggerDefaultDefinition } from '@/workflow/workflow-trigger/utils/getTriggerDefaultDefinition'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useTheme } from '@emotion/react'; import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; import { useIcons } from 'twenty-ui/display'; import { MenuItem } from 'twenty-ui/navigation'; +import { FeatureFlagKey } from '~/generated/graphql'; export const CommandMenuWorkflowSelectTriggerTypeContent = ({ workflow, @@ -32,6 +34,9 @@ export const CommandMenuWorkflowSelectTriggerTypeContent = ({ workflowSelectedNodeComponentState, ); const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu(); + const isIteratorEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_WORKFLOW_ITERATOR_ENABLED, + ); const handleTriggerTypeClick = ({ type, @@ -48,6 +53,7 @@ export const CommandMenuWorkflowSelectTriggerTypeContent = ({ defaultLabel, type, activeNonSystemObjectMetadataItems, + isIteratorEnabled, }), ); diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/VariableChip.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/VariableChip.tsx index b4ecb8a0ece..4a43e5ce582 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/VariableChip.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/VariableChip.tsx @@ -1,9 +1,9 @@ import { useSearchVariable } from '@/workflow/workflow-variables/hooks/useSearchVariable'; -import { extractRawVariableNamePart } from '@/workflow/workflow-variables/utils/extractRawVariableNamePart'; import { css, useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { isDefined } from 'twenty-shared/utils'; +import { extractRawVariableNamePart } from 'twenty-shared/workflow'; import { IconAlertTriangle, IconX } from 'twenty-ui/display'; const StyledChip = styled.div<{ deletable: boolean; danger: boolean }>` diff --git a/packages/twenty-front/src/modules/workflow/types/Workflow.ts b/packages/twenty-front/src/modules/workflow/types/Workflow.ts index bc94c37b1f4..088a58d1318 100644 --- a/packages/twenty-front/src/modules/workflow/types/Workflow.ts +++ b/packages/twenty-front/src/modules/workflow/types/Workflow.ts @@ -1,4 +1,7 @@ import { + type BulkRecordsAvailability, + type GlobalAvailability, + type SingleRecordAvailability, type workflowAiAgentActionSchema, type workflowCodeActionSchema, type workflowCreateRecordActionSchema, @@ -82,6 +85,11 @@ export type WorkflowManualTriggerAvailability = | 'EVERYWHERE' | 'WHEN_RECORD_SELECTED'; +export type WorkflowManualTriggerAvailabilityV2 = + | GlobalAvailability + | SingleRecordAvailability + | BulkRecordsAvailability; + export type WorkflowTrigger = z.infer; export type WorkflowTriggerType = WorkflowTrigger['type']; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepNodeDetail.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepNodeDetail.tsx index 7525eba490e..57d458c34b8 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepNodeDetail.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepNodeDetail.tsx @@ -18,9 +18,12 @@ import { WorkflowEditActionHttpRequest } from '@/workflow/workflow-steps/workflo import { WorkflowEditActionIterator } from '@/workflow/workflow-steps/workflow-actions/iterator-action/WorkflowEditActionIterator'; import { WorkflowEditTriggerCronForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm'; import { WorkflowEditTriggerDatabaseEventForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm'; -import { WorkflowEditTriggerManualForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerManualForm'; +import { WorkflowEditTriggerManual } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerManual'; +import { WorkflowEditTriggerManualDeprecated } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerManualDeprecated'; import { WorkflowEditTriggerWebhookForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { assertUnreachable, isDefined } from 'twenty-shared/utils'; +import { FeatureFlagKey } from '~/generated/graphql'; type WorkflowRunStepNodeDetailProps = { stepId: string; @@ -41,6 +44,10 @@ export const WorkflowRunStepNodeDetail = ({ steps, }); + const isIteratorEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_WORKFLOW_ITERATOR_ENABLED, + ); + if (!isDefined(stepDefinition) || !isDefined(stepDefinition.definition)) { return null; } @@ -60,8 +67,20 @@ export const WorkflowRunStepNodeDetail = ({ ); } case 'MANUAL': { + if (isIteratorEnabled) { + return ( + + ); + } + return ( - { + const isIteratorEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_WORKFLOW_ITERATOR_ENABLED, + ); const stepDefinition = getStepDefinitionOrThrow({ stepId, trigger, @@ -68,8 +74,18 @@ export const WorkflowStepDetail = ({ ); } case 'MANUAL': { + if (isIteratorEnabled) { + return ( + + ); + } + return ( - { diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowDropdownStepOutputItems.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowDropdownStepOutputItems.tsx index 581d38dd9cb..57ac4bccf00 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowDropdownStepOutputItems.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowDropdownStepOutputItems.tsx @@ -15,7 +15,6 @@ import { getStepFilterOperands } from '@/workflow/workflow-steps/workflow-action import { useVariableDropdown } from '@/workflow/workflow-variables/hooks/useVariableDropdown'; import { isRecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/guards/isRecordOutputSchemaV2'; import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; -import { extractRawVariableNamePart } from '@/workflow/workflow-variables/utils/extractRawVariableNamePart'; import { getCurrentSubStepFromPath } from '@/workflow/workflow-variables/utils/getCurrentSubStepFromPath'; import { getStepHeaderLabel } from '@/workflow/workflow-variables/utils/getStepHeaderLabel'; import { getStepItemIcon } from '@/workflow/workflow-variables/utils/getStepItemIcon'; @@ -25,6 +24,7 @@ import { useLingui } from '@lingui/react/macro'; import { useRecoilCallback } from 'recoil'; import { type StepFilter } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { extractRawVariableNamePart } from 'twenty-shared/workflow'; import { IconChevronLeft, OverflowingTextWithTooltip, diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx index a4d5ec813cb..ad10e93ac56 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx @@ -9,12 +9,12 @@ import { useAvailableVariablesInWorkflowStep } from '@/workflow/workflow-variabl import { useSearchVariable } from '@/workflow/workflow-variables/hooks/useSearchVariable'; import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; -import { extractRawVariableNamePart } from '@/workflow/workflow-variables/utils/extractRawVariableNamePart'; import { useTheme } from '@emotion/react'; import { useLingui } from '@lingui/react/macro'; import { useContext, useState } from 'react'; import { type StepFilter } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { extractRawVariableNamePart } from 'twenty-shared/workflow'; import { useIcons } from 'twenty-ui/display'; import { FieldMetadataType } from '~/generated-metadata/graphql'; diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManual.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManual.tsx new file mode 100644 index 00000000000..800ef0aaa28 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManual.tsx @@ -0,0 +1,241 @@ +import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; +import { IconPicker } from '@/ui/input/components/IconPicker'; +import { Select } from '@/ui/input/components/Select'; +import { SelectControl } from '@/ui/input/components/SelectControl'; +import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; +import { type WorkflowManualTrigger } from '@/workflow/types/Workflow'; +import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; +import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; +import { MANUAL_TRIGGER_AVAILABILITY_TYPE_OPTIONS } from '@/workflow/workflow-trigger/constants/ManualTriggerAvailabilityTypeOptions'; +import { MANUAL_TRIGGER_IS_PINNED_OPTIONS } from '@/workflow/workflow-trigger/constants/ManualTriggerIsPinnedOptions'; +import { getManualTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings'; +import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerDefaultLabel'; +import { getTriggerHeaderType } from '@/workflow/workflow-trigger/utils/getTriggerHeaderType'; +import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon'; +import { getTriggerIconColor } from '@/workflow/workflow-trigger/utils/getTriggerIconColor'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { useLingui } from '@lingui/react/macro'; +import { useIcons } from 'twenty-ui/display'; +import { type SelectOption } from 'twenty-ui/input'; + +type WorkflowEditTriggerManualProps = { + trigger: WorkflowManualTrigger; + triggerOptions: + | { + readonly: true; + onTriggerUpdate?: undefined; + } + | { + readonly?: false; + onTriggerUpdate: (trigger: WorkflowManualTrigger) => void; + }; +}; + +const StyledLabel = styled.span` + color: ${({ theme }) => theme.font.color.light}; + font-size: ${({ theme }) => theme.font.size.xs}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + margin-bottom: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledDescription = styled.span` + color: ${({ theme }) => theme.font.color.light}; + font-size: ${({ theme }) => theme.font.size.sm}; + margin-top: ${({ theme }) => theme.spacing(0.25)}; +`; + +const StyledIconPickerContainer = styled.div` + display: flex; + flex-direction: column; +`; + +export const WorkflowEditTriggerManual = ({ + trigger, + triggerOptions, +}: WorkflowEditTriggerManualProps) => { + const theme = useTheme(); + + const { t } = useLingui(); + + const { getIcon } = useIcons(); + + const { activeNonSystemObjectMetadataItems } = + useFilteredObjectMetadataItems(); + + const availableMetadata: Array> = + activeNonSystemObjectMetadataItems.map((item) => ({ + label: item.labelPlural, + value: item.nameSingular, + Icon: getIcon(item.icon), + })); + + const availability = trigger.settings.availability; + + const headerTitle = trigger.name ?? getTriggerDefaultLabel(trigger); + + const headerIcon = getTriggerIcon(trigger); + + const headerType = getTriggerHeaderType(trigger); + + const availabilityDescriptions = { + SINGLE_RECORD: t`The selected record will be passed to your workflow`, + BULK_RECORDS: t`The selected records will be passed to your workflow`, + GLOBAL: t`No record is required to trigger this workflow`, + }; + + return ( + <> + { + if (triggerOptions.readonly === true) { + return; + } + + triggerOptions.onTriggerUpdate({ + ...trigger, + name: newName, + }); + }} + Icon={getIcon(headerIcon)} + iconColor={getTriggerIconColor({ theme, triggerType: trigger.type })} + initialTitle={headerTitle} + headerType={headerType} + disabled={triggerOptions.readonly} + /> + + { + if (triggerOptions.readonly === true || !availability) { + return; + } + + triggerOptions.onTriggerUpdate({ + ...trigger, + settings: { + ...trigger.settings, + availability: { + type: availability.type, + objectNameSingular, + }, + outputSchema: {}, + }, + }); + }} + dropdownOffset={{ y: parseInt(theme.spacing(1), 10) }} + dropdownWidth={GenericDropdownContentWidth.ExtraLarge} + /> + ) : null} + + { + if (triggerOptions.readonly === true) { + e.stopPropagation(); + e.preventDefault(); + } + }} + > + {t`Command Icon`} + + {t`The icon your workflow trigger will display in the command menu`} + + } + onChange={({ iconKey }) => { + if (triggerOptions.readonly === true) { + return; + } + + triggerOptions.onTriggerUpdate({ + ...trigger, + settings: { + ...trigger.settings, + icon: iconKey, + }, + }); + }} + /> + +