Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable the fields of all CRUD workflow actions on readonly mode #9939

Merged
merged 11 commits into from
Jan 31, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type FormMultiSelectFieldInputProps = {
VariablePicker?: VariablePickerComponent;
readonly?: boolean;
placeholder?: string;
testId?: string;
};

const StyledDisplayModeReadonlyContainer = styled.div`
Expand Down Expand Up @@ -68,6 +69,7 @@ export const FormMultiSelectFieldInput = ({
VariablePicker,
readonly,
placeholder,
testId,
}: FormMultiSelectFieldInputProps) => {
const inputId = useId();
const theme = useTheme();
Expand Down Expand Up @@ -176,7 +178,7 @@ export const FormMultiSelectFieldInput = ({
const placeholderText = placeholder ?? label;

return (
<FormFieldInputContainer>
<FormFieldInputContainer data-testid={testId}>
{label ? <InputLabel>{label}</InputLabel> : null}

<FormFieldInputRowContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,14 @@ export const WorkflowStepHeader = ({
iconColor,
initialTitle,
headerType,
disabled,
}: {
onTitleChange: (newTitle: string) => void;
Icon: IconComponent;
iconColor: string;
initialTitle: string;
headerType: string;
disabled?: boolean;
}) => {
const theme = useTheme();
const [title, setTitle] = useState(initialTitle);
Expand All @@ -67,17 +69,16 @@ export const WorkflowStepHeader = ({
return (
<StyledHeader>
<StyledHeaderIconContainer>
{
<Icon
color={iconColor}
stroke={theme.icon.stroke.sm}
size={theme.icon.size.lg}
/>
}
<Icon
color={iconColor}
stroke={theme.icon.stroke.sm}
size={theme.icon.size.lg}
/>
</StyledHeaderIconContainer>
<StyledHeaderInfo>
<StyledHeaderTitle>
<TextInput
disabled={disabled}
value={title}
copyButton={false}
hotkeyScope="workflow-step-title"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
import { ComponentDecorator, IconPlus, THEME_LIGHT } from 'twenty-ui';
import { WorkflowStepHeader } from '../WorkflowStepHeader';

const meta: Meta<typeof WorkflowStepHeader> = {
title: 'Modules/Workflow/WorkflowStepHeader',
component: WorkflowStepHeader,
args: {
onTitleChange: fn(),
},
argTypes: {},
decorators: [ComponentDecorator],
};

export default meta;

type Story = StoryObj<typeof WorkflowStepHeader>;

export const Default: Story = {
args: {
headerType: 'Action',
iconColor: THEME_LIGHT.font.color.tertiary,
initialTitle: 'Create Record',
Icon: IconPlus,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

expect(await canvas.findByDisplayValue('Create Record')).toBeVisible();
expect(await canvas.findByText('Action')).toBeVisible();
},
};

export const EditableTitle: Story = {
args: {
headerType: 'Action',
iconColor: THEME_LIGHT.font.color.tertiary,
initialTitle: 'Create Record',
Icon: IconPlus,
onTitleChange: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);

const titleInput = await canvas.findByDisplayValue('Create Record');

const NEW_TITLE = 'New Title';

await userEvent.clear(titleInput);

await waitFor(() => {
expect(args.onTitleChange).toHaveBeenCalledWith('');
});

await userEvent.type(titleInput, `{selectall}{space}${NEW_TITLE}`);
Devessier marked this conversation as resolved.
Show resolved Hide resolved

await waitFor(() => {
expect(args.onTitleChange).toHaveBeenCalledWith(NEW_TITLE);
});

expect(args.onTitleChange).toHaveBeenCalledTimes(2);
expect(titleInput).toHaveValue(NEW_TITLE);
},
};

export const Disabled: Story = {
args: {
headerType: 'Action',
iconColor: THEME_LIGHT.font.color.tertiary,
initialTitle: 'Create Record',
Icon: IconPlus,
disabled: true,
onTitleChange: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);

const titleInput = await canvas.findByDisplayValue('Create Record');
expect(titleInput).toBeDisabled();

const NEW_TITLE = 'New Title';

await userEvent.type(titleInput, NEW_TITLE);

expect(args.onTitleChange).not.toHaveBeenCalled();
expect(titleInput).toHaveValue('Create Record');
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export const WorkflowEditActionFormCreateRecord = ({
iconColor={theme.font.color.tertiary}
initialTitle={headerTitle}
headerType="Action"
disabled={isFormDisabled}
/>
<WorkflowStepBody>
<Select
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export const WorkflowEditActionFormDeleteRecord = ({
iconColor={theme.font.color.tertiary}
initialTitle={headerTitle}
headerType="Action"
disabled={isFormDisabled}
/>
<WorkflowStepBody>
<Select
Expand Down Expand Up @@ -157,6 +158,8 @@ export const WorkflowEditActionFormDeleteRecord = ({
}
objectNameSingular={formData.objectName}
defaultValue={formData.objectRecordId}
testId="workflow-edit-action-record-delete-object-record-id"
disabled={isFormDisabled}
/>
</WorkflowStepBody>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export const WorkflowEditActionFormUpdateRecord = ({
iconColor={theme.font.color.tertiary}
initialTitle={headerTitle}
headerType="Action"
disabled={isFormDisabled}
/>

<WorkflowStepBody>
Expand Down Expand Up @@ -205,15 +206,18 @@ export const WorkflowEditActionFormUpdateRecord = ({
<HorizontalSeparator noMargin />

<WorkflowSingleRecordPicker
testId="workflow-edit-action-record-update-object-record-id"
label="Record"
onChange={(objectRecordId) =>
handleFieldChange('objectRecordId', objectRecordId)
}
objectNameSingular={formData.objectName}
defaultValue={formData.objectRecordId}
disabled={isFormDisabled}
/>

<FormMultiSelectFieldInput
testId="workflow-edit-action-record-update-fields-to-update"
label="Fields to update"
defaultValue={formData.fieldsToUpdate}
options={inlineFieldDefinitions.map((field) => ({
Expand All @@ -226,6 +230,7 @@ export const WorkflowEditActionFormUpdateRecord = ({
handleFieldChange('fieldsToUpdate', fieldsToUpdate)
}
placeholder="Select fields to update"
readonly={isFormDisabled}
/>

<HorizontalSeparator noMargin />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ type WorkflowSingleRecordFieldChipProps = {
selectedRecord?: ObjectRecord;
objectNameSingular: string;
onRemove: () => void;
disabled?: boolean;
};

export const WorkflowSingleRecordFieldChip = ({
draftValue,
selectedRecord,
objectNameSingular,
onRemove,
disabled,
}: WorkflowSingleRecordFieldChipProps) => {
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });

Expand All @@ -50,7 +52,7 @@ export const WorkflowSingleRecordFieldChip = ({
return (
<VariableChipStandalone
rawVariableName={objectMetadataItem.labelSingular}
onRemove={onRemove}
onRemove={disabled ? onRemove : undefined}
Devessier marked this conversation as resolved.
Show resolved Hide resolved
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {

import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { FormFieldInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputContainer';
import { FormFieldInputInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputInputContainer';
import { FormFieldInputRowContainer } from '@/object-record/record-field/form-types/components/FormFieldInputRowContainer';
import { SingleRecordSelect } from '@/object-record/relation-picker/components/SingleRecordSelect';
import { useRecordPicker } from '@/object-record/relation-picker/hooks/useRecordPicker';
Expand All @@ -24,18 +25,7 @@ import styled from '@emotion/styled';
import { useCallback } from 'react';
import { isValidUuid } from '~/utils/isValidUuid';

const StyledFormSelectContainer = styled.div`
background-color: ${({ theme }) => theme.background.transparent.lighter};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-top-left-radius: ${({ theme }) => theme.border.radius.sm};
border-bottom-left-radius: ${({ theme }) => theme.border.radius.sm};
border-right: none;
border-bottom-right-radius: none;
border-top-right-radius: none;
box-sizing: border-box;
display: flex;
overflow: 'hidden';
width: 100%;
const StyledFormSelectContainer = styled(FormFieldInputInputContainer)`
justify-content: space-between;
align-items: center;
padding-right: ${({ theme }) => theme.spacing(1)};
Expand Down Expand Up @@ -76,13 +66,17 @@ export type WorkflowSingleRecordPickerProps = {
defaultValue: RecordId | Variable;
onChange: (value: RecordId | Variable) => void;
objectNameSingular: string;
disabled?: boolean;
testId?: string;
};

export const WorkflowSingleRecordPicker = ({
label,
defaultValue,
objectNameSingular,
onChange,
disabled,
testId,
}: WorkflowSingleRecordPickerProps) => {
const draftValue: WorkflowSingleRecordPickerValue =
isStandaloneVariableString(defaultValue)
Expand Down Expand Up @@ -137,60 +131,64 @@ export const WorkflowSingleRecordPicker = ({
};

return (
<FormFieldInputContainer>
<FormFieldInputContainer data-testid={testId}>
{label ? <InputLabel>{label}</InputLabel> : null}
<FormFieldInputRowContainer>
<StyledFormSelectContainer>
<StyledFormSelectContainer hasRightElement={!disabled}>
Devessier marked this conversation as resolved.
Show resolved Hide resolved
<WorkflowSingleRecordFieldChip
draftValue={draftValue}
selectedRecord={selectedRecord}
objectNameSingular={objectNameSingular}
onRemove={handleUnlinkVariable}
Devessier marked this conversation as resolved.
Show resolved Hide resolved
/>
<DropdownScope dropdownScopeId={dropdownId}>
<Dropdown
dropdownId={dropdownId}
dropdownPlacement="left-start"
onClose={handleCloseRelationPickerDropdown}
clickableComponent={
<LightIconButton
className="displayOnHover"
Icon={IconChevronDown}
accent="tertiary"
/>
}
dropdownComponents={
<RecordPickerComponentInstanceContext.Provider
value={{ instanceId: dropdownId }}
>
<SingleRecordSelect
EmptyIcon={IconForbid}
emptyLabel={'No ' + objectNameSingular}
onCancel={() => closeDropdown()}
onRecordSelected={handleRecordSelected}
objectNameSingular={objectNameSingular}
recordPickerInstanceId={dropdownId}
selectedRecordIds={
draftValue?.value &&
!isStandaloneVariableString(draftValue.value)
? [draftValue.value]
: []
}
{!disabled && (
<DropdownScope dropdownScopeId={dropdownId}>
<Dropdown
dropdownId={dropdownId}
dropdownPlacement="left-start"
onClose={handleCloseRelationPickerDropdown}
clickableComponent={
<LightIconButton
className="displayOnHover"
Icon={IconChevronDown}
accent="tertiary"
/>
</RecordPickerComponentInstanceContext.Provider>
}
dropdownHotkeyScope={{ scope: dropdownId }}
/>
</DropdownScope>
}
dropdownComponents={
<RecordPickerComponentInstanceContext.Provider
value={{ instanceId: dropdownId }}
>
<SingleRecordSelect
EmptyIcon={IconForbid}
emptyLabel={'No ' + objectNameSingular}
onCancel={() => closeDropdown()}
onRecordSelected={handleRecordSelected}
objectNameSingular={objectNameSingular}
recordPickerInstanceId={dropdownId}
selectedRecordIds={
draftValue?.value &&
!isStandaloneVariableString(draftValue.value)
? [draftValue.value]
: []
}
/>
</RecordPickerComponentInstanceContext.Provider>
}
dropdownHotkeyScope={{ scope: dropdownId }}
/>
</DropdownScope>
)}
</StyledFormSelectContainer>
<StyledSearchVariablesDropdownContainer>
<WorkflowVariablesDropdown
inputId={variablesDropdownId}
onVariableSelect={handleVariableTagInsert}
disabled={false}
objectNameSingularToSelect={objectNameSingular}
/>
</StyledSearchVariablesDropdownContainer>

{!disabled && (
<StyledSearchVariablesDropdownContainer>
<WorkflowVariablesDropdown
inputId={variablesDropdownId}
onVariableSelect={handleVariableTagInsert}
objectNameSingularToSelect={objectNameSingular}
/>
</StyledSearchVariablesDropdownContainer>
)}
</FormFieldInputRowContainer>
</FormFieldInputContainer>
);
Expand Down
Loading
Loading