From c981ae329eea139b563deb767674eb522c725949 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Mon, 10 Mar 2025 13:44:58 +0100 Subject: [PATCH] Add variable path (#10720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Capture d’écran 2025-03-07 à 09 44 21 - search through step output schema the variable - build the variable path - returns the variable label - display both --- .../form-types/components/VariableChip.tsx | 37 +++- .../components/VariableChipStandalone.tsx | 8 +- .../FormAddressFieldInput.stories.tsx | 17 +- .../FormCurrencyFieldInput.stories.tsx | 15 +- .../FormDateFieldInput.stories.tsx | 15 +- .../FormDateTimeFieldInput.stories.tsx | 14 +- .../FormEmailsFieldInput.stories.tsx | 7 +- .../FormFullNameFieldInput.stories.tsx | 11 +- .../FormLinksFieldInput.stories.tsx | 10 +- .../FormMultiSelectFieldInput.stories.tsx | 7 +- .../FormPhoneFieldInput.stories.tsx | 18 +- .../FormRawJsonFieldInput.stories.tsx | 21 +- .../FormSelectFieldInput.stories.tsx | 7 +- .../FormTextFieldInput.stories.tsx | 29 ++- .../FormUuidFieldInput.stories.tsx | 85 ++------ ...RightDrawerWorkflowRunViewStep.stories.tsx | 2 + .../WorkflowSingleRecordFieldChip.tsx | 6 +- ...flowEditActionFormDeleteRecord.stories.tsx | 6 - .../CaptureAllVariableTagInnerRegex.ts | 1 + .../useAvailableVariablesInWorkflowStep.ts | 6 +- .../__tests__/extractVariableLabel.test.ts | 43 +++- .../searchVariableThroughOutputSchema.test.ts | 195 ++++++++++++++++++ .../utils/extractRawVariableNamePart.ts | 32 +++ .../utils/extractVariableLabel.ts | 21 -- .../searchVariableThroughOutputSchema.ts | 128 ++++++++++++ .../workflow-variables/utils/variableTag.ts | 7 +- .../SettingsExperience.stories.tsx | 2 +- .../decorators/WorkflowStepDecorator.tsx | 28 ++- .../src/testing/mock-data/workflow.ts | 37 +++- 29 files changed, 620 insertions(+), 195 deletions(-) create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/constants/CaptureAllVariableTagInnerRegex.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/searchVariableThroughOutputSchema.test.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/extractRawVariableNamePart.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/extractVariableLabel.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughOutputSchema.ts diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/VariableChip.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/VariableChip.tsx index c4456ef83..23d3b64ce 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/VariableChip.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/VariableChip.tsx @@ -1,4 +1,6 @@ -import { extractVariableLabel } from '@/workflow/workflow-variables/utils/extractVariableLabel'; +import { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema'; +import { extractRawVariableNamePart } from '@/workflow/workflow-variables/utils/extractRawVariableNamePart'; +import { searchVariableThroughOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughOutputSchema'; import { css, useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared'; @@ -60,17 +62,48 @@ const StyledDelete = styled.button` type VariableChipProps = { rawVariableName: string; onRemove?: () => void; + isFullRecord?: boolean; }; export const VariableChip = ({ rawVariableName, onRemove, + isFullRecord = false, }: VariableChipProps) => { const theme = useTheme(); + const { getStepsOutputSchema } = useStepsOutputSchema({}); + const stepId = extractRawVariableNamePart({ + rawVariableName, + part: 'stepId', + }); + + if (!isDefined(stepId)) { + return null; + } + + const stepOutputSchema = getStepsOutputSchema([stepId])?.[0]; + + if (!isDefined(stepOutputSchema)) { + return null; + } + + const { variableLabel, variablePathLabel } = + searchVariableThroughOutputSchema({ + stepOutputSchema, + rawVariableName, + isFullRecord, + }); + + const label = isDefined(variableLabel) + ? variableLabel + : extractRawVariableNamePart({ + rawVariableName, + part: 'selectedField', + }); return ( - {extractVariableLabel(rawVariableName)} + {label} {onRemove ? ( diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/VariableChipStandalone.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/VariableChipStandalone.tsx index f21aa0249..b3bf79d39 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/VariableChipStandalone.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/VariableChipStandalone.tsx @@ -10,15 +10,21 @@ const StyledContainer = styled.div` type VariableChipStandaloneProps = { rawVariableName: string; onRemove?: () => void; + isFullRecord?: boolean; }; export const VariableChipStandalone = ({ rawVariableName, onRemove, + isFullRecord, }: VariableChipStandaloneProps) => { return ( - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormAddressFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormAddressFieldInput.stories.tsx index 97e3ddf24..46c46aba2 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormAddressFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormAddressFieldInput.stories.tsx @@ -1,5 +1,7 @@ import { Meta, StoryObj } from '@storybook/react'; import { expect, fn, userEvent, within } from '@storybook/test'; +import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator'; +import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow'; import { FormAddressFieldInput } from '../FormAddressFieldInput'; const meta: Meta = { @@ -7,6 +9,7 @@ const meta: Meta = { component: FormAddressFieldInput, args: {}, argTypes: {}, + decorators: [WorkflowStepDecorator], }; export default meta; @@ -40,12 +43,12 @@ export const WithVariables: Story = { args: { label: 'Address', defaultValue: { - addressStreet1: '{{a.street1}}', - addressStreet2: '{{a.street2}}', - addressCity: '{{a.city}}', - addressState: '{{a.state}}', - addressCountry: '{{a.country}}', - addressPostcode: '{{a.postcode}}', + addressStreet1: `{{${MOCKED_STEP_ID}.address.street1}}`, + addressStreet2: `{{${MOCKED_STEP_ID}.address.street2}}`, + addressCity: `{{${MOCKED_STEP_ID}.address.city}}`, + addressState: `{{${MOCKED_STEP_ID}.address.state}}`, + addressCountry: `{{${MOCKED_STEP_ID}.address.country}}`, + addressPostcode: `{{${MOCKED_STEP_ID}.address.postcode}}`, addressLat: 39.781721, addressLng: -89.650148, }, @@ -58,14 +61,12 @@ export const WithVariables: Story = { const street2Variable = await canvas.findByText('street2'); const cityVariable = await canvas.findByText('city'); const stateVariable = await canvas.findByText('state'); - const countryVariable = await canvas.findByText('country'); const postcodeVariable = await canvas.findByText('postcode'); expect(street1Variable).toBeVisible(); expect(street2Variable).toBeVisible(); expect(cityVariable).toBeVisible(); expect(stateVariable).toBeVisible(); - expect(countryVariable).toBeVisible(); expect(postcodeVariable).toBeVisible(); const variablePickers = await canvas.findAllByText('VariablePicker'); diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormCurrencyFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormCurrencyFieldInput.stories.tsx index d0962dc37..597172c10 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormCurrencyFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormCurrencyFieldInput.stories.tsx @@ -2,6 +2,8 @@ import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode'; import { FieldCurrencyValue } from '@/object-record/record-field/types/FieldMetadata'; import { Meta, StoryObj } from '@storybook/react'; import { expect, within } from '@storybook/test'; +import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator'; +import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow'; import { FormCurrencyFieldInput } from '../FormCurrencyFieldInput'; const meta: Meta = { @@ -9,6 +11,7 @@ const meta: Meta = { component: FormCurrencyFieldInput, args: {}, argTypes: {}, + decorators: [WorkflowStepDecorator], }; export default meta; @@ -36,18 +39,18 @@ export const WithVariable: Story = { args: { label: 'Salary', defaultValue: { - currencyCode: CurrencyCode.USD, - amountMicros: '{{a.b.c}}', + currencyCode: `{{${MOCKED_STEP_ID}.amount.currencyCode}}` as CurrencyCode, + amountMicros: `{{${MOCKED_STEP_ID}.amount.amountMicros}}`, }, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const currency = await canvas.findByText(/USD/); - expect(currency).toBeVisible(); + const amountMicros = await canvas.findByText('My Amount Micros'); + const currencyCode = await canvas.findByText('My Currency Code'); - const amountVariable = await canvas.findByText('c'); - expect(amountVariable).toBeVisible(); + expect(amountMicros).toBeVisible(); + expect(currencyCode).toBeVisible(); }, }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormDateFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormDateFieldInput.stories.tsx index 5f08c2835..57b89f19c 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormDateFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormDateFieldInput.stories.tsx @@ -12,6 +12,8 @@ import { } from '@storybook/test'; import { DateTime } from 'luxon'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; +import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator'; +import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow'; import { FormDateFieldInput } from '../FormDateFieldInput'; const meta: Meta = { @@ -19,7 +21,7 @@ const meta: Meta = { component: FormDateFieldInput, args: {}, argTypes: {}, - decorators: [I18nFrontDecorator], + decorators: [I18nFrontDecorator, WorkflowStepDecorator], }; export default meta; @@ -308,7 +310,7 @@ export const SwitchesToStandaloneVariable: Story = { return ( - ); - }, - }, - play: async ({ canvasElement, args }) => { - const canvas = within(canvasElement); - - const input = await canvas.findByPlaceholderText('Enter UUID'); - - expect(input).toBeVisible(); - expect(input).toHaveDisplayValue(''); - - const addVariableButton = await canvas.findByRole('button', { - name: 'Add variable', - }); - - const [, , variableTag] = await Promise.all([ - userEvent.click(addVariableButton), - - waitForElementToBeRemoved(input), - waitFor(() => { - const variableTag = canvas.getByText('test'); - expect(variableTag).toBeVisible(); - - return variableTag; - }), - waitFor(() => { - expect(args.onPersist).toHaveBeenCalledWith('{{test}}'); - }), - ]); - - const removeVariableButton = canvasElement.querySelector( - 'button .tabler-icon-x', - ); - - await Promise.all([ - userEvent.click(removeVariableButton), - - waitForElementToBeRemoved(variableTag), - waitFor(() => { - const input = canvas.getByPlaceholderText('Enter UUID'); - expect(input).toBeVisible(); - }), - waitFor(() => { - expect(args.onPersist).toHaveBeenCalledWith(null); - }), - ]); - }, -}; - export const Disabled: Story = { args: { label: 'UUID field', @@ -222,7 +163,7 @@ export const Disabled: Story = { return (