From 2b59198a1bb1f24c708db441b1db9464de5a34f8 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Tue, 9 Sep 2025 18:37:00 +0200 Subject: [PATCH] Migrate output schema to V2 (#14311) Previous refacto was creating output schema V2 which has more specific schemas based on the step type. Before we were using one common schema, which was too complex when searching for variable informations. This PR migrate the deprecated schemas and remove the old code: - mark previous `BaseOutputSchema` as deprecated - remove other previous schemas - use V2 everywhere - icon should not be stored in schema. Instead it should be generated based on the fieldmetadata or the item type --- .../ai/hooks/useAiAgentOutputSchema.ts | 12 +- .../__tests__/getFunctionOutputSchema.test.ts | 8 +- .../utils/getFunctionOutputSchema.ts | 17 +- .../workflow/hooks/useStepsOutputSchema.ts | 16 +- .../stepsOutputSchemaFamilySelector.ts | 4 +- .../states/stepsOutputSchemaFamilyState.ts | 4 +- .../components/WorkflowEditActionAiAgent.tsx | 4 +- .../WorkflowOutputFieldTypeSelector.tsx | 2 +- .../WorkflowDropdownStepOutputItems.tsx | 48 ++- .../WorkflowStepFilterFieldSelect.tsx | 8 +- .../hooks/useHttpRequestOutputSchema.ts | 4 +- .../convertOutputSchemaToJson.test.ts | 76 ++-- .../getHttpRequestOutputSchema.test.ts | 115 +----- .../utils/convertOutputSchemaToJson.ts | 10 +- .../utils/getHttpRequestOutputSchema.ts | 19 +- .../components/WorkflowVariablesDropdown.tsx | 20 +- .../WorkflowVariablesDropdownFieldItems.tsx | 82 ---- ...=> WorkflowVariablesDropdownStepItems.tsx} | 66 ++-- ...tsx => WorkflowVariablesDropdownSteps.tsx} | 10 +- .../useAvailableVariablesInWorkflowStep.ts | 12 +- .../hooks/useSearchVariable.ts | 69 +--- .../hooks/useVariableDropdown.ts | 29 +- .../types/BaseOutputSchemaV2.ts | 46 +-- .../types/CodeOutputSchema.ts | 12 +- .../types/DatabaseEventTriggerOutputSchema.ts | 3 + .../types/LinkOutputSchema.ts | 10 + .../types/OpenStepOutputSchema.ts | 3 + .../types/RecordActionOutputSchema.ts | 3 + .../types/StepOutputSchema.ts | 64 --- .../types/StepOutputSchemaV2.ts | 24 ++ .../types/guards/isBaseOutputSchemaV2.ts | 9 + .../types/guards/isCodeOutputSchema.ts | 13 + .../isDatabaseEventTriggerOutputSchema.ts | 13 + .../types/guards/isFindRecordsOutputSchema.ts | 13 + .../types/guards/isFormOutputSchema.ts | 13 + .../types/guards/isLinkOutputSchema.ts | 13 + .../types/guards/isOpenStepOutputSchema.ts | 25 ++ .../guards/isRecordActionOutputSchema.ts | 15 + .../types/guards/isRecordOutputSchemaV2.ts | 13 + .../__tests__/filterOutputSchema.test.ts | 13 +- .../getCurrentSubStepFromPath.test.ts | 14 +- .../__tests__/getStepHeaderLabel.test.ts | 23 +- .../__tests__/isBaseOutputSchema.test.ts | 14 - .../searchVariableThroughOutputSchema.test.ts | 373 ------------------ .../utils/filterOutputSchema.ts | 58 +-- .../utils/getCurrentSubStepFromPath.ts | 18 +- .../utils/getOutputSchemaType.ts | 26 -- .../utils/getStepHeaderLabel.ts | 12 +- .../utils/getStepItemIcon.ts | 38 ++ .../utils/isBaseOutputSchema.ts | 10 - .../utils/isLinkOutputSchema.ts | 10 - .../utils/isRecordOutputSchema.ts | 10 - .../utils/isRecordOutputSchemaV2.ts | 10 - .../searchVariableThroughOutputSchema.ts | 200 ---------- .../searchVariableThroughOutputSchemaV2.ts | 83 ++++ ...searchVariableThroughRecordOutputSchema.ts | 2 +- .../utils/should-generate-field-fake-value.ts | 4 +- 57 files changed, 589 insertions(+), 1256 deletions(-) delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownFieldItems.tsx rename packages/twenty-front/src/modules/workflow/workflow-variables/components/{WorkflowVariablesDropdownAllItems.tsx => WorkflowVariablesDropdownStepItems.tsx} (64%) rename packages/twenty-front/src/modules/workflow/workflow-variables/components/{WorkflowVariablesDropdownWorkflowStepItems.tsx => WorkflowVariablesDropdownSteps.tsx} (90%) create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/DatabaseEventTriggerOutputSchema.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/LinkOutputSchema.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/OpenStepOutputSchema.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/RecordActionOutputSchema.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/StepOutputSchema.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/StepOutputSchemaV2.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isBaseOutputSchemaV2.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isCodeOutputSchema.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isDatabaseEventTriggerOutputSchema.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isFindRecordsOutputSchema.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isFormOutputSchema.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isLinkOutputSchema.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isOpenStepOutputSchema.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isRecordActionOutputSchema.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isRecordOutputSchemaV2.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/isBaseOutputSchema.test.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/searchVariableThroughOutputSchema.test.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/getOutputSchemaType.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/getStepItemIcon.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/isBaseOutputSchema.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/isLinkOutputSchema.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/isRecordOutputSchema.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/isRecordOutputSchemaV2.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughOutputSchema.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughOutputSchemaV2.ts diff --git a/packages/twenty-front/src/modules/ai/hooks/useAiAgentOutputSchema.ts b/packages/twenty-front/src/modules/ai/hooks/useAiAgentOutputSchema.ts index a4a5cf646f..4aecfbd6fe 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAiAgentOutputSchema.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAiAgentOutputSchema.ts @@ -1,14 +1,13 @@ -import { type WorkflowAiAgentAction } from '@/workflow/types/Workflow'; import { type OutputSchemaField } from '@/ai/constants/OutputFieldTypeOptions'; -import { type BaseOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { type WorkflowAiAgentAction } from '@/workflow/types/Workflow'; +import { type BaseOutputSchemaDeprecated } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2'; import { useState } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { useDebouncedCallback } from 'use-debounce'; import { v4 } from 'uuid'; -import { getFieldIcon } from '../utils/getFieldIcon'; export const useAiAgentOutputSchema = ( - outputSchema?: BaseOutputSchema, + outputSchema?: BaseOutputSchemaDeprecated, onActionUpdate?: (action: WorkflowAiAgentAction) => void, action?: WorkflowAiAgentAction, readonly?: boolean, @@ -18,7 +17,6 @@ export const useAiAgentOutputSchema = ( id: v4(), name, type: field.type, - description: field.description, })), ); @@ -28,16 +26,14 @@ export const useAiAgentOutputSchema = ( return; } - const newOutputSchema = fields.reduce( + const newOutputSchema = fields.reduce( (schema, field) => { if (isDefined(field.name)) { schema[field.name] = { isLeaf: true, type: field.type, value: null, - icon: getFieldIcon(field.type), label: field.name, - description: field.description, }; } return schema; diff --git a/packages/twenty-front/src/modules/serverless-functions/utils/__tests__/getFunctionOutputSchema.test.ts b/packages/twenty-front/src/modules/serverless-functions/utils/__tests__/getFunctionOutputSchema.test.ts index 9f425ffd1b..a3a9e13898 100644 --- a/packages/twenty-front/src/modules/serverless-functions/utils/__tests__/getFunctionOutputSchema.test.ts +++ b/packages/twenty-front/src/modules/serverless-functions/utils/__tests__/getFunctionOutputSchema.test.ts @@ -14,25 +14,23 @@ describe('getFunctionOutputSchema', () => { isLeaf: true, type: 'unknown', value: null, - icon: 'IconVariable', label: 'a', }, b: { isLeaf: true, type: 'string', value: 'b', - icon: 'IconVariable', label: 'b', }, c: { isLeaf: false, - icon: 'IconVariable', + type: 'object', + label: 'c', value: { cc: { isLeaf: true, type: 'number', value: 1, - icon: 'IconVariable', label: 'cc', }, }, @@ -41,14 +39,12 @@ describe('getFunctionOutputSchema', () => { isLeaf: true, type: 'boolean', value: true, - icon: 'IconVariable', label: 'd', }, e: { isLeaf: true, type: 'array', value: [1, 2, 3], - icon: 'IconVariable', label: 'e', }, }; diff --git a/packages/twenty-front/src/modules/serverless-functions/utils/getFunctionOutputSchema.ts b/packages/twenty-front/src/modules/serverless-functions/utils/getFunctionOutputSchema.ts index 5a34c50f6b..37a726e5fc 100644 --- a/packages/twenty-front/src/modules/serverless-functions/utils/getFunctionOutputSchema.ts +++ b/packages/twenty-front/src/modules/serverless-functions/utils/getFunctionOutputSchema.ts @@ -1,9 +1,11 @@ -import { type InputSchemaPropertyType } from '@/workflow/types/InputSchema'; -import { type BaseOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { + type BaseOutputSchemaV2, + type LeafType, +} from '@/workflow/workflow-variables/types/BaseOutputSchemaV2'; import { isObject } from '@sniptt/guards'; import { isDefined } from 'twenty-shared/utils'; -const getValueType = (value: any): InputSchemaPropertyType => { +const getValueType = (value: any): LeafType => { if (!isDefined(value) || value === null) { return 'unknown'; } @@ -19,20 +21,18 @@ const getValueType = (value: any): InputSchemaPropertyType => { if (Array.isArray(value)) { return 'array'; } - if (isObject(value)) { - return 'object'; - } return 'unknown'; }; export const getFunctionOutputSchema = (testResult: object) => { return testResult ? Object.entries(testResult).reduce( - (acc: BaseOutputSchema, [key, value]) => { + (acc: BaseOutputSchemaV2, [key, value]) => { if (isObject(value) && !Array.isArray(value)) { acc[key] = { isLeaf: false, - icon: 'IconVariable', + type: 'object', + label: key, value: getFunctionOutputSchema(value), }; } else { @@ -40,7 +40,6 @@ export const getFunctionOutputSchema = (testResult: object) => { isLeaf: true, value, type: getValueType(value), - icon: 'IconVariable', label: key, }; } diff --git a/packages/twenty-front/src/modules/workflow/hooks/useStepsOutputSchema.ts b/packages/twenty-front/src/modules/workflow/hooks/useStepsOutputSchema.ts index c01de8dfe3..5f8ec90939 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useStepsOutputSchema.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useStepsOutputSchema.ts @@ -5,9 +5,9 @@ import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/ import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerDefaultLabel'; import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon'; import { - type OutputSchema, - type StepOutputSchema, -} from '@/workflow/workflow-variables/types/StepOutputSchema'; + type OutputSchemaV2, + type StepOutputSchemaV2, +} from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; @@ -17,11 +17,12 @@ export const useStepsOutputSchema = () => { ({ set }) => (workflowVersion: WorkflowVersion) => { workflowVersion.steps?.forEach((step) => { - const stepOutputSchema: StepOutputSchema = { + const stepOutputSchema: StepOutputSchemaV2 = { id: step.id, name: step.name, + type: step.type, icon: getActionIcon(step.type), - outputSchema: step.settings?.outputSchema as OutputSchema, + outputSchema: step.settings?.outputSchema as OutputSchemaV2, }; set( @@ -37,13 +38,14 @@ export const useStepsOutputSchema = () => { if (isDefined(trigger)) { const triggerIconKey = getTriggerIcon(trigger); - const triggerOutputSchema: StepOutputSchema = { + const triggerOutputSchema: StepOutputSchemaV2 = { id: TRIGGER_STEP_ID, name: isDefined(trigger.name) ? trigger.name : getTriggerDefaultLabel(trigger), + type: trigger.type, icon: triggerIconKey, - outputSchema: trigger.settings?.outputSchema as OutputSchema, + outputSchema: trigger.settings?.outputSchema as OutputSchemaV2, }; set( diff --git a/packages/twenty-front/src/modules/workflow/states/selectors/stepsOutputSchemaFamilySelector.ts b/packages/twenty-front/src/modules/workflow/states/selectors/stepsOutputSchemaFamilySelector.ts index 14e2a29d29..8606525e2b 100644 --- a/packages/twenty-front/src/modules/workflow/states/selectors/stepsOutputSchemaFamilySelector.ts +++ b/packages/twenty-front/src/modules/workflow/states/selectors/stepsOutputSchemaFamilySelector.ts @@ -1,11 +1,11 @@ import { stepsOutputSchemaFamilyState } from '@/workflow/states/stepsOutputSchemaFamilyState'; import { getStepOutputSchemaFamilyStateKey } from '@/workflow/utils/getStepOutputSchemaFamilyStateKey'; -import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; import { selectorFamily } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const stepsOutputSchemaFamilySelector = selectorFamily< - StepOutputSchema[], + StepOutputSchemaV2[], { workflowVersionId: string; stepIds: string[] } >({ key: 'stepsOutputSchemaFamilySelector', diff --git a/packages/twenty-front/src/modules/workflow/states/stepsOutputSchemaFamilyState.ts b/packages/twenty-front/src/modules/workflow/states/stepsOutputSchemaFamilyState.ts index 82f7f58fda..9599b6c657 100644 --- a/packages/twenty-front/src/modules/workflow/states/stepsOutputSchemaFamilyState.ts +++ b/packages/twenty-front/src/modules/workflow/states/stepsOutputSchemaFamilyState.ts @@ -1,8 +1,8 @@ import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState'; -import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; export const stepsOutputSchemaFamilyState = createFamilyState< - StepOutputSchema | null, + StepOutputSchemaV2 | null, string | undefined >({ key: 'stepsOutputSchemaFamilyState', diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowEditActionAiAgent.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowEditActionAiAgent.tsx index 8d05d31371..e0c9b8fa36 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowEditActionAiAgent.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowEditActionAiAgent.tsx @@ -7,7 +7,7 @@ import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowS import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader'; import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker'; -import { type BaseOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { type BaseOutputSchemaDeprecated } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; import { useRecoilValue } from 'recoil'; @@ -47,7 +47,7 @@ export const WorkflowEditActionAiAgent = ({ }); const { handleOutputSchemaChange, outputFields } = useAiAgentOutputSchema( - action.settings.outputSchema as BaseOutputSchema, + action.settings.outputSchema as BaseOutputSchemaDeprecated, actionOptions.readonly === true ? undefined : actionOptions.onActionUpdate, action, actionOptions.readonly, diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowOutputFieldTypeSelector.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowOutputFieldTypeSelector.tsx index 24224ec877..a67f41b496 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowOutputFieldTypeSelector.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowOutputFieldTypeSelector.tsx @@ -1,7 +1,7 @@ +import { OUTPUT_FIELD_TYPE_OPTIONS } from '@/ai/constants/OutputFieldTypeOptions'; import { Select } from '@/ui/input/components/Select'; import { type InputSchemaPropertyType } from '@/workflow/types/InputSchema'; import { t } from '@lingui/core/macro'; -import { OUTPUT_FIELD_TYPE_OPTIONS } from '@/ai/constants/OutputFieldTypeOptions'; type WorkflowOutputFieldTypeSelectorProps = { value?: InputSchemaPropertyType; 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 6b6130ff06..967c2c5ac7 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 @@ -2,9 +2,9 @@ import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenu import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; -import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; import { useGetFieldMetadataItemByIdOrThrow } from '@/object-metadata/hooks/useGetFieldMetadataItemById'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; @@ -13,12 +13,13 @@ import { stepsOutputSchemaFamilySelector } from '@/workflow/states/selectors/ste import { useUpsertStepFilterSettings } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useUpsertStepFilterSettings'; import { getStepFilterOperands } from '@/workflow/workflow-steps/workflow-actions/filter-action/utils/getStepFilterOperands'; 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 { getVariableTemplateFromPath } from '@/workflow/workflow-variables/utils/getVariableTemplateFromPath'; -import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isRecordOutputSchema'; -import { searchVariableThroughOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughOutputSchema'; +import { searchVariableThroughOutputSchemaV2 } from '@/workflow/workflow-variables/utils/searchVariableThroughOutputSchemaV2'; import { useLingui } from '@lingui/react/macro'; import { useRecoilCallback } from 'recoil'; import { type StepFilter } from 'twenty-shared/types'; @@ -32,7 +33,7 @@ import { MenuItemSelect } from 'twenty-ui/navigation'; type WorkflowDropdownStepOutputItemsProps = { stepFilter: StepFilter; - step: StepOutputSchema; + step: StepOutputSchemaV2; onSelect: () => void; onBack: () => void; }; @@ -51,6 +52,7 @@ export const WorkflowDropdownStepOutputItems = ({ useGetFieldMetadataItemByIdOrThrow(); const workflowVersionId = useWorkflowVersionIdOrThrow(); + const { objectMetadataItems } = useObjectMetadataItems(); const updateStepFilter = useRecoilCallback( ({ snapshot }) => @@ -75,8 +77,9 @@ export const WorkflowDropdownStepOutputItems = ({ .getValue(); const { variableType, fieldMetadataId, compositeFieldSubFieldName } = - searchVariableThroughOutputSchema({ + searchVariableThroughOutputSchemaV2({ stepOutputSchema: currentStepOutputSchema, + stepType: step.type, rawVariableName, isFullRecord: false, }); @@ -112,6 +115,7 @@ export const WorkflowDropdownStepOutputItems = ({ }, [ workflowVersionId, + step.type, getFieldMetadataItemByIdOrThrow, upsertStepFilterSettings, stepFilter, @@ -142,7 +146,7 @@ export const WorkflowDropdownStepOutputItems = ({ const getDisplayedSubStepObject = () => { const currentSubStep = getCurrentSubStepFromPath(step, currentPath); - if (!isRecordOutputSchema(currentSubStep)) { + if (!isRecordOutputSchemaV2(currentSubStep)) { return; } @@ -152,14 +156,14 @@ export const WorkflowDropdownStepOutputItems = ({ const handleSelectObject = () => { const currentSubStep = getCurrentSubStepFromPath(step, currentPath); - if (!isRecordOutputSchema(currentSubStep)) { + if (!isRecordOutputSchemaV2(currentSubStep)) { return; } updateStepFilter({ rawVariableName: getVariableTemplateFromPath({ stepId: step.id, - path: [...currentPath, currentSubStep.object.fieldIdName], + path: [...currentPath, 'id'], }), isFullRecord: true, }); @@ -168,16 +172,22 @@ export const WorkflowDropdownStepOutputItems = ({ const displayedSubStepObject = getDisplayedSubStepObject(); + const subStepObjectMetadataItem = isDefined( + displayedSubStepObject?.objectMetadataId, + ) + ? objectMetadataItems.find( + (item) => item.id === displayedSubStepObject?.objectMetadataId, + ) + : undefined; + const shouldDisplaySubStepObject = searchInputValue - ? displayedSubStepObject?.label && - displayedSubStepObject.label + ? isDefined(subStepObjectMetadataItem) && + subStepObjectMetadataItem.labelSingular .toLowerCase() .includes(searchInputValue.toLowerCase()) : true; - const shouldDisplayObject = - shouldDisplaySubStepObject && displayedSubStepObject?.label; - const nameSingular = displayedSubStepObject?.nameSingular; + const objectLabel = subStepObjectMetadataItem?.labelSingular; return ( @@ -200,22 +210,22 @@ export const WorkflowDropdownStepOutputItems = ({ /> - {shouldDisplayObject && ( + {shouldDisplaySubStepObject && ( )} - {filteredOptions.length > 0 && shouldDisplayObject && ( + {filteredOptions.length > 0 && shouldDisplaySubStepObject && ( )} {filteredOptions.map(([key, subStep]) => ( 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 251d4e25fc..a4d5ec813c 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 @@ -4,11 +4,11 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { WorkflowDropdownStepOutputItems } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowDropdownStepOutputItems'; import { WorkflowStepFilterContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/WorkflowStepFilterContext'; -import { WorkflowVariablesDropdownWorkflowStepItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownWorkflowStepItems'; +import { WorkflowVariablesDropdownSteps } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownSteps'; import { useAvailableVariablesInWorkflowStep } from '@/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep'; import { useSearchVariable } from '@/workflow/workflow-variables/hooks/useSearchVariable'; +import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; -import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; import { extractRawVariableNamePart } from '@/workflow/workflow-variables/utils/extractRawVariableNamePart'; import { useTheme } from '@emotion/react'; import { useLingui } from '@lingui/react/macro'; @@ -51,7 +51,7 @@ export const WorkflowStepFilterFieldSelect = ({ : undefined; const [selectedStep, setSelectedStep] = useState< - StepOutputSchema | undefined + StepOutputSchemaV2 | undefined >(initialStep); const stepId = extractRawVariableNamePart({ @@ -137,7 +137,7 @@ export const WorkflowStepFilterFieldSelect = ({ } dropdownComponents={ !isDefined(selectedStep) ? ( - { it('should convert simple object schema to JSON', () => { - const schema: BaseOutputSchema = { + const schema: BaseOutputSchemaV2 = { name: { isLeaf: true, type: 'string', label: 'name', value: 'John', - icon: 'IconText', }, age: { isLeaf: true, type: 'number', label: 'age', value: 25, - icon: 'IconNumber', }, isActive: { isLeaf: true, type: 'boolean', label: 'isActive', value: true, - icon: 'IconCheckbox', }, }; const expected = { @@ -35,27 +32,24 @@ describe('convertOutputSchemaToJson', () => { }); it('should convert array schema to JSON array', () => { - const schema: BaseOutputSchema = { + const schema: BaseOutputSchemaV2 = { '0': { isLeaf: true, type: 'string', label: '0', value: 'first', - icon: 'IconText', }, '1': { isLeaf: true, type: 'string', label: '1', value: 'second', - icon: 'IconText', }, '2': { isLeaf: true, type: 'string', label: '2', value: 'third', - icon: 'IconText', }, }; const expected = ['first', 'second', 'third']; @@ -63,9 +57,10 @@ describe('convertOutputSchemaToJson', () => { }); it('should convert nested object schema to JSON', () => { - const schema: BaseOutputSchema = { + const schema: BaseOutputSchemaV2 = { user: { isLeaf: false, + type: 'object', label: 'user', value: { name: { @@ -73,17 +68,16 @@ describe('convertOutputSchemaToJson', () => { type: 'string', label: 'name', value: 'John', - icon: 'IconText', }, age: { isLeaf: true, type: 'number', label: 'age', value: 25, - icon: 'IconNumber', }, address: { isLeaf: false, + type: 'object', label: 'address', value: { city: { @@ -91,20 +85,16 @@ describe('convertOutputSchemaToJson', () => { type: 'string', label: 'city', value: 'New York', - icon: 'IconText', }, country: { isLeaf: true, type: 'string', label: 'country', value: 'USA', - icon: 'IconText', }, }, - icon: 'IconBox', }, }, - icon: 'IconBox', }, }; const expected = { @@ -121,9 +111,10 @@ describe('convertOutputSchemaToJson', () => { }); it('should convert nested array schema to JSON', () => { - const schema: BaseOutputSchema = { + const schema: BaseOutputSchemaV2 = { '0': { isLeaf: false, + type: 'object', label: '0', value: { '0': { @@ -131,20 +122,18 @@ describe('convertOutputSchemaToJson', () => { type: 'number', label: '0', value: 1, - icon: 'IconNumber', }, '1': { isLeaf: true, type: 'number', label: '1', value: 2, - icon: 'IconNumber', }, }, - icon: 'IconBox', }, '1': { isLeaf: false, + type: 'object', label: '1', value: { '0': { @@ -152,17 +141,14 @@ describe('convertOutputSchemaToJson', () => { type: 'number', label: '0', value: 3, - icon: 'IconNumber', }, '1': { isLeaf: true, type: 'number', label: '1', value: 4, - icon: 'IconNumber', }, }, - icon: 'IconBox', }, }; const expected = [ @@ -173,13 +159,15 @@ describe('convertOutputSchemaToJson', () => { }); it('should handle mixed array and object schema', () => { - const schema: BaseOutputSchema = { + const schema: BaseOutputSchemaV2 = { users: { isLeaf: false, + type: 'object', label: 'users', value: { '0': { isLeaf: false, + type: 'object', label: '0', value: { name: { @@ -187,20 +175,18 @@ describe('convertOutputSchemaToJson', () => { type: 'string', label: 'name', value: 'John', - icon: 'IconText', }, age: { isLeaf: true, type: 'number', label: 'age', value: 25, - icon: 'IconNumber', }, }, - icon: 'IconBox', }, '1': { isLeaf: false, + type: 'object', label: '1', value: { name: { @@ -208,23 +194,20 @@ describe('convertOutputSchemaToJson', () => { type: 'string', label: 'name', value: 'Jane', - icon: 'IconText', }, age: { isLeaf: true, type: 'number', label: 'age', value: 30, - icon: 'IconNumber', }, }, - icon: 'IconBox', }, }, - icon: 'IconBox', }, metadata: { isLeaf: false, + type: 'object', label: 'metadata', value: { count: { @@ -232,17 +215,14 @@ describe('convertOutputSchemaToJson', () => { type: 'number', label: 'count', value: 2, - icon: 'IconNumber', }, active: { isLeaf: true, type: 'boolean', label: 'active', value: true, - icon: 'IconCheckbox', }, }, - icon: 'IconBox', }, }; const expected = { @@ -265,20 +245,18 @@ describe('convertOutputSchemaToJson', () => { }); it('should handle null values', () => { - const schema: BaseOutputSchema = { + const schema: BaseOutputSchemaV2 = { name: { isLeaf: true, type: 'unknown', label: 'name', value: null, - icon: 'IconQuestionMark', }, age: { isLeaf: true, type: 'unknown', label: 'age', value: null, - icon: 'IconQuestionMark', }, }; const expected = { @@ -289,23 +267,26 @@ describe('convertOutputSchemaToJson', () => { }); it('should handle empty object schema', () => { - const schema: BaseOutputSchema = {}; + const schema: BaseOutputSchemaV2 = {}; const expected = {}; expect(convertOutputSchemaToJson(schema)).toEqual(expected); }); it('should handle complex nested structure', () => { - const schema: BaseOutputSchema = { + const schema: BaseOutputSchemaV2 = { data: { isLeaf: false, + type: 'object', label: 'data', value: { users: { isLeaf: false, + type: 'object', label: 'users', value: { '0': { isLeaf: false, + type: 'object', label: '0', value: { id: { @@ -313,17 +294,16 @@ describe('convertOutputSchemaToJson', () => { type: 'number', label: 'id', value: 1, - icon: 'IconNumber', }, name: { isLeaf: true, type: 'string', label: 'name', value: 'John', - icon: 'IconText', }, roles: { isLeaf: false, + type: 'object', label: 'roles', value: { '0': { @@ -331,20 +311,18 @@ describe('convertOutputSchemaToJson', () => { type: 'string', label: '0', value: 'admin', - icon: 'IconText', }, '1': { isLeaf: true, type: 'string', label: '1', value: 'user', - icon: 'IconText', }, }, - icon: 'IconBox', }, settings: { isLeaf: false, + type: 'object', label: 'settings', value: { theme: { @@ -352,26 +330,22 @@ describe('convertOutputSchemaToJson', () => { type: 'string', label: 'theme', value: 'dark', - icon: 'IconText', }, notifications: { isLeaf: true, type: 'boolean', label: 'notifications', value: true, - icon: 'IconCheckbox', }, }, - icon: 'IconBox', }, }, - icon: 'IconBox', }, }, - icon: 'IconBox', }, metadata: { isLeaf: false, + type: 'object', label: 'metadata', value: { total: { @@ -379,20 +353,16 @@ describe('convertOutputSchemaToJson', () => { type: 'number', label: 'total', value: 1, - icon: 'IconNumber', }, page: { isLeaf: true, type: 'number', label: 'page', value: 1, - icon: 'IconNumber', }, }, - icon: 'IconBox', }, }, - icon: 'IconBox', }, }; const expected = { diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/utils/__tests__/getHttpRequestOutputSchema.test.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/utils/__tests__/getHttpRequestOutputSchema.test.ts index 056a48ca06..ee53898119 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/utils/__tests__/getHttpRequestOutputSchema.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/utils/__tests__/getHttpRequestOutputSchema.test.ts @@ -23,14 +23,12 @@ describe('getHttpRequestOutputSchema', () => { type: 'string', label: 'name', value: 'John', - icon: 'IconAbc', }, email: { isLeaf: true, type: 'string', label: 'email', value: 'john@example.com', - icon: 'IconAbc', }, }; expect(getHttpRequestOutputSchema(input)).toEqual(expected); @@ -44,14 +42,12 @@ describe('getHttpRequestOutputSchema', () => { type: 'number', label: 'age', value: 25, - icon: 'IconText', }, score: { isLeaf: true, type: 'number', label: 'score', value: 98.5, - icon: 'IconText', }, }; expect(getHttpRequestOutputSchema(input)).toEqual(expected); @@ -65,14 +61,12 @@ describe('getHttpRequestOutputSchema', () => { type: 'boolean', label: 'isActive', value: true, - icon: 'IconCheckbox', }, isVerified: { isLeaf: true, type: 'boolean', label: 'isVerified', value: false, - icon: 'IconCheckbox', }, }; expect(getHttpRequestOutputSchema(input)).toEqual(expected); @@ -93,44 +87,40 @@ describe('getHttpRequestOutputSchema', () => { user: { isLeaf: false, label: 'user', + type: 'object', value: { name: { isLeaf: true, type: 'string', label: 'name', value: 'John', - icon: 'IconAbc', }, age: { isLeaf: true, type: 'number', label: 'age', value: 25, - icon: 'IconText', }, address: { isLeaf: false, label: 'address', + type: 'object', value: { city: { isLeaf: true, type: 'string', label: 'city', value: 'New York', - icon: 'IconAbc', }, country: { isLeaf: true, type: 'string', label: 'country', value: 'USA', - icon: 'IconAbc', }, }, - icon: 'IconBox', }, }, - icon: 'IconBox', }, }; expect(getHttpRequestOutputSchema(input)).toEqual(expected); @@ -144,74 +134,22 @@ describe('getHttpRequestOutputSchema', () => { }; const expected = { tags: { - isLeaf: false, + isLeaf: true, label: 'tags', - value: { - '0': { - isLeaf: true, - type: 'string', - label: '0', - value: 'tag1', - icon: 'IconAbc', - }, - '1': { - isLeaf: true, - type: 'string', - label: '1', - value: 'tag2', - icon: 'IconAbc', - }, - }, - icon: 'IconBox', + type: 'array', + value: ['tag1', 'tag2'], }, scores: { - isLeaf: false, + isLeaf: true, label: 'scores', - value: { - '0': { - isLeaf: true, - type: 'number', - label: '0', - value: 1, - icon: 'IconText', - }, - '1': { - isLeaf: true, - type: 'number', - label: '1', - value: 2, - icon: 'IconText', - }, - '2': { - isLeaf: true, - type: 'number', - label: '2', - value: 3, - icon: 'IconText', - }, - }, - icon: 'IconBox', + type: 'array', + value: [1, 2, 3], }, flags: { - isLeaf: false, + isLeaf: true, label: 'flags', - value: { - '0': { - isLeaf: true, - type: 'boolean', - label: '0', - value: true, - icon: 'IconCheckbox', - }, - '1': { - isLeaf: true, - type: 'boolean', - label: '1', - value: false, - icon: 'IconCheckbox', - }, - }, - icon: 'IconBox', + type: 'array', + value: [true, false], }, }; expect(getHttpRequestOutputSchema(input)).toEqual(expected); @@ -225,14 +163,12 @@ describe('getHttpRequestOutputSchema', () => { type: 'unknown', label: 'name', value: null, - icon: 'IconQuestionMark', }, age: { isLeaf: true, type: 'unknown', label: 'age', value: null, - icon: 'IconQuestionMark', }, }; expect(getHttpRequestOutputSchema(input)).toEqual(expected); @@ -256,70 +192,49 @@ describe('getHttpRequestOutputSchema', () => { type: 'string', label: 'name', value: 'John', - icon: 'IconAbc', }, age: { isLeaf: true, type: 'number', label: 'age', value: 25, - icon: 'IconText', }, isActive: { isLeaf: true, type: 'boolean', label: 'isActive', value: true, - icon: 'IconCheckbox', }, tags: { - isLeaf: false, + isLeaf: true, label: 'tags', - value: { - '0': { - isLeaf: true, - type: 'string', - label: '0', - value: 'tag1', - icon: 'IconAbc', - }, - '1': { - isLeaf: true, - type: 'string', - label: '1', - value: 'tag2', - icon: 'IconAbc', - }, - }, - icon: 'IconBox', + type: 'array', + value: ['tag1', 'tag2'], }, address: { isLeaf: false, label: 'address', + type: 'object', value: { city: { isLeaf: true, type: 'string', label: 'city', value: 'New York', - icon: 'IconAbc', }, zip: { isLeaf: true, type: 'number', label: 'zip', value: 10001, - icon: 'IconText', }, }, - icon: 'IconBox', }, metadata: { isLeaf: true, type: 'unknown', label: 'metadata', value: null, - icon: 'IconQuestionMark', }, }; expect(getHttpRequestOutputSchema(input)).toEqual(expected); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/utils/convertOutputSchemaToJson.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/utils/convertOutputSchemaToJson.ts index 0cc891954b..6d8ea293a4 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/utils/convertOutputSchemaToJson.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/utils/convertOutputSchemaToJson.ts @@ -1,7 +1,7 @@ -import { type BaseOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { type BaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2'; export const convertOutputSchemaToJson = ( - schema: BaseOutputSchema, + schema: BaseOutputSchemaV2, ): Record | unknown[] => { const keys = Object.keys(schema); @@ -17,7 +17,7 @@ export const convertOutputSchemaToJson = ( if (entry.isLeaf) { return entry.value; } - return convertOutputSchemaToJson(entry.value as BaseOutputSchema); + return convertOutputSchemaToJson(entry.value as BaseOutputSchemaV2); }); } @@ -27,7 +27,9 @@ export const convertOutputSchemaToJson = ( if (entry.isLeaf) { result[key] = entry.value; } else { - result[key] = convertOutputSchemaToJson(entry.value as BaseOutputSchema); + result[key] = convertOutputSchemaToJson( + entry.value as BaseOutputSchemaV2, + ); } }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/utils/getHttpRequestOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/utils/getHttpRequestOutputSchema.ts index 51fcb042f8..d24e4a6fda 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/utils/getHttpRequestOutputSchema.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/utils/getHttpRequestOutputSchema.ts @@ -1,5 +1,5 @@ import { type InputSchemaPropertyType } from '@/workflow/types/InputSchema'; -import { type BaseOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { type BaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2'; const getValueType = (value: unknown): InputSchemaPropertyType => { if (value === null || value === undefined) { @@ -25,12 +25,12 @@ const getValueType = (value: unknown): InputSchemaPropertyType => { export const getHttpRequestOutputSchema = ( responseData: unknown, -): BaseOutputSchema => { +): BaseOutputSchemaV2 => { if (typeof responseData !== 'object' || responseData === null) { return {}; } - const schema: BaseOutputSchema = {}; + const schema: BaseOutputSchemaV2 = {}; Object.entries(responseData).forEach(([key, value]) => { const type = getValueType(value); @@ -41,7 +41,6 @@ export const getHttpRequestOutputSchema = ( type: 'string', label: key, value, - icon: 'IconAbc', }; break; case 'number': @@ -50,7 +49,6 @@ export const getHttpRequestOutputSchema = ( type: 'number', label: key, value, - icon: 'IconText', }; break; case 'boolean': @@ -59,16 +57,22 @@ export const getHttpRequestOutputSchema = ( type: 'boolean', label: key, value, - icon: 'IconCheckbox', }; break; case 'array': + schema[key] = { + isLeaf: true, + label: key, + type: 'array', + value, + }; + break; case 'object': schema[key] = { isLeaf: false, label: key, value: getHttpRequestOutputSchema(value), - icon: 'IconBox', + type: 'object', }; break; case 'unknown': @@ -78,7 +82,6 @@ export const getHttpRequestOutputSchema = ( type: 'unknown', label: key, value: value === null ? null : String(value), - icon: 'IconQuestionMark', }; break; } diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdown.tsx b/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdown.tsx index 3f066846b5..65dcb42042 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdown.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdown.tsx @@ -4,13 +4,12 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { type InputSchemaPropertyType } from '@/workflow/types/InputSchema'; -import { WorkflowVariablesDropdownAllItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownAllItems'; -import { WorkflowVariablesDropdownFieldItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownFieldItems'; -import { WorkflowVariablesDropdownWorkflowStepItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownWorkflowStepItems'; +import { WorkflowVariablesDropdownStepItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownStepItems'; +import { WorkflowVariablesDropdownSteps } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownSteps'; import { SEARCH_VARIABLES_DROPDOWN_ID } from '@/workflow/workflow-variables/constants/SearchVariablesDropdownId'; import { useAvailableVariablesInWorkflowStep } from '@/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep'; -import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useState } from 'react'; @@ -73,7 +72,7 @@ export const WorkflowVariablesDropdown = ({ : undefined; const [selectedStep, setSelectedStep] = useState< - StepOutputSchema | undefined + StepOutputSchemaV2 | undefined >(initialStep); const handleStepSelect = (stepId: string) => { @@ -122,22 +121,17 @@ export const WorkflowVariablesDropdown = ({ } dropdownComponents={ !isDefined(selectedStep) ? ( - - ) : shouldDisplayRecordObjects ? ( - ) : ( - ) } diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownFieldItems.tsx b/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownFieldItems.tsx deleted file mode 100644 index 1520df5fd0..0000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownFieldItems.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; -import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; -import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; -import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; -import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; - -import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; -import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; -import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; -import { getStepHeaderLabel } from '@/workflow/workflow-variables/utils/getStepHeaderLabel'; -import { - IconChevronLeft, - OverflowingTextWithTooltip, - useIcons, -} from 'twenty-ui/display'; -import { MenuItemSelect } from 'twenty-ui/navigation'; -import { useVariableDropdown } from '../hooks/useVariableDropdown'; - -type WorkflowVariablesDropdownFieldItemsProps = { - step: StepOutputSchema; - onSelect: (value: string) => void; - onBack: () => void; -}; - -export const WorkflowVariablesDropdownFieldItems = ({ - step, - onSelect, - onBack, -}: WorkflowVariablesDropdownFieldItemsProps) => { - const { getIcon } = useIcons(); - const { - searchInputValue, - setSearchInputValue, - handleSelectField, - goBack, - filteredOptions, - currentPath, - } = useVariableDropdown({ - step, - onSelect, - onBack, - }); - - return ( - - - } - > - - - setSearchInputValue(event.target.value)} - /> - - - {filteredOptions.map(([key, option]) => ( - handleSelectField(key)} - text={option.label || key} - hasSubMenu={!option.isLeaf} - LeftIcon={option.icon ? getIcon(option.icon) : undefined} - contextualText={ - option.isLeaf ? option?.value?.toString() : undefined - } - /> - ))} - - - ); -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownAllItems.tsx b/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownStepItems.tsx similarity index 64% rename from packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownAllItems.tsx rename to packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownStepItems.tsx index e0f640fc8b..0b14c17172 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownAllItems.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownStepItems.tsx @@ -2,16 +2,19 @@ import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenu import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; -import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; +import { isRecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/guards/isRecordOutputSchemaV2'; +import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; import { getCurrentSubStepFromPath } from '@/workflow/workflow-variables/utils/getCurrentSubStepFromPath'; import { getStepHeaderLabel } from '@/workflow/workflow-variables/utils/getStepHeaderLabel'; +import { getStepItemIcon } from '@/workflow/workflow-variables/utils/getStepItemIcon'; import { getVariableTemplateFromPath } from '@/workflow/workflow-variables/utils/getVariableTemplateFromPath'; -import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isRecordOutputSchema'; import { useLingui } from '@lingui/react/macro'; +import { isDefined } from 'twenty-shared/utils'; import { IconChevronLeft, OverflowingTextWithTooltip, @@ -20,17 +23,19 @@ import { import { MenuItemSelect } from 'twenty-ui/navigation'; import { useVariableDropdown } from '../hooks/useVariableDropdown'; -type WorkflowVariablesDropdownAllItemsProps = { - step: StepOutputSchema; +type WorkflowVariablesDropdownStepItemsProps = { + step: StepOutputSchemaV2; onSelect: (value: string) => void; onBack: () => void; + shouldDisplayRecordObjects: boolean; }; -export const WorkflowVariablesDropdownAllItems = ({ +export const WorkflowVariablesDropdownStepItems = ({ step, onSelect, onBack, -}: WorkflowVariablesDropdownAllItemsProps) => { + shouldDisplayRecordObjects, +}: WorkflowVariablesDropdownStepItemsProps) => { const { t } = useLingui(); const { getIcon } = useIcons(); const { @@ -46,10 +51,12 @@ export const WorkflowVariablesDropdownAllItems = ({ onBack, }); + const { objectMetadataItems } = useObjectMetadataItems(); + const getDisplayedSubStepObject = () => { const currentSubStep = getCurrentSubStepFromPath(step, currentPath); - if (!isRecordOutputSchema(currentSubStep)) { + if (!isRecordOutputSchemaV2(currentSubStep)) { return; } @@ -59,30 +66,37 @@ export const WorkflowVariablesDropdownAllItems = ({ const handleSelectObject = () => { const currentSubStep = getCurrentSubStepFromPath(step, currentPath); - if (!isRecordOutputSchema(currentSubStep)) { + if (!isRecordOutputSchemaV2(currentSubStep)) { return; } onSelect( getVariableTemplateFromPath({ stepId: step.id, - path: [...currentPath, currentSubStep.object.fieldIdName], + path: [...currentPath, 'id'], }), ); }; const displayedSubStepObject = getDisplayedSubStepObject(); - const shouldDisplaySubStepObject = searchInputValue - ? displayedSubStepObject?.label && - displayedSubStepObject.label + const displayedSubStepObjectMetadata = isDefined(displayedSubStepObject) + ? objectMetadataItems.find( + (item) => item.id === displayedSubStepObject?.objectMetadataId, + ) + : undefined; + + const isObjectFoundThroughSearch = isDefined(searchInputValue) + ? isDefined(displayedSubStepObject?.label) && + displayedSubStepObject?.label .toLowerCase() .includes(searchInputValue.toLowerCase()) : true; - const shouldDisplayObject = - shouldDisplaySubStepObject && displayedSubStepObject?.label; - const nameSingular = displayedSubStepObject?.nameSingular; + const shouldDisplaySubStepObject = + shouldDisplayRecordObjects && isObjectFoundThroughSearch; + + const objectLabel = displayedSubStepObjectMetadata?.labelSingular; return ( @@ -105,22 +119,22 @@ export const WorkflowVariablesDropdownAllItems = ({ /> - {shouldDisplayObject && ( + {shouldDisplaySubStepObject && ( )} - {filteredOptions.length > 0 && shouldDisplayObject && ( + {filteredOptions.length > 0 && shouldDisplaySubStepObject && ( )} {filteredOptions.map(([key, subStep]) => ( @@ -131,7 +145,15 @@ export const WorkflowVariablesDropdownAllItems = ({ onClick={() => handleSelectField(key)} text={subStep.label || key} hasSubMenu={!subStep.isLeaf} - LeftIcon={subStep.icon ? getIcon(subStep.icon) : undefined} + LeftIcon={ + subStep.icon + ? getIcon(subStep.icon) + : getIcon( + getStepItemIcon({ + itemType: subStep.type, + }), + ) + } contextualText={ subStep.isLeaf ? subStep?.value?.toString() : undefined } diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownWorkflowStepItems.tsx b/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownSteps.tsx similarity index 90% rename from packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownWorkflowStepItems.tsx rename to packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownSteps.tsx index b3e02ce601..82f16fde5a 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownWorkflowStepItems.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownSteps.tsx @@ -6,22 +6,22 @@ import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/Dropdow import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; -import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; import { useState } from 'react'; import { IconX, OverflowingTextWithTooltip, useIcons } from 'twenty-ui/display'; import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation'; -type WorkflowVariablesDropdownWorkflowStepItemsProps = { +type WorkflowVariablesDropdownStepsProps = { dropdownId: string; - steps: StepOutputSchema[]; + steps: StepOutputSchemaV2[]; onSelect: (value: string) => void; }; -export const WorkflowVariablesDropdownWorkflowStepItems = ({ +export const WorkflowVariablesDropdownSteps = ({ dropdownId, steps, onSelect, -}: WorkflowVariablesDropdownWorkflowStepItemsProps) => { +}: WorkflowVariablesDropdownStepsProps) => { const { getIcon } = useIcons(); const [searchInputValue, setSearchInputValue] = useState(''); diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep.ts index 9304736e7c..4ff1a5c21b 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep.ts @@ -4,10 +4,7 @@ import { stepsOutputSchemaFamilySelector } from '@/workflow/states/selectors/ste import { type InputSchemaPropertyType } from '@/workflow/types/InputSchema'; import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; import { getPreviousSteps } from '@/workflow/workflow-steps/utils/getWorkflowPreviousSteps'; -import { - type OutputSchema, - type StepOutputSchema, -} from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; import { filterOutputSchema } from '@/workflow/workflow-variables/utils/filterOutputSchema'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; @@ -22,7 +19,7 @@ export const useAvailableVariablesInWorkflowStep = ({ shouldDisplayRecordFields: boolean; shouldDisplayRecordObjects: boolean; fieldTypesToExclude?: InputSchemaPropertyType[]; -}): StepOutputSchema[] => { +}): StepOutputSchemaV2[] => { const workflowSelectedNode = useRecoilComponentValue( workflowSelectedNodeComponentState, ); @@ -33,7 +30,7 @@ export const useAvailableVariablesInWorkflowStep = ({ ? getPreviousSteps(steps, workflowSelectedNode).map((step) => step.id) : []; - const availableStepsOutputSchema: StepOutputSchema[] = useRecoilValue( + const availableStepsOutputSchema: StepOutputSchemaV2[] = useRecoilValue( stepsOutputSchemaFamilySelector({ workflowVersionId: flow.workflowVersionId, stepIds: [TRIGGER_STEP_ID, ...previousStepIds], @@ -47,7 +44,7 @@ export const useAvailableVariablesInWorkflowStep = ({ shouldDisplayRecordObjects, outputSchema: stepOutputSchema.outputSchema, fieldTypesToExclude, - }) as OutputSchema; + }); if (!isDefined(outputSchema) || isEmptyObject(outputSchema)) { return undefined; @@ -57,6 +54,7 @@ export const useAvailableVariablesInWorkflowStep = ({ id: stepOutputSchema.id, name: stepOutputSchema.name, icon: stepOutputSchema.icon, + type: stepOutputSchema.type, outputSchema, }; }) diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useSearchVariable.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useSearchVariable.ts index e1d1cd815d..ad1883dff1 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useSearchVariable.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useSearchVariable.ts @@ -1,18 +1,7 @@ import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow'; import { useWorkflowVersionIdOrThrow } from '@/workflow/hooks/useWorkflowVersionIdOrThrow'; import { stepsOutputSchemaFamilySelector } from '@/workflow/states/selectors/stepsOutputSchemaFamilySelector'; -import type { BaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2'; -import type { CodeOutputSchema } from '@/workflow/workflow-variables/types/CodeOutputSchema'; -import type { FindRecordsOutputSchema } from '@/workflow/workflow-variables/types/FindRecordsOutputSchema'; -import type { FormOutputSchema } from '@/workflow/workflow-variables/types/FormOutputSchema'; -import { type RecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/RecordOutputSchemaV2'; -import { getOutputSchemaType } from '@/workflow/workflow-variables/utils/getOutputSchemaType'; -import { searchVariableThroughBaseOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughBaseOutputSchema'; -import { searchVariableThroughCodeOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughCodeOutputSchema'; -import { searchVariableThroughFindRecordsOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughFindRecordsOutputSchema'; -import { searchVariableThroughFormOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughFormOutputSchema'; -import { searchVariableThroughRecordEventOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughRecordEventOutputSchema'; -import { searchVariableThroughRecordOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughRecordOutputSchema'; +import { searchVariableThroughOutputSchemaV2 } from '@/workflow/workflow-variables/utils/searchVariableThroughOutputSchemaV2'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; @@ -62,59 +51,9 @@ export const useSearchVariable = ({ }; } - const outputSchemaType = getOutputSchemaType(stepType); - - if (outputSchemaType === 'RECORD') { - return searchVariableThroughRecordOutputSchema({ - stepName: stepOutputSchema.name, - recordOutputSchema: stepOutputSchema.outputSchema as RecordOutputSchemaV2, - rawVariableName, - isFullRecord, - }); - } - - if (outputSchemaType === 'DATABASE_EVENT') { - return searchVariableThroughRecordEventOutputSchema({ - stepName: stepOutputSchema.name, - recordOutputSchema: stepOutputSchema.outputSchema as RecordOutputSchemaV2, - rawVariableName, - isFullRecord, - }); - } - - if (outputSchemaType === 'FIND_RECORDS') { - return searchVariableThroughFindRecordsOutputSchema({ - stepName: stepOutputSchema.name, - searchRecordOutputSchema: - stepOutputSchema.outputSchema as unknown as FindRecordsOutputSchema, - rawVariableName, - isFullRecord, - }); - } - - if (outputSchemaType === 'FORM') { - return searchVariableThroughFormOutputSchema({ - stepName: stepOutputSchema.name, - formOutputSchema: - stepOutputSchema.outputSchema as unknown as FormOutputSchema, - rawVariableName, - isFullRecord, - }); - } - - if (outputSchemaType === 'CODE') { - return searchVariableThroughCodeOutputSchema({ - stepName: stepOutputSchema.name, - codeOutputSchema: - stepOutputSchema.outputSchema as unknown as CodeOutputSchema, - rawVariableName, - isFullRecord, - }); - } - - return searchVariableThroughBaseOutputSchema({ - stepName: stepOutputSchema.name, - baseOutputSchema: stepOutputSchema.outputSchema as BaseOutputSchemaV2, + return searchVariableThroughOutputSchemaV2({ + stepOutputSchema, + stepType, rawVariableName, isFullRecord, }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useVariableDropdown.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useVariableDropdown.ts index 4e345ec42c..82ad1b0e03 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useVariableDropdown.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useVariableDropdown.ts @@ -6,23 +6,22 @@ import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState'; import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; +import { type BaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2'; +import { type LinkOutputSchema } from '@/workflow/workflow-variables/types/LinkOutputSchema'; +import { type FieldOutputSchemaV2 } from '@/workflow/workflow-variables/types/RecordOutputSchemaV2'; +import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; import { getVariableTemplateFromPath } from '@/workflow/workflow-variables/utils/getVariableTemplateFromPath'; import { useState } from 'react'; import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { useIcons } from 'twenty-ui/display'; -import { - type BaseOutputSchema, - type LinkOutputSchema, - type StepOutputSchema, -} from '../types/StepOutputSchema'; +import { isBaseOutputSchemaV2 } from '../types/guards/isBaseOutputSchemaV2'; +import { isLinkOutputSchema } from '../types/guards/isLinkOutputSchema'; +import { isRecordOutputSchemaV2 } from '../types/guards/isRecordOutputSchemaV2'; import { getCurrentSubStepFromPath } from '../utils/getCurrentSubStepFromPath'; -import { isBaseOutputSchema } from '../utils/isBaseOutputSchema'; -import { isLinkOutputSchema } from '../utils/isLinkOutputSchema'; -import { isRecordOutputSchema } from '../utils/isRecordOutputSchema'; type UseVariableDropdownProps = { - step: StepOutputSchema; + step: StepOutputSchemaV2; onSelect: (value: string) => void; onBack: () => void; }; @@ -72,9 +71,9 @@ export const useVariableDropdown = ({ if (isLinkOutputSchema(currentSubStep)) { return { link: currentSubStep.link }; - } else if (isRecordOutputSchema(currentSubStep)) { + } else if (isRecordOutputSchemaV2(currentSubStep)) { return currentSubStep.fields; - } else if (isBaseOutputSchema(currentSubStep)) { + } else if (isBaseOutputSchemaV2(currentSubStep)) { return currentSubStep; } }; @@ -83,7 +82,9 @@ export const useVariableDropdown = ({ const currentSubStep = getCurrentSubStepFromPath(step, currentPath); const handleSelectBaseOutputSchema = ( - baseOutputSchema: BaseOutputSchema, + baseOutputSchema: + | BaseOutputSchemaV2 + | Record, ) => { if (!baseOutputSchema[key]?.isLeaf) { setCurrentPath([...currentPath, key]); @@ -136,9 +137,9 @@ export const useVariableDropdown = ({ if (isLinkOutputSchema(currentSubStep)) { handleSelectLinkOutputSchema(currentSubStep); - } else if (isRecordOutputSchema(currentSubStep)) { + } else if (isRecordOutputSchemaV2(currentSubStep)) { handleSelectBaseOutputSchema(currentSubStep.fields); - } else if (isBaseOutputSchema(currentSubStep)) { + } else if (isBaseOutputSchemaV2(currentSubStep)) { handleSelectBaseOutputSchema(currentSubStep); } }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/BaseOutputSchemaV2.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/BaseOutputSchemaV2.ts index 0d362efba3..b62fa8f0ae 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/types/BaseOutputSchemaV2.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/BaseOutputSchemaV2.ts @@ -1,36 +1,15 @@ -type BaseLeaf = { +import { type InputSchemaPropertyType } from '@/workflow/types/InputSchema'; + +export type LeafType = 'string' | 'number' | 'boolean' | 'array' | 'unknown'; + +export type Leaf = { isLeaf: true; + type: LeafType; label: string; + value: any; }; -type LeafString = BaseLeaf & { - type: 'string'; - value: string; -}; - -type LeafNumber = BaseLeaf & { - type: 'number'; - value: number; -}; - -type LeafBoolean = BaseLeaf & { - type: 'boolean'; - value: boolean; -}; - -type LeafArray = BaseLeaf & { - type: 'array'; - value: unknown[]; -}; - -type LeafUnknown = BaseLeaf & { - type: 'unknown'; - value: unknown; -}; - -type Leaf = LeafString | LeafNumber | LeafBoolean | LeafArray | LeafUnknown; - -type Node = { +export type Node = { isLeaf: false; type: 'object' | 'unknown'; label: string; @@ -38,3 +17,12 @@ type Node = { }; export type BaseOutputSchemaV2 = Record; + +export type LeafDeprecated = { + isLeaf: true; + type: InputSchemaPropertyType | undefined; + label: string; + value: any; +}; + +export type BaseOutputSchemaDeprecated = Record; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/CodeOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/CodeOutputSchema.ts index 0727ea7668..349ac579db 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/types/CodeOutputSchema.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/CodeOutputSchema.ts @@ -1,14 +1,4 @@ import { type BaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2'; - -type Link = { - isLeaf: true; - tab?: string; - label?: string; -}; - -export type LinkOutputSchema = { - link: Link; - _outputSchemaType: 'LINK'; -}; +import { type LinkOutputSchema } from '@/workflow/workflow-variables/types/LinkOutputSchema'; export type CodeOutputSchema = LinkOutputSchema | BaseOutputSchemaV2; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/DatabaseEventTriggerOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/DatabaseEventTriggerOutputSchema.ts new file mode 100644 index 0000000000..1a140c5eb6 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/DatabaseEventTriggerOutputSchema.ts @@ -0,0 +1,3 @@ +import { type RecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/RecordOutputSchemaV2'; + +export type DatabaseEventTriggerOutputSchema = RecordOutputSchemaV2; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/LinkOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/LinkOutputSchema.ts new file mode 100644 index 0000000000..0e065fe4f4 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/LinkOutputSchema.ts @@ -0,0 +1,10 @@ +type Link = { + isLeaf: true; + tab?: string; + label?: string; +}; + +export type LinkOutputSchema = { + link: Link; + _outputSchemaType: 'LINK'; +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/OpenStepOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/OpenStepOutputSchema.ts new file mode 100644 index 0000000000..3769545d04 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/OpenStepOutputSchema.ts @@ -0,0 +1,3 @@ +import { type BaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2'; + +export type OpenStepOutputSchema = BaseOutputSchemaV2; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/RecordActionOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/RecordActionOutputSchema.ts new file mode 100644 index 0000000000..dffb7d0832 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/RecordActionOutputSchema.ts @@ -0,0 +1,3 @@ +import { type RecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/RecordOutputSchemaV2'; + +export type RecordActionOutputSchema = RecordOutputSchemaV2; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/StepOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/StepOutputSchema.ts deleted file mode 100644 index 48cc49fe11..0000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/types/StepOutputSchema.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { type InputSchemaPropertyType } from '@/workflow/types/InputSchema'; - -type Leaf = { - isLeaf: true; - type?: InputSchemaPropertyType; - icon?: string; - label?: string; - description?: string; - value: any; - fieldMetadataId?: string; - isCompositeSubField?: boolean; -}; - -type Node = { - isLeaf: false; - type?: InputSchemaPropertyType; - icon?: string; - label?: string; - value: OutputSchema; - description?: string; - fieldMetadataId?: string; - isCompositeSubField?: boolean; -}; - -type Link = { - isLeaf: true; - tab?: string; - icon?: string; - label?: string; -}; - -export type BaseOutputSchema = Record; - -export type FieldOutputSchema = (Leaf | Node) & { - fieldMetadataId: string; -}; - -export type RecordOutputSchema = { - object: { - nameSingular: string; - fieldIdName: string; - objectMetadataId: string; - isRelationField?: boolean; - } & Leaf; - fields: Record; - _outputSchemaType: 'RECORD'; -}; - -export type LinkOutputSchema = { - link: Link; - _outputSchemaType: 'LINK'; -}; - -export type OutputSchema = - | BaseOutputSchema - | RecordOutputSchema - | LinkOutputSchema; - -export type StepOutputSchema = { - id: string; - name: string; - icon?: string; - outputSchema: OutputSchema; -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/StepOutputSchemaV2.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/StepOutputSchemaV2.ts new file mode 100644 index 0000000000..a19e8cb4e5 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/StepOutputSchemaV2.ts @@ -0,0 +1,24 @@ +import { + type WorkflowActionType, + type WorkflowTriggerType, +} from '@/workflow/types/Workflow'; +import { type BaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2'; +import { type CodeOutputSchema } from '@/workflow/workflow-variables/types/CodeOutputSchema'; +import { type FindRecordsOutputSchema } from '@/workflow/workflow-variables/types/FindRecordsOutputSchema'; +import { type FormOutputSchema } from '@/workflow/workflow-variables/types/FormOutputSchema'; +import { type RecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/RecordOutputSchemaV2'; + +export type OutputSchemaV2 = + | BaseOutputSchemaV2 + | CodeOutputSchema + | FindRecordsOutputSchema + | FormOutputSchema + | RecordOutputSchemaV2; + +export type StepOutputSchemaV2 = { + id: string; + name: string; + type: WorkflowTriggerType | WorkflowActionType; + icon?: string; + outputSchema: OutputSchemaV2; +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isBaseOutputSchemaV2.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isBaseOutputSchemaV2.ts new file mode 100644 index 0000000000..dd5b412875 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isBaseOutputSchemaV2.ts @@ -0,0 +1,9 @@ +import { type BaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2'; +import { type OutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; +import { isObject } from '@sniptt/guards'; + +export const isBaseOutputSchemaV2 = ( + outputSchema: OutputSchemaV2, +): outputSchema is BaseOutputSchemaV2 => { + return !(isObject(outputSchema) && '_outputSchemaType' in outputSchema); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isCodeOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isCodeOutputSchema.ts new file mode 100644 index 0000000000..4fb31df929 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isCodeOutputSchema.ts @@ -0,0 +1,13 @@ +import { + type WorkflowActionType, + type WorkflowTriggerType, +} from '@/workflow/types/Workflow'; +import { type CodeOutputSchema } from '@/workflow/workflow-variables/types/CodeOutputSchema'; +import { type OutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; + +export const isCodeOutputSchema = ( + stepType: WorkflowActionType | WorkflowTriggerType, + schema: OutputSchemaV2, +): schema is CodeOutputSchema => { + return stepType === 'CODE'; +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isDatabaseEventTriggerOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isDatabaseEventTriggerOutputSchema.ts new file mode 100644 index 0000000000..95c547186f --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isDatabaseEventTriggerOutputSchema.ts @@ -0,0 +1,13 @@ +import { + type WorkflowActionType, + type WorkflowTriggerType, +} from '@/workflow/types/Workflow'; +import { type DatabaseEventTriggerOutputSchema } from '@/workflow/workflow-variables/types/DatabaseEventTriggerOutputSchema'; +import { type OutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; + +export const isDatabaseEventTriggerOutputSchema = ( + stepType: WorkflowActionType | WorkflowTriggerType, + schema: OutputSchemaV2, +): schema is DatabaseEventTriggerOutputSchema => { + return stepType === 'DATABASE_EVENT'; +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isFindRecordsOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isFindRecordsOutputSchema.ts new file mode 100644 index 0000000000..892e86de58 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isFindRecordsOutputSchema.ts @@ -0,0 +1,13 @@ +import { + type WorkflowActionType, + type WorkflowTriggerType, +} from '@/workflow/types/Workflow'; +import { type FindRecordsOutputSchema } from '@/workflow/workflow-variables/types/FindRecordsOutputSchema'; +import { type OutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; + +export const isFindRecordsOutputSchema = ( + stepType: WorkflowActionType | WorkflowTriggerType, + schema: OutputSchemaV2, +): schema is FindRecordsOutputSchema => { + return stepType === 'FIND_RECORDS'; +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isFormOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isFormOutputSchema.ts new file mode 100644 index 0000000000..0fc7117b84 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isFormOutputSchema.ts @@ -0,0 +1,13 @@ +import { + type WorkflowActionType, + type WorkflowTriggerType, +} from '@/workflow/types/Workflow'; +import { type FormOutputSchema } from '@/workflow/workflow-variables/types/FormOutputSchema'; +import { type OutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; + +export const isFormOutputSchema = ( + stepType: WorkflowActionType | WorkflowTriggerType, + schema: OutputSchemaV2, +): schema is FormOutputSchema => { + return stepType === 'FORM'; +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isLinkOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isLinkOutputSchema.ts new file mode 100644 index 0000000000..573b2fa39d --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isLinkOutputSchema.ts @@ -0,0 +1,13 @@ +import { type LinkOutputSchema } from '@/workflow/workflow-variables/types/LinkOutputSchema'; +import { type OutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; +import { isObject } from '@sniptt/guards'; + +export const isLinkOutputSchema = ( + outputSchema: OutputSchemaV2, +): outputSchema is LinkOutputSchema => { + return ( + isObject(outputSchema) && + '_outputSchemaType' in outputSchema && + outputSchema._outputSchemaType === 'LINK' + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isOpenStepOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isOpenStepOutputSchema.ts new file mode 100644 index 0000000000..7afb0ccd06 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isOpenStepOutputSchema.ts @@ -0,0 +1,25 @@ +import { + type WorkflowActionType, + type WorkflowTriggerType, +} from '@/workflow/types/Workflow'; +import { type OpenStepOutputSchema } from '@/workflow/workflow-variables/types/OpenStepOutputSchema'; +import { type OutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; + +import { isCodeOutputSchema } from './isCodeOutputSchema'; +import { isDatabaseEventTriggerOutputSchema } from './isDatabaseEventTriggerOutputSchema'; +import { isFindRecordsOutputSchema } from './isFindRecordsOutputSchema'; +import { isFormOutputSchema } from './isFormOutputSchema'; +import { isRecordActionOutputSchema } from './isRecordActionOutputSchema'; + +export const isOpenStepOutputSchema = ( + stepType: WorkflowActionType | WorkflowTriggerType, + schema: OutputSchemaV2, +): schema is OpenStepOutputSchema => { + return ( + !isRecordActionOutputSchema(stepType, schema) && + !isDatabaseEventTriggerOutputSchema(stepType, schema) && + !isFindRecordsOutputSchema(stepType, schema) && + !isFormOutputSchema(stepType, schema) && + !isCodeOutputSchema(stepType, schema) + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isRecordActionOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isRecordActionOutputSchema.ts new file mode 100644 index 0000000000..66aed4af17 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isRecordActionOutputSchema.ts @@ -0,0 +1,15 @@ +import { + type WorkflowActionType, + type WorkflowTriggerType, +} from '@/workflow/types/Workflow'; +import { type RecordActionOutputSchema } from '@/workflow/workflow-variables/types/RecordActionOutputSchema'; +import { type OutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; + +const RECORD_ACTION_TYPES = ['CREATE_RECORD', 'UPDATE_RECORD', 'DELETE_RECORD']; + +export const isRecordActionOutputSchema = ( + stepType: WorkflowActionType | WorkflowTriggerType, + schema: OutputSchemaV2, +): schema is RecordActionOutputSchema => { + return RECORD_ACTION_TYPES.includes(stepType); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isRecordOutputSchemaV2.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isRecordOutputSchemaV2.ts new file mode 100644 index 0000000000..22f90fe1cd --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/types/guards/isRecordOutputSchemaV2.ts @@ -0,0 +1,13 @@ +import { type RecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/RecordOutputSchemaV2'; +import { type OutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; +import { isObject } from '@sniptt/guards'; + +export const isRecordOutputSchemaV2 = ( + outputSchema: OutputSchemaV2, +): outputSchema is RecordOutputSchemaV2 => { + return ( + isObject(outputSchema) && + '_outputSchemaType' in outputSchema && + outputSchema._outputSchemaType === 'RECORD' + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/filterOutputSchema.test.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/filterOutputSchema.test.ts index 4b5665f1a2..25e7dc70d0 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/filterOutputSchema.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/filterOutputSchema.test.ts @@ -1,4 +1,5 @@ -import { type OutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { type BaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2'; +import { type RecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/RecordOutputSchemaV2'; import { FieldMetadataType } from 'twenty-shared/types'; import { filterOutputSchema } from '../filterOutputSchema'; @@ -6,19 +7,17 @@ describe('filterOutputSchema', () => { const createRecordSchema = ( nameSingular: string, fields = {}, - ): OutputSchema => ({ + ): RecordOutputSchemaV2 => ({ _outputSchemaType: 'RECORD', object: { - nameSingular, - fieldIdName: 'id', - isLeaf: true, - value: 'Fake value', + label: nameSingular, objectMetadataId: '123', + isRelationField: false, }, fields, }); - const createBaseSchema = (fields = {}): OutputSchema => ({ + const createBaseSchema = (fields = {}): BaseOutputSchemaV2 => ({ ...fields, }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/getCurrentSubStepFromPath.test.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/getCurrentSubStepFromPath.test.ts index 44e6b1fcec..a6d3b685a0 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/getCurrentSubStepFromPath.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/getCurrentSubStepFromPath.test.ts @@ -1,22 +1,20 @@ -import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; import { getCurrentSubStepFromPath } from '@/workflow/workflow-variables/utils/getCurrentSubStepFromPath'; +import { FieldMetadataType } from 'twenty-shared/types'; const mockStep = { id: 'step-1', name: 'Step 1', + type: 'CREATE_RECORD', outputSchema: { company: { isLeaf: false, - icon: 'company', label: 'Company', value: { object: { - nameSingular: 'company', - fieldIdName: 'id', label: 'Company', - value: 'John', - isLeaf: true, objectMetadataId: '123', + isRelationField: false, }, fields: { name: { @@ -24,13 +22,15 @@ const mockStep = { value: 'Twenty', isLeaf: true, fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', + type: FieldMetadataType.TEXT, + isCompositeSubField: false, }, }, _outputSchemaType: 'RECORD', }, }, }, -} satisfies StepOutputSchema; +} satisfies StepOutputSchemaV2; describe('getCurrentSubStepFromPath', () => { it('should return the current sub step from the path', () => { diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/getStepHeaderLabel.test.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/getStepHeaderLabel.test.ts index c8d56dfe38..6c5ac2efa3 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/getStepHeaderLabel.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/getStepHeaderLabel.test.ts @@ -1,22 +1,20 @@ -import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; import { getStepHeaderLabel } from '@/workflow/workflow-variables/utils/getStepHeaderLabel'; +import { FieldMetadataType } from 'twenty-shared/types'; const mockStep = { id: 'step-1', name: 'Step 1', + type: 'CREATE_RECORD', outputSchema: { company: { isLeaf: false, - icon: 'company', label: 'Company', value: { object: { - nameSingular: 'company', - fieldIdName: 'id', label: 'Company', - value: 'John', - isLeaf: true, objectMetadataId: '123', + isRelationField: false, }, fields: { name: { @@ -24,35 +22,46 @@ const mockStep = { value: 'Twenty', isLeaf: true, fieldMetadataId: '123e4567-e89b-12d3-a456-426614174001', + type: FieldMetadataType.TEXT, + isCompositeSubField: false, }, address: { isLeaf: false, label: 'Address', fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', + type: FieldMetadataType.ADDRESS, value: { street: { label: 'Street', value: '123 Main St', isLeaf: true, fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', + type: FieldMetadataType.TEXT, + isCompositeSubField: true, }, city: { label: 'City', value: 'New York', isLeaf: true, fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', + type: FieldMetadataType.TEXT, + isCompositeSubField: true, }, state: { label: 'State', value: 'NY', isLeaf: true, fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', + type: FieldMetadataType.TEXT, + isCompositeSubField: true, }, zip: { label: 'Zip', value: '10001', isLeaf: true, fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', + type: FieldMetadataType.TEXT, + isCompositeSubField: true, }, }, }, @@ -61,7 +70,7 @@ const mockStep = { }, }, }, -} satisfies StepOutputSchema; +} satisfies StepOutputSchemaV2; describe('getStepHeaderLabel', () => { it('should return the step name when the path is empty', () => { diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/isBaseOutputSchema.test.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/isBaseOutputSchema.test.ts deleted file mode 100644 index e3d5672f6b..0000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/isBaseOutputSchema.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { isBaseOutputSchema } from '@/workflow/workflow-variables/utils/isBaseOutputSchema'; - -describe('isBaseOutputSchema', () => { - // This looks weird, but that is the way this method was built - it('should return false for base output schema', () => { - expect( - isBaseOutputSchema({ _outputSchemaType: 'LINK', link: { isLeaf: true } }), - ).toBe(false); - }); - - it('should return true in other cases', () => { - expect(isBaseOutputSchema({})).toBe(true); - }); -}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/searchVariableThroughOutputSchema.test.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/searchVariableThroughOutputSchema.test.ts deleted file mode 100644 index 590776f251..0000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/searchVariableThroughOutputSchema.test.ts +++ /dev/null @@ -1,373 +0,0 @@ -import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; -import { searchVariableThroughOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughOutputSchema'; -import { FieldMetadataType } from '~/generated-metadata/graphql'; - -describe('searchVariableThroughOutputSchema', () => { - describe('step tests', () => { - const mockStep = { - id: 'step-1', - name: 'Step 1', - outputSchema: { - company: { - isLeaf: false, - icon: 'company', - label: 'Company', - value: { - object: { - nameSingular: 'company', - fieldIdName: 'id', - label: 'Company', - value: 'John', - isLeaf: true, - objectMetadataId: '123', - }, - fields: { - name: { - label: 'Name', - value: 'Twenty', - isLeaf: true, - fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', - }, - address: { - label: 'Address', - value: '123 Main St', - isLeaf: true, - fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', - }, - }, - _outputSchemaType: 'RECORD', - }, - }, - person: { - isLeaf: false, - icon: 'person', - label: 'Person', - value: { - object: { - nameSingular: 'person', - fieldIdName: 'id', - label: 'Person', - value: 'Jane', - isLeaf: true, - objectMetadataId: '123', - }, - fields: { - firstName: { - label: 'First Name', - value: 'Jane', - isLeaf: true, - fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', - }, - lastName: { - label: 'Last Name', - value: 'Doe', - isLeaf: true, - fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', - }, - email: { - label: 'Email', - value: 'jane@example.com', - isLeaf: true, - fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', - }, - }, - _outputSchemaType: 'RECORD', - }, - }, - simpleData: { - isLeaf: true, - label: 'Simple Data', - value: 'Simple value', - }, - nestedData: { - isLeaf: false, - label: 'Nested Data', - value: { - field1: { label: 'Field 1', value: 'Value 1', isLeaf: true }, - field2: { label: 'Field 2', value: 'Value 2', isLeaf: true }, - }, - }, - }, - } satisfies StepOutputSchema; - it('should not break with wrong path', () => { - const result = searchVariableThroughOutputSchema({ - stepOutputSchema: mockStep, - rawVariableName: '{{step-1.wrong.wrong.wrong}}', - isFullRecord: false, - }); - - expect(result).toEqual({ - variableLabel: undefined, - variablePathLabel: 'Step 1 > undefined', - variableType: 'unknown', - }); - }); - - it('should find a company field variable', () => { - const result = searchVariableThroughOutputSchema({ - stepOutputSchema: mockStep, - rawVariableName: '{{step-1.company.name}}', - isFullRecord: false, - }); - - expect(result).toEqual({ - compositeFieldSubFieldName: undefined, - fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', - variableLabel: 'Name', - variablePathLabel: 'Step 1 > Company > Name', - variableType: 'unknown', - }); - }); - - it('should find a person field variable', () => { - const result = searchVariableThroughOutputSchema({ - stepOutputSchema: mockStep, - rawVariableName: '{{step-1.person.email}}', - isFullRecord: false, - }); - - expect(result).toEqual({ - compositeFieldSubFieldName: undefined, - fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', - variableLabel: 'Email', - variablePathLabel: 'Step 1 > Person > Email', - variableType: 'unknown', - }); - }); - - it('should find a company object variable', () => { - const result = searchVariableThroughOutputSchema({ - stepOutputSchema: mockStep, - rawVariableName: '{{step-1.company.id}}', - isFullRecord: true, - }); - - expect(result).toEqual({ - variableLabel: 'Company', - variablePathLabel: 'Step 1 > Company > Company', - variableType: 'unknown', - }); - }); - - it('should find a person object variable', () => { - const result = searchVariableThroughOutputSchema({ - stepOutputSchema: mockStep, - rawVariableName: '{{step-1.person.id}}', - isFullRecord: true, - }); - - expect(result).toEqual({ - variableLabel: 'Person', - variablePathLabel: 'Step 1 > Person > Person', - variableType: 'unknown', - }); - }); - - it('should handle simple data fields', () => { - const result = searchVariableThroughOutputSchema({ - stepOutputSchema: mockStep, - rawVariableName: '{{step-1.simpleData}}', - isFullRecord: false, - }); - - expect(result).toEqual({ - variableLabel: 'Simple Data', - variablePathLabel: 'Step 1 > Simple Data', - variableType: 'unknown', - }); - }); - - it('should handle nested data fields', () => { - const result = searchVariableThroughOutputSchema({ - stepOutputSchema: mockStep, - rawVariableName: '{{step-1.nestedData.field1}}', - isFullRecord: false, - }); - - expect(result).toEqual({ - variableLabel: 'Field 1', - variablePathLabel: 'Step 1 > Nested Data > Field 1', - variableType: 'unknown', - }); - }); - - it('should handle invalid variable names', () => { - const result = searchVariableThroughOutputSchema({ - stepOutputSchema: mockStep, - rawVariableName: '{{invalid}}', - isFullRecord: false, - }); - - expect(result).toEqual({ - variableLabel: undefined, - variablePathLabel: 'Step 1 > undefined', - variableType: 'unknown', - }); - }); - - it('should handle non-existent paths', () => { - const result = searchVariableThroughOutputSchema({ - stepOutputSchema: mockStep, - rawVariableName: '{{step-1.nonExistent.field}}', - isFullRecord: false, - }); - - expect(result).toEqual({ - variableLabel: undefined, - variablePathLabel: 'Step 1 > undefined', - variableType: 'unknown', - }); - }); - - it('should handle the case where the path has dots in field names', () => { - const mockStepWithDotInField = { - id: 'step-1', - name: 'Step 1', - outputSchema: { - 'complex.field': { - isLeaf: false, - label: 'Complex Field', - value: { - field1: { label: 'Field 1', value: 'Value 1', isLeaf: true }, - field2: { label: 'Field 2', value: 'Value 2', isLeaf: true }, - }, - }, - }, - } satisfies StepOutputSchema; - - const result = searchVariableThroughOutputSchema({ - stepOutputSchema: mockStepWithDotInField, - rawVariableName: '{{step-1.complex.field.field1}}', - isFullRecord: false, - }); - - expect(result).toEqual({ - variableLabel: 'Field 1', - variablePathLabel: 'Step 1 > Complex Field > Field 1', - variableType: 'unknown', - }); - }); - }); - - describe('trigger tests', () => { - const mockTrigger = { - id: 'trigger', - name: 'Record is Created', - icon: 'IconPlaylistAdd', - outputSchema: { - fields: { - 'properties.after.id': { - icon: 'Icon123', - type: FieldMetadataType.UUID, - label: 'Id', - value: '123e4567-e89b-12d3-a456-426614174000', - isLeaf: true, - fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', - }, - 'properties.after.name': { - icon: 'IconBuildingSkyscraper', - type: FieldMetadataType.TEXT, - label: 'Name', - value: 'My text', - isLeaf: true, - fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', - }, - 'properties.after.annualRecurringRevenue': { - icon: 'IconMoneybag', - label: 'ARR', - fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', - value: { - amountMicros: { - type: FieldMetadataType.NUMERIC, - label: ' Amount Micros', - value: null, - isLeaf: true, - fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', - isCompositeSubField: true, - }, - currencyCode: { - type: FieldMetadataType.TEXT, - label: ' Currency Code', - value: 'My text', - isLeaf: true, - fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', - isCompositeSubField: true, - }, - }, - isLeaf: false, - }, - }, - object: { - icon: 'IconBuildingSkyscraper', - label: 'Company', - value: 'A company', - isLeaf: true, - fieldIdName: 'properties.after.id', - nameSingular: 'company', - objectMetadataId: '123', - }, - _outputSchemaType: 'RECORD', - }, - } satisfies StepOutputSchema; - it('should find a simple field from trigger', () => { - const result = searchVariableThroughOutputSchema({ - stepOutputSchema: mockTrigger, - rawVariableName: '{{trigger.properties.after.name}}', - isFullRecord: false, - }); - - expect(result).toEqual({ - compositeFieldSubFieldName: undefined, - fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', - variableLabel: 'Name', - variablePathLabel: 'Record is Created > Name', - variableType: FieldMetadataType.TEXT, - }); - }); - - it('should find a nested field from trigger', () => { - const result = searchVariableThroughOutputSchema({ - stepOutputSchema: mockTrigger, - rawVariableName: - '{{trigger.properties.after.annualRecurringRevenue.amountMicros}}', - isFullRecord: false, - }); - - expect(result).toEqual({ - compositeFieldSubFieldName: 'amountMicros', - fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', - variableLabel: ' Amount Micros', - variablePathLabel: 'Record is Created > ARR > Amount Micros', - variableType: FieldMetadataType.NUMERIC, - }); - }); - - it('should find the object field from trigger', () => { - const result = searchVariableThroughOutputSchema({ - stepOutputSchema: mockTrigger, - rawVariableName: '{{trigger.object}}', - isFullRecord: true, - }); - - expect(result).toEqual({ - variableLabel: 'Company', - variablePathLabel: 'Record is Created > Company', - variableType: 'unknown', - }); - }); - - it('should handle invalid trigger field path', () => { - const result = searchVariableThroughOutputSchema({ - stepOutputSchema: mockTrigger, - rawVariableName: '{{trigger.nonExistent}}', - isFullRecord: false, - }); - - expect(result).toEqual({ - variableLabel: undefined, - variablePathLabel: 'Record is Created > undefined', - variableType: 'unknown', - }); - }); - }); -}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/filterOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/filterOutputSchema.ts index 8570bdaed9..4510724cca 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/filterOutputSchema.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/filterOutputSchema.ts @@ -1,14 +1,17 @@ import { type InputSchemaPropertyType } from '@/workflow/types/InputSchema'; import { - type BaseOutputSchema, - type FieldOutputSchema, - type OutputSchema, - type RecordOutputSchema, -} from '@/workflow/workflow-variables/types/StepOutputSchema'; -import { isBaseOutputSchema } from '@/workflow/workflow-variables/utils/isBaseOutputSchema'; + type BaseOutputSchemaV2, + type Node, +} from '@/workflow/workflow-variables/types/BaseOutputSchemaV2'; +import { + type FieldOutputSchemaV2, + type RecordOutputSchemaV2, +} from '@/workflow/workflow-variables/types/RecordOutputSchemaV2'; +import { type OutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; +import { isBaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/guards/isBaseOutputSchemaV2'; +import { isLinkOutputSchema } from '@/workflow/workflow-variables/types/guards/isLinkOutputSchema'; +import { isRecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/guards/isRecordOutputSchemaV2'; import { isFieldTypeCompatibleWithRecordId } from '@/workflow/workflow-variables/utils/isFieldTypeCompatibleWithRecordId'; -import { isLinkOutputSchema } from '@/workflow/workflow-variables/utils/isLinkOutputSchema'; -import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isRecordOutputSchema'; import { isDefined } from 'twenty-shared/utils'; const isValidRecordOutputSchema = ({ @@ -18,7 +21,7 @@ const isValidRecordOutputSchema = ({ }: { shouldDisplayRecordFields: boolean; shouldDisplayRecordObjects: boolean; - outputSchema: RecordOutputSchema; + outputSchema: RecordOutputSchemaV2; }): boolean => { if (shouldDisplayRecordObjects && !shouldDisplayRecordFields) { return isDefined(outputSchema.object); @@ -32,11 +35,11 @@ const filterRecordOutputSchema = ({ shouldDisplayRecordFields, shouldDisplayRecordObjects, }: { - outputSchema: RecordOutputSchema; + outputSchema: RecordOutputSchemaV2; shouldDisplayRecordFields: boolean; shouldDisplayRecordObjects: boolean; -}): RecordOutputSchema | undefined => { - const filteredFields: Record = {}; +}): RecordOutputSchemaV2 | undefined => { + const filteredFields: Record = {}; let hasValidFields = false; for (const key in outputSchema.fields) { @@ -60,7 +63,7 @@ const filterRecordOutputSchema = ({ filteredFields[key] = { ...field, value: validSubSchema, - }; + } as FieldOutputSchemaV2; hasValidFields = true; } } @@ -80,7 +83,7 @@ const filterRecordOutputSchema = ({ return { _outputSchemaType: 'RECORD', fields: filteredFields, - } as RecordOutputSchema; + } as RecordOutputSchemaV2; } return undefined; @@ -91,11 +94,11 @@ const filterBaseOutputSchema = ({ shouldDisplayRecordFields, shouldDisplayRecordObjects, }: { - outputSchema: BaseOutputSchema; + outputSchema: BaseOutputSchemaV2; shouldDisplayRecordFields: boolean; shouldDisplayRecordObjects: boolean; -}): BaseOutputSchema | undefined => { - const filteredSchema: BaseOutputSchema = {}; +}): BaseOutputSchemaV2 | undefined => { + const filteredSchema: BaseOutputSchemaV2 = {}; let hasValidFields = false; for (const key in outputSchema) { @@ -118,7 +121,7 @@ const filterBaseOutputSchema = ({ filteredSchema[key] = { ...field, value: validSubSchema, - }; + } as Node; hasValidFields = true; } } @@ -134,10 +137,10 @@ const filterRecordOutputSchemaFieldsByType = ({ outputSchema, fieldTypesToExclude, }: { - outputSchema: RecordOutputSchema; + outputSchema: RecordOutputSchemaV2; fieldTypesToExclude: InputSchemaPropertyType[]; -}): RecordOutputSchema => { - const filteredFields: Record = {}; +}): RecordOutputSchemaV2 => { + const filteredFields: Record = {}; for (const key in outputSchema.fields) { const field = outputSchema.fields[key]; @@ -164,15 +167,18 @@ export const filterOutputSchema = ({ }: { shouldDisplayRecordFields: boolean; shouldDisplayRecordObjects: boolean; - outputSchema?: OutputSchema; + outputSchema?: OutputSchemaV2; fieldTypesToExclude?: InputSchemaPropertyType[]; -}): OutputSchema | undefined => { +}): OutputSchemaV2 | undefined => { if (!isDefined(outputSchema)) { return undefined; } if (!shouldDisplayRecordObjects || shouldDisplayRecordFields) { - if (isRecordOutputSchema(outputSchema) && isDefined(fieldTypesToExclude)) { + if ( + isRecordOutputSchemaV2(outputSchema) && + isDefined(fieldTypesToExclude) + ) { return filterRecordOutputSchemaFieldsByType({ outputSchema, fieldTypesToExclude, @@ -184,13 +190,13 @@ export const filterOutputSchema = ({ if (isLinkOutputSchema(outputSchema)) { return outputSchema; - } else if (isRecordOutputSchema(outputSchema)) { + } else if (isRecordOutputSchemaV2(outputSchema)) { return filterRecordOutputSchema({ outputSchema, shouldDisplayRecordFields, shouldDisplayRecordObjects, }); - } else if (isBaseOutputSchema(outputSchema)) { + } else if (isBaseOutputSchemaV2(outputSchema)) { return filterBaseOutputSchema({ outputSchema, shouldDisplayRecordFields, diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getCurrentSubStepFromPath.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getCurrentSubStepFromPath.ts index 9331a26e9e..dbb3d20f30 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getCurrentSubStepFromPath.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getCurrentSubStepFromPath.ts @@ -1,20 +1,20 @@ import { - type OutputSchema, - type StepOutputSchema, -} from '@/workflow/workflow-variables/types/StepOutputSchema'; -import { isBaseOutputSchema } from '@/workflow/workflow-variables/utils/isBaseOutputSchema'; -import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isRecordOutputSchema'; + type OutputSchemaV2, + type StepOutputSchemaV2, +} from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; +import { isBaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/guards/isBaseOutputSchemaV2'; +import { isRecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/guards/isRecordOutputSchemaV2'; export const getCurrentSubStepFromPath = ( - step: StepOutputSchema, + step: StepOutputSchemaV2, path: string[], -): OutputSchema => { +): OutputSchemaV2 => { let currentSubStep = step.outputSchema; for (const key of path) { - if (isRecordOutputSchema(currentSubStep)) { + if (isRecordOutputSchemaV2(currentSubStep)) { currentSubStep = currentSubStep.fields[key]?.value; - } else if (isBaseOutputSchema(currentSubStep)) { + } else if (isBaseOutputSchemaV2(currentSubStep)) { currentSubStep = currentSubStep[key]?.value; } } diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getOutputSchemaType.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getOutputSchemaType.ts deleted file mode 100644 index c25b226830..0000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getOutputSchemaType.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - type WorkflowActionType, - type WorkflowTriggerType, -} from '@/workflow/types/Workflow'; - -export const getOutputSchemaType = ( - stepType: WorkflowActionType | WorkflowTriggerType, -): 'RECORD' | 'DATABASE_EVENT' | 'FIND_RECORDS' | 'FORM' | 'CODE' | 'BASE' => { - switch (stepType) { - case 'CREATE_RECORD': - case 'UPDATE_RECORD': - case 'DELETE_RECORD': - case 'MANUAL': - return 'RECORD'; - case 'DATABASE_EVENT': - return 'DATABASE_EVENT'; - case 'FIND_RECORDS': - return 'FIND_RECORDS'; - case 'FORM': - return 'FORM'; - case 'CODE': - return 'CODE'; - default: - return 'BASE'; - } -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getStepHeaderLabel.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getStepHeaderLabel.ts index d03af20ea5..b74e47102f 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getStepHeaderLabel.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getStepHeaderLabel.ts @@ -1,11 +1,11 @@ -import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema'; +import { isBaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/guards/isBaseOutputSchemaV2'; +import { isRecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/guards/isRecordOutputSchemaV2'; +import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; import { getCurrentSubStepFromPath } from '@/workflow/workflow-variables/utils/getCurrentSubStepFromPath'; -import { isBaseOutputSchema } from '@/workflow/workflow-variables/utils/isBaseOutputSchema'; -import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isRecordOutputSchema'; import { isDefined } from 'twenty-shared/utils'; export const getStepHeaderLabel = ( - step: StepOutputSchema, + step: StepOutputSchemaV2, currentPath: string[], ) => { if (currentPath.length === 0) { @@ -23,14 +23,14 @@ export const getStepHeaderLabel = ( } if ( - isRecordOutputSchema(previousSubStep) && + isRecordOutputSchemaV2(previousSubStep) && isDefined(previousSubStep.fields[subStepName]?.label) ) { return previousSubStep.fields[subStepName].label; } if ( - isBaseOutputSchema(previousSubStep) && + isBaseOutputSchemaV2(previousSubStep) && isDefined(previousSubStep[subStepName]?.label) ) { return previousSubStep[subStepName].label; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getStepItemIcon.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getStepItemIcon.ts new file mode 100644 index 0000000000..d5be1e397f --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getStepItemIcon.ts @@ -0,0 +1,38 @@ +import { type InputSchemaPropertyType } from '@/workflow/types/InputSchema'; +import { FieldMetadataType } from 'twenty-shared/types'; + +export const getStepItemIcon = ({ + itemType, +}: { + itemType: InputSchemaPropertyType; +}) => { + if (itemType === 'string' || itemType === FieldMetadataType.TEXT) { + return 'IconAbc'; + } + + if ( + itemType === 'number' || + itemType === FieldMetadataType.NUMERIC || + itemType === FieldMetadataType.NUMBER + ) { + return 'Icon123'; + } + + if (itemType === 'boolean' || itemType === FieldMetadataType.BOOLEAN) { + return 'IconCheckbox'; + } + + if (itemType === 'array' || itemType === FieldMetadataType.ARRAY) { + return 'IconBrackets'; + } + + if (itemType === 'object') { + return 'IconBraces'; + } + + if (itemType === 'unknown') { + return 'IconQuestionMark'; + } + + return undefined; +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isBaseOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isBaseOutputSchema.ts deleted file mode 100644 index 135c82d860..0000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isBaseOutputSchema.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - type BaseOutputSchema, - type OutputSchema, -} from '@/workflow/workflow-variables/types/StepOutputSchema'; - -export const isBaseOutputSchema = ( - outputSchema: OutputSchema, -): outputSchema is BaseOutputSchema => { - return !outputSchema._outputSchemaType; -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isLinkOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isLinkOutputSchema.ts deleted file mode 100644 index 9ac62fb112..0000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isLinkOutputSchema.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - type LinkOutputSchema, - type OutputSchema, -} from '@/workflow/workflow-variables/types/StepOutputSchema'; - -export const isLinkOutputSchema = ( - outputSchema: OutputSchema, -): outputSchema is LinkOutputSchema => { - return outputSchema._outputSchemaType === 'LINK'; -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isRecordOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isRecordOutputSchema.ts deleted file mode 100644 index 46fdfbce58..0000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isRecordOutputSchema.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - type OutputSchema, - type RecordOutputSchema, -} from '@/workflow/workflow-variables/types/StepOutputSchema'; - -export const isRecordOutputSchema = ( - outputSchema: OutputSchema, -): outputSchema is RecordOutputSchema => { - return outputSchema._outputSchemaType === 'RECORD'; -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isRecordOutputSchemaV2.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isRecordOutputSchemaV2.ts deleted file mode 100644 index 54b3ab9533..0000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isRecordOutputSchemaV2.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - type RecordFieldLeaf, - type RecordOutputSchemaV2, -} from '@/workflow/workflow-variables/types/RecordOutputSchemaV2'; - -export const isRecordOutputSchemaV2 = ( - outputSchema: RecordOutputSchemaV2 | Record, -): outputSchema is RecordOutputSchemaV2 => { - return outputSchema._outputSchemaType === 'RECORD'; -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughOutputSchema.ts deleted file mode 100644 index 5df8d9c021..0000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughOutputSchema.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { CAPTURE_ALL_VARIABLE_TAG_INNER_REGEX } from '@/workflow/workflow-variables/constants/CaptureAllVariableTagInnerRegex'; -import { type VariableSearchResult } from '@/workflow/workflow-variables/hooks/useSearchVariable'; -import { - type OutputSchema, - type StepOutputSchema, -} from '@/workflow/workflow-variables/types/StepOutputSchema'; -import { isBaseOutputSchema } from '@/workflow/workflow-variables/utils/isBaseOutputSchema'; -import { isLinkOutputSchema } from '@/workflow/workflow-variables/utils/isLinkOutputSchema'; -import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isRecordOutputSchema'; -import { isDefined } from 'twenty-shared/utils'; - -const getDisplayedSubStepObjectLabel = (outputSchema: OutputSchema) => { - if (!isRecordOutputSchema(outputSchema)) { - return; - } - - return outputSchema.object.label; -}; - -const getDisplayedSubStepFieldLabel = ( - key: string, - outputSchema: OutputSchema, -) => { - if (isBaseOutputSchema(outputSchema)) { - return outputSchema[key]?.label; - } - - if (isRecordOutputSchema(outputSchema)) { - return outputSchema.fields[key]?.label; - } - - return; -}; - -const getVariableType = (key: string, outputSchema: OutputSchema): string => { - if (isRecordOutputSchema(outputSchema)) { - return outputSchema.fields[key]?.type ?? 'unknown'; - } - - if (isLinkOutputSchema(outputSchema)) { - return 'unknown'; - } - - return outputSchema[key]?.type ?? 'unknown'; -}; - -const getFieldMetadataId = ( - key: string, - outputSchema: OutputSchema, -): string | undefined => { - if (isRecordOutputSchema(outputSchema)) { - return outputSchema.fields[key]?.fieldMetadataId; - } - - return undefined; -}; - -const isCompositeSubField = ( - key: string, - outputSchema: OutputSchema, -): boolean => { - if (isBaseOutputSchema(outputSchema) && outputSchema[key]?.isLeaf) { - return outputSchema[key]?.isCompositeSubField ?? false; - } - - return false; -}; - -const searchCurrentStepOutputSchema = ({ - stepOutputSchema, - path, - isFullRecord, - selectedField, -}: { - stepOutputSchema: StepOutputSchema; - path: string[]; - isFullRecord: boolean; - selectedField: string; -}): VariableSearchResult => { - let currentSubStep = stepOutputSchema.outputSchema; - let nextKeyIndex = 0; - let nextKey = path[nextKeyIndex]; - let variablePathLabel = stepOutputSchema.name; - let isSelectedFieldInNextKey = false; - let parentFieldMetadataId: string | undefined; - - const handleFieldNotFound = () => { - if (nextKeyIndex + 1 < path.length) { - // If the key is not found in the step, we handle the case where the path has been wrongly split - // For example, if there is a dot in the field name - nextKey = `${nextKey}.${path[nextKeyIndex + 1]}`; - } else { - // If we already reached the end of the path, we add the selected field to the next key - nextKey = `${nextKey}.${selectedField}`; - isSelectedFieldInNextKey = true; - } - }; - - while (nextKeyIndex < path.length) { - if (!isDefined(currentSubStep)) { - break; - } - - if (isRecordOutputSchema(currentSubStep)) { - const currentField = currentSubStep.fields[nextKey]; - if (isDefined(currentField)) { - currentSubStep = currentField.value; - nextKey = path[nextKeyIndex + 1]; - variablePathLabel = `${variablePathLabel} > ${currentField.label}`; - parentFieldMetadataId = currentField.fieldMetadataId; - } else { - handleFieldNotFound(); - } - } else if (isBaseOutputSchema(currentSubStep)) { - if (isDefined(currentSubStep[nextKey])) { - const currentField = currentSubStep[nextKey]; - currentSubStep = currentField.value; - nextKey = path[nextKeyIndex + 1]; - variablePathLabel = `${variablePathLabel} > ${currentField.label}`; - parentFieldMetadataId = currentField.fieldMetadataId; - } else { - handleFieldNotFound(); - } - } - nextKeyIndex++; - } - - if (!isDefined(currentSubStep)) { - return { - variableLabel: undefined, - variablePathLabel: undefined, - variableType: undefined, - }; - } - - const variableName = isSelectedFieldInNextKey ? nextKey : selectedField; - const variableLabel = - isFullRecord && isRecordOutputSchema(currentSubStep) - ? getDisplayedSubStepObjectLabel(currentSubStep) - : getDisplayedSubStepFieldLabel(variableName, currentSubStep); - - return { - variableLabel, - variablePathLabel: `${variablePathLabel} > ${variableLabel}`, - variableType: getVariableType(variableName, currentSubStep), - fieldMetadataId: - getFieldMetadataId(variableName, currentSubStep) ?? parentFieldMetadataId, - compositeFieldSubFieldName: isCompositeSubField( - variableName, - currentSubStep, - ) - ? variableName - : undefined, - }; -}; - -export const searchVariableThroughOutputSchema = ({ - stepOutputSchema, - rawVariableName, - isFullRecord = false, -}: { - stepOutputSchema: StepOutputSchema; - rawVariableName: string; - isFullRecord?: boolean; -}): VariableSearchResult => { - if (!isDefined(stepOutputSchema)) { - return { - variableLabel: undefined, - variablePathLabel: undefined, - }; - } - - const variableWithoutBrackets = rawVariableName.replace( - CAPTURE_ALL_VARIABLE_TAG_INNER_REGEX, - (_, variableName) => { - return variableName; - }, - ); - - const parts = variableWithoutBrackets.split('.'); - - const stepId = parts.at(0); - const selectedField = parts.at(-1); - // path is the remaining parts of the variable name - const path = parts.slice(1, -1); - - if (!isDefined(stepId) || !isDefined(selectedField)) { - return { - variableLabel: undefined, - variablePathLabel: undefined, - }; - } - - return searchCurrentStepOutputSchema({ - stepOutputSchema, - path, - isFullRecord, - selectedField, - }); -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughOutputSchemaV2.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughOutputSchemaV2.ts new file mode 100644 index 0000000000..2eca82a301 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughOutputSchemaV2.ts @@ -0,0 +1,83 @@ +import { + type WorkflowActionType, + type WorkflowTriggerType, +} from '@/workflow/types/Workflow'; +import { isCodeOutputSchema } from '@/workflow/workflow-variables/types/guards/isCodeOutputSchema'; +import { isDatabaseEventTriggerOutputSchema } from '@/workflow/workflow-variables/types/guards/isDatabaseEventTriggerOutputSchema'; +import { isFindRecordsOutputSchema } from '@/workflow/workflow-variables/types/guards/isFindRecordsOutputSchema'; +import { isFormOutputSchema } from '@/workflow/workflow-variables/types/guards/isFormOutputSchema'; +import { isRecordActionOutputSchema } from '@/workflow/workflow-variables/types/guards/isRecordActionOutputSchema'; +import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2'; + +import { searchVariableThroughBaseOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughBaseOutputSchema'; +import { searchVariableThroughCodeOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughCodeOutputSchema'; +import { searchVariableThroughFindRecordsOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughFindRecordsOutputSchema'; +import { searchVariableThroughFormOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughFormOutputSchema'; +import { searchVariableThroughRecordEventOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughRecordEventOutputSchema'; +import { searchVariableThroughRecordOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughRecordOutputSchema'; + +export const searchVariableThroughOutputSchemaV2 = ({ + stepOutputSchema, + stepType, + rawVariableName, + isFullRecord, +}: { + stepOutputSchema: StepOutputSchemaV2; + stepType: WorkflowTriggerType | WorkflowActionType; + rawVariableName: string; + isFullRecord: boolean; +}) => { + if (isRecordActionOutputSchema(stepType, stepOutputSchema.outputSchema)) { + return searchVariableThroughRecordOutputSchema({ + stepName: stepOutputSchema.name, + recordOutputSchema: stepOutputSchema.outputSchema, + rawVariableName, + isFullRecord, + }); + } + + if ( + isDatabaseEventTriggerOutputSchema(stepType, stepOutputSchema.outputSchema) + ) { + return searchVariableThroughRecordEventOutputSchema({ + stepName: stepOutputSchema.name, + recordOutputSchema: stepOutputSchema.outputSchema, + rawVariableName, + isFullRecord, + }); + } + + if (isFindRecordsOutputSchema(stepType, stepOutputSchema.outputSchema)) { + return searchVariableThroughFindRecordsOutputSchema({ + stepName: stepOutputSchema.name, + searchRecordOutputSchema: stepOutputSchema.outputSchema, + rawVariableName, + isFullRecord, + }); + } + + if (isFormOutputSchema(stepType, stepOutputSchema.outputSchema)) { + return searchVariableThroughFormOutputSchema({ + stepName: stepOutputSchema.name, + formOutputSchema: stepOutputSchema.outputSchema, + rawVariableName, + isFullRecord, + }); + } + + if (isCodeOutputSchema(stepType, stepOutputSchema.outputSchema)) { + return searchVariableThroughCodeOutputSchema({ + stepName: stepOutputSchema.name, + codeOutputSchema: stepOutputSchema.outputSchema, + rawVariableName, + isFullRecord, + }); + } + + return searchVariableThroughBaseOutputSchema({ + stepName: stepOutputSchema.name, + baseOutputSchema: stepOutputSchema.outputSchema, + rawVariableName, + isFullRecord, + }); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughRecordOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughRecordOutputSchema.ts index 0a0c98f809..d053f43715 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughRecordOutputSchema.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughRecordOutputSchema.ts @@ -5,7 +5,7 @@ import { type RecordFieldNodeValue, type RecordOutputSchemaV2, } from '@/workflow/workflow-variables/types/RecordOutputSchemaV2'; -import { isRecordOutputSchemaV2 } from '@/workflow/workflow-variables/utils/isRecordOutputSchemaV2'; +import { isRecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/guards/isRecordOutputSchemaV2'; import { isDefined } from 'twenty-shared/utils'; const getRecordObjectLabel = ( diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value.ts index dc311eee54..e297f093b6 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value.ts @@ -6,13 +6,15 @@ const isManyToOneRelationField = (field: FieldMetadataEntity) => (field as FieldMetadataEntity).settings ?.relationType === 'MANY_TO_ONE'; +const EXCLUDED_SYSTEM_FIELDS = ['searchVector', 'position']; + // TODO refactor export const shouldGenerateFieldFakeValue = ( field: FieldMetadataEntity, ) => { return ( field.isActive && - field.name !== 'searchVector' && + !(EXCLUDED_SYSTEM_FIELDS.includes(field.name) && field.isSystem) && (field.type !== FieldMetadataType.RELATION || isManyToOneRelationField(field as unknown as FieldMetadataEntity)) );