Update design for not found variables (#10756)

<img width="494" alt="Capture d’écran 2025-03-10 à 14 09 37"
src="https://github.com/user-attachments/assets/ea4a55a1-c7f0-4138-a494-592b197ec8a2"
/>
This commit is contained in:
Thomas Trompette
2025-03-10 18:42:38 +01:00
committed by GitHub
parent 252d95c741
commit c8b44aa242
17 changed files with 159 additions and 47 deletions

View File

@ -3,12 +3,13 @@ import { extractRawVariableNamePart } from '@/workflow/workflow-variables/utils/
import { searchVariableThroughOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughOutputSchema';
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared';
import { IconX } from 'twenty-ui';
import { IconAlertTriangle, IconX } from 'twenty-ui';
const StyledChip = styled.div<{ deletable: boolean }>`
background-color: ${({ theme }) => theme.accent.quaternary};
border: 1px solid ${({ theme }) => theme.accent.tertiary};
const StyledChip = styled.div<{ deletable: boolean; danger: boolean }>`
background-color: ${({ theme, danger }) =>
danger ? theme.background.danger : theme.accent.quaternary};
border-radius: 4px;
height: 20px;
box-sizing: border-box;
@ -31,12 +32,13 @@ const StyledChip = styled.div<{ deletable: boolean }>`
`}
`;
const StyledLabel = styled.span`
color: ${({ theme }) => theme.color.blue};
const StyledLabel = styled.span<{ danger: boolean }>`
color: ${({ theme, danger }) =>
danger ? theme.color.red : theme.color.blue};
line-height: 140%;
`;
const StyledDelete = styled.button`
const StyledDelete = styled.button<{ danger: boolean }>`
box-sizing: border-box;
height: 20px;
width: 20px;
@ -50,12 +52,14 @@ const StyledDelete = styled.button`
margin: 0;
background: none;
border: none;
color: ${({ theme }) => theme.color.blue};
color: ${({ theme, danger }) =>
danger ? theme.color.red : theme.color.blue};
border-top-right-radius: ${({ theme }) => theme.border.radius.sm};
border-bottom-right-radius: ${({ theme }) => theme.border.radius.sm};
&:hover {
background-color: ${({ theme }) => theme.accent.secondary};
background-color: ${({ theme, danger }) =>
danger ? theme.color.red20 : theme.accent.secondary};
}
`;
@ -71,6 +75,7 @@ export const VariableChip = ({
isFullRecord = false,
}: VariableChipProps) => {
const theme = useTheme();
const { t } = useLingui();
const { getStepsOutputSchema } = useStepsOutputSchema({});
const stepId = extractRawVariableNamePart({
rawVariableName,
@ -90,19 +95,29 @@ export const VariableChip = ({
isFullRecord,
});
const label = isDefined(variableLabel)
? variableLabel
: extractRawVariableNamePart({
rawVariableName,
part: 'selectedField',
});
const isVariableNotFound = !isDefined(variableLabel);
const label = isVariableNotFound ? t`Not Found` : variableLabel;
const title = isVariableNotFound ? t`Variable not found` : variablePathLabel;
return (
<StyledChip deletable={isDefined(onRemove)}>
<StyledLabel title={variablePathLabel}>{label}</StyledLabel>
<StyledChip deletable={isDefined(onRemove)} danger={isVariableNotFound}>
{!isDefined(variableLabel) && (
<IconAlertTriangle
size={theme.icon.size.sm}
stroke={theme.icon.stroke.sm}
color={theme.color.red}
/>
)}
<StyledLabel title={title} danger={isVariableNotFound}>
{label}
</StyledLabel>
{onRemove ? (
<StyledDelete onClick={onRemove} aria-label="Remove variable">
<StyledDelete
onClick={onRemove}
aria-label="Remove variable"
danger={isVariableNotFound}
>
<IconX size={theme.icon.size.sm} stroke={theme.icon.stroke.sm} />
</StyledDelete>
) : null}

View File

@ -1,5 +1,6 @@
import { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, within } from '@storybook/test';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow';
import { FormAddressFieldInput } from '../FormAddressFieldInput';
@ -9,7 +10,7 @@ const meta: Meta<typeof FormAddressFieldInput> = {
component: FormAddressFieldInput,
args: {},
argTypes: {},
decorators: [WorkflowStepDecorator],
decorators: [WorkflowStepDecorator, I18nFrontDecorator],
};
export default meta;
@ -57,11 +58,11 @@ export const WithVariables: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const street1Variable = await canvas.findByText('street1');
const street2Variable = await canvas.findByText('street2');
const cityVariable = await canvas.findByText('city');
const stateVariable = await canvas.findByText('state');
const postcodeVariable = await canvas.findByText('postcode');
const street1Variable = await canvas.findByText('Street 1');
const street2Variable = await canvas.findByText('Street 2');
const cityVariable = await canvas.findByText('My City');
const stateVariable = await canvas.findByText('My State');
const postcodeVariable = await canvas.findByText('My Postcode');
expect(street1Variable).toBeVisible();
expect(street2Variable).toBeVisible();

View File

@ -2,6 +2,7 @@ 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 { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow';
import { FormCurrencyFieldInput } from '../FormCurrencyFieldInput';
@ -11,7 +12,7 @@ const meta: Meta<typeof FormCurrencyFieldInput> = {
component: FormCurrencyFieldInput,
args: {},
argTypes: {},
decorators: [WorkflowStepDecorator],
decorators: [WorkflowStepDecorator, I18nFrontDecorator],
};
export default meta;

View File

@ -1,5 +1,6 @@
import { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, within } from '@storybook/test';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow';
import { FormEmailsFieldInput } from '../FormEmailsFieldInput';
@ -9,7 +10,7 @@ const meta: Meta<typeof FormEmailsFieldInput> = {
component: FormEmailsFieldInput,
args: {},
argTypes: {},
decorators: [WorkflowStepDecorator],
decorators: [WorkflowStepDecorator, I18nFrontDecorator],
};
export default meta;

View File

@ -1,5 +1,6 @@
import { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, within } from '@storybook/test';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow';
import { FormFullNameFieldInput } from '../FormFullNameFieldInput';
@ -9,7 +10,7 @@ const meta: Meta<typeof FormFullNameFieldInput> = {
component: FormFullNameFieldInput,
args: {},
argTypes: {},
decorators: [WorkflowStepDecorator],
decorators: [WorkflowStepDecorator, I18nFrontDecorator],
};
export default meta;

View File

@ -1,6 +1,7 @@
import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { fn, userEvent, within } from '@storybook/test';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
import { FormLinksFieldInput } from '../FormLinksFieldInput';
@ -9,7 +10,7 @@ const meta: Meta<typeof FormLinksFieldInput> = {
component: FormLinksFieldInput,
args: {},
argTypes: {},
decorators: [WorkflowStepDecorator],
decorators: [WorkflowStepDecorator, I18nFrontDecorator],
};
export default meta;

View File

@ -1,6 +1,7 @@
import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { fn, userEvent, within } from '@storybook/test';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow';
import { FormMultiSelectFieldInput } from '../FormMultiSelectFieldInput';
@ -10,7 +11,7 @@ const meta: Meta<typeof FormMultiSelectFieldInput> = {
component: FormMultiSelectFieldInput,
args: {},
argTypes: {},
decorators: [WorkflowStepDecorator],
decorators: [WorkflowStepDecorator, I18nFrontDecorator],
};
export default meta;

View File

@ -2,6 +2,7 @@ import { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
import { FieldPhonesValue } from '@/object-record/record-field/types/FieldMetadata';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow';
import { FormPhoneFieldInput } from '../FormPhoneFieldInput';
@ -11,7 +12,7 @@ const meta: Meta<typeof FormPhoneFieldInput> = {
component: FormPhoneFieldInput,
args: {},
argTypes: {},
decorators: [WorkflowStepDecorator],
decorators: [WorkflowStepDecorator, I18nFrontDecorator],
};
export default meta;
@ -139,7 +140,7 @@ export const SelectingVariables: Story = {
return (
<button
onClick={() => {
onVariableSelect(`{{${MOCKED_STEP_ID}.phoneNumber}}`);
onVariableSelect(`{{${MOCKED_STEP_ID}.phone.number}}`);
}}
>
Add variable
@ -162,12 +163,12 @@ export const SelectingVariables: Story = {
await userEvent.click(phoneNumberVariablePicker);
const phoneNumberVariable = await canvas.findByText('phoneNumber');
const phoneNumberVariable = await canvas.findByText('My Number');
expect(phoneNumberVariable).toBeVisible();
await waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith({
primaryPhoneNumber: `{{${MOCKED_STEP_ID}.phoneNumber}}`,
primaryPhoneNumber: `{{${MOCKED_STEP_ID}.phone.number}}`,
primaryPhoneCountryCode: '',
primaryPhoneCallingCode: '',
});

View File

@ -2,6 +2,7 @@ import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { fn, userEvent, waitFor, within } from '@storybook/test';
import { getUserDevice } from 'twenty-ui';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow';
import { FormRawJsonFieldInput } from '../FormRawJsonFieldInput';
@ -11,7 +12,7 @@ const meta: Meta<typeof FormRawJsonFieldInput> = {
component: FormRawJsonFieldInput,
args: {},
argTypes: {},
decorators: [WorkflowStepDecorator],
decorators: [WorkflowStepDecorator, I18nFrontDecorator],
};
export default meta;

View File

@ -1,6 +1,7 @@
import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { fn, userEvent, within } from '@storybook/test';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow';
import { FormSelectFieldInput } from '../FormSelectFieldInput';
@ -10,7 +11,7 @@ const meta: Meta<typeof FormSelectFieldInput> = {
component: FormSelectFieldInput,
args: {},
argTypes: {},
decorators: [WorkflowStepDecorator],
decorators: [WorkflowStepDecorator, I18nFrontDecorator],
};
export default meta;

View File

@ -8,6 +8,7 @@ import {
within,
} from '@storybook/test';
import { getUserDevice } from 'twenty-ui';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow';
import { FormTextFieldInput } from '../FormTextFieldInput';
@ -17,7 +18,7 @@ const meta: Meta<typeof FormTextFieldInput> = {
component: FormTextFieldInput,
args: {},
argTypes: {},
decorators: [WorkflowStepDecorator],
decorators: [WorkflowStepDecorator, I18nFrontDecorator],
};
export default meta;

View File

@ -1,7 +1,8 @@
import { WorkflowVersionComponentInstanceContext } from '@/workflow/states/context/WorkflowVersionComponentInstanceContext';
import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { fn, userEvent, waitFor, within } from '@storybook/test';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow';
import { FormUuidFieldInput } from '../FormUuidFieldInput';
@ -10,15 +11,7 @@ const meta: Meta<typeof FormUuidFieldInput> = {
component: FormUuidFieldInput,
args: {},
argTypes: {},
decorators: [
(Story) => (
<WorkflowVersionComponentInstanceContext.Provider
value={{ instanceId: 'workflow-version-id' }}
>
<Story />
</WorkflowVersionComponentInstanceContext.Provider>
),
],
decorators: [WorkflowStepDecorator, I18nFrontDecorator],
};
export default meta;

View File

@ -0,0 +1,27 @@
import { VariableChip } from '@/object-record/record-field/form-types/components/VariableChip';
import { Meta, StoryObj } from '@storybook/react';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow';
const meta: Meta<typeof VariableChip> = {
title: 'UI/Data/Field/Form/Input/VariableChip',
component: VariableChip,
decorators: [WorkflowStepDecorator, I18nFrontDecorator],
};
export default meta;
type Story = StoryObj<typeof VariableChip>;
export const Default: Story = {
args: {
rawVariableName: `{{${MOCKED_STEP_ID}.address.street1}}`,
},
};
export const WithVariableNotFound: Story = {
args: {
rawVariableName: `{{${MOCKED_STEP_ID}.unknown.variable}}`,
},
};

View File

@ -2,14 +2,15 @@ import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { fn, userEvent, within } from '@storybook/test';
import { ComponentDecorator } from 'twenty-ui';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { getWorkflowNodeIdMock } from '~/testing/mock-data/workflow';
import { WorkflowEditActionFormCreateRecord } from '../WorkflowEditActionFormCreateRecord';
import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
const meta: Meta<typeof WorkflowEditActionFormCreateRecord> = {
title: 'Modules/Workflow/WorkflowEditActionFormCreateRecord',
@ -47,6 +48,7 @@ const meta: Meta<typeof WorkflowEditActionFormCreateRecord> = {
ObjectMetadataItemsDecorator,
SnackBarDecorator,
WorkspaceDecorator,
I18nFrontDecorator,
],
};

View File

@ -2,6 +2,7 @@ import { WorkflowDeleteRecordAction } from '@/workflow/types/Workflow';
import { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, within } from '@storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
@ -51,6 +52,7 @@ const meta: Meta<typeof WorkflowEditActionFormDeleteRecord> = {
SnackBarDecorator,
RouterDecorator,
WorkspaceDecorator,
I18nFrontDecorator,
],
};

View File

@ -2,6 +2,7 @@ import { WorkflowUpdateRecordAction } from '@/workflow/types/Workflow';
import { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, within } from '@storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
@ -64,6 +65,7 @@ const meta: Meta<typeof WorkflowEditActionFormUpdateRecord> = {
SnackBarDecorator,
RouterDecorator,
WorkspaceDecorator,
I18nFrontDecorator,
],
};

View File

@ -217,6 +217,67 @@ export const workflowQueryResult = {
value: 1000000000,
isLeaf: true,
},
address: {
isLeaf: false,
value: {
street1: {
isLeaf: true,
label: 'Street 1',
value: '123 Main St',
},
street2: {
isLeaf: true,
label: 'Street 2',
value: 'Apt 1',
},
city: {
isLeaf: true,
label: 'My City',
value: 'San Francisco',
},
state: {
isLeaf: true,
label: 'My State',
value: 'CA',
},
country: {
isLeaf: true,
label: 'My Country',
value: 'United States',
},
postcode: {
isLeaf: true,
label: 'My Postcode',
value: '94101',
},
lat: {
isLeaf: true,
label: 'My Lat',
value: 37.774929,
},
lng: {
isLeaf: true,
label: 'My Lng',
value: -122.419418,
},
},
},
phone: {
isLeaf: false,
label: 'Phone',
value: {
countryCode: {
isLeaf: true,
label: 'My Country Code',
value: '+1',
},
number: {
isLeaf: true,
label: 'My Number',
value: '1234567890',
},
},
},
},
object: {
icon: 'IconTargetArrow',