Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { FormRawJsonFieldInput } from '@/object-record/record-field/ui/form-types/components/FormRawJsonFieldInput';
import { type VariablePickerComponent } from '@/object-record/record-field/ui/form-types/types/VariablePickerComponent';
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';

type WorkflowEditActionCodeFieldArrayInputProps = {
label: string;
defaultValue: unknown;
readonly?: boolean;
onChange: (value: unknown) => void;
VariablePicker?: VariablePickerComponent;
};

// Renders the current value as the JSON string the user edits. Arrays are
// stringified; a standalone variable (or any legacy raw string) is shown as-is
// so it can be re-parsed on the next edit.
const getStringifiedDefaultValue = (value: unknown): string | null => {
if (!isDefined(value)) {
return null;
}

if (typeof value === 'string') {
return value === '' ? null : value;
}

if (Array.isArray(value)) {
return value.length > 0 ? JSON.stringify(value) : null;
}

return null;
};

export const WorkflowEditActionCodeFieldArrayInput = ({
label,
defaultValue,
readonly,
onChange,
VariablePicker,
}: WorkflowEditActionCodeFieldArrayInputProps) => {
const [error, setError] = useState<string | undefined>();
const [errorVisible, setErrorVisible] = useState(false);

const handleChange = (value: string | null) => {
if (readonly === true) {
return;
}

if (!isDefined(value) || value.trim() === '') {
setError(undefined);
onChange([]);

return;
}

if (isStandaloneVariableString(value)) {
setError(undefined);
onChange(value);

return;
}

let parsedValue: unknown;

try {
parsedValue = JSON.parse(value);
} catch {
setError(t`Enter a valid JSON array, e.g. ["a", "b"]`);

return;
}

if (!Array.isArray(parsedValue)) {
setError(t`Value must be a JSON array, e.g. ["a", "b"]`);

return;
}

setError(undefined);
onChange(parsedValue);
};

return (
<FormRawJsonFieldInput
label={label}
placeholder={t`Enter a JSON array, e.g. ["a", "b"]`}
error={errorVisible ? error : undefined}
onBlur={() => setErrorVisible(true)}
readonly={readonly}
defaultValue={getStringifiedDefaultValue(defaultValue)}
onChange={handleChange}
VariablePicker={VariablePicker}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { FormSingleRecordPicker } from '@/object-record/record-field/ui/form-typ
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
import { type VariablePickerComponent } from '@/object-record/record-field/ui/form-types/types/VariablePickerComponent';
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
import { WorkflowEditActionCodeFieldArrayInput } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCodeFieldArrayInput';
import { getWorkflowCodeFieldsEnumSelectOptions } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWorkflowCodeFieldsEnumSelectOptions';
import { getWorkflowCodeFieldsLeafKind } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWorkflowCodeFieldsLeafKind';
import { t } from '@lingui/core/macro';
Expand Down Expand Up @@ -89,6 +90,18 @@ export const WorkflowEditActionCodeFieldLeaf = ({
}
}

if (leafKind === 'array') {
return (
<WorkflowEditActionCodeFieldArrayInput
label={label}
defaultValue={inputValue}
readonly={readonly}
onChange={onChange}
VariablePicker={VariablePicker}
/>
);
}

if (leafKind === 'boolean') {
return (
<FormBooleanFieldInput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,30 @@ describe('getWorkflowCodeFieldsLeafKind', () => {
).toBe('record-array');
});

it('should map arrays of primitives to the array kind', () => {
expect(
getWorkflowCodeFieldsLeafKind({
type: 'array',
items: { type: 'string' },
}),
).toBe('array');
expect(
getWorkflowCodeFieldsLeafKind({
type: 'array',
items: { type: 'number' },
}),
).toBe('array');
expect(
getWorkflowCodeFieldsLeafKind({
type: 'array',
items: { type: 'boolean' },
}),
).toBe('array');
expect(
getWorkflowCodeFieldsLeafKind({ type: FieldMetadataType.ARRAY }),
).toBe('array');
});

it('should map the legacy object/array+marker form to record kinds', () => {
expect(
getWorkflowCodeFieldsLeafKind({
Expand All @@ -66,11 +90,5 @@ describe('getWorkflowCodeFieldsLeafKind', () => {
}),
).toBe('record-array');
expect(getWorkflowCodeFieldsLeafKind({ type: 'object' })).toBe('text');
expect(
getWorkflowCodeFieldsLeafKind({
type: 'array',
items: { type: 'object' },
}),
).toBe('text');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { isDefined } from 'twenty-shared/utils';
import { type InputSchemaProperty } from 'twenty-shared/workflow';

type WorkflowCodeFieldsLeafKind =
| 'array'
| 'boolean'
| 'enum'
| 'number'
Expand Down Expand Up @@ -37,6 +38,10 @@ export const getWorkflowCodeFieldsLeafKind = (
return 'enum';
}

if (property.type === 'array' || property.type === FieldMetadataType.ARRAY) {
return 'array';
}

switch (property.type) {
case 'boolean':
case FieldMetadataType.BOOLEAN:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ 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 { useVariableDropdown } from '@/workflow/workflow-variables/hooks/useVariableDropdown';
import { isBaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/guards/isBaseOutputSchemaV2';
import { isIteratorOutputSchema } from '@/workflow/workflow-variables/types/guards/isIteratorOutputSchema';
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';
Expand All @@ -17,9 +19,10 @@ import { getStepItemIcon } from '@/workflow/workflow-variables/utils/getStepItem
import { getVariableTemplateFromPath } from '@/workflow/workflow-variables/utils/getVariableTemplateFromPath';
import { useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared/utils';
import { isFlattenedArrayOutputSchema } from 'twenty-shared/workflow';
import { IconChevronLeft, useIcons } from 'twenty-ui/icon';
import { OverflowingTextWithTooltip } from 'twenty-ui/surfaces';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { OverflowingTextWithTooltip } from 'twenty-ui/surfaces';

type WorkflowVariablesDropdownStepItemsProps = {
step: StepOutputSchemaV2;
Expand Down Expand Up @@ -80,6 +83,61 @@ export const WorkflowVariablesDropdownStepItems = ({
);
};

// The iterator exposes the element currently being iterated on as `currentItem`.
// When it is an object we let the user select the whole item, not only one of its fields.
const iteratorCurrentItemNode = isIteratorOutputSchema(
step.type,
step.outputSchema,
)
? step.outputSchema.currentItem
: undefined;

const isViewingIteratorCurrentItem =
isDefined(iteratorCurrentItemNode) &&
!iteratorCurrentItemNode.isLeaf &&
currentPath.length === 1 &&
currentPath[0] === 'currentItem';

const isWholeItemFoundThroughSearch = isDefined(searchInputValue)
? (iteratorCurrentItemNode?.label
?.toLowerCase()
.includes(searchInputValue.toLowerCase()) ?? false)
: true;

const shouldDisplayWholeIteratorItem =
isViewingIteratorCurrentItem && isWholeItemFoundThroughSearch;

const handleSelectWholeIteratorItem = () => {
onSelect(
getVariableTemplateFromPath({
stepId: step.id,
path: currentPath,
}),
);
};

const isStepOutputFlattenedArray =
isBaseOutputSchemaV2(step.outputSchema) &&
isFlattenedArrayOutputSchema(step.outputSchema);

const isWholeListFoundThroughSearch = isDefined(searchInputValue)
? t`Whole list`.toLowerCase().includes(searchInputValue.toLowerCase())
: true;

const shouldDisplayWholeList =
isStepOutputFlattenedArray &&
currentPath.length === 0 &&
isWholeListFoundThroughSearch;

const handleSelectWholeList = () => {
onSelect(
getVariableTemplateFromPath({
stepId: step.id,
path: [],
}),
);
};

const displayedSubStepObject = getDisplayedSubStepObject();

const displayedSubStepObjectMetadata = isDefined(displayedSubStepObject)
Expand Down Expand Up @@ -136,6 +194,28 @@ export const WorkflowVariablesDropdownStepItems = ({
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{shouldDisplayWholeIteratorItem && (
<MenuItemSelect
selected={false}
focused={false}
onClick={handleSelectWholeIteratorItem}
text={iteratorCurrentItemNode?.label ?? ''}
hasSubMenu={false}
LeftIcon={getIcon(iteratorCurrentItemNode?.icon ?? 'IconBraces')}
contextualText={t`Use the whole item`}
/>
)}
{shouldDisplayWholeList && (
<MenuItemSelect
selected={false}
focused={false}
onClick={handleSelectWholeList}
text={t`Whole list`}
hasSubMenu={false}
LeftIcon={getIcon('IconListDetails')}
contextualText={t`Use the whole list`}
/>
)}
{shouldDisplaySubStepObject && (
<MenuItemSelect
selected={false}
Expand All @@ -148,9 +228,10 @@ export const WorkflowVariablesDropdownStepItems = ({
contextualText={t`Pick a ${objectLabel} record`}
/>
)}
{filteredOptions.length > 0 && shouldDisplaySubStepObject && (
<DropdownMenuSeparator />
)}
{filteredOptions.length > 0 &&
(shouldDisplaySubStepObject ||
shouldDisplayWholeIteratorItem ||
shouldDisplayWholeList) && <DropdownMenuSeparator />}
{filteredOptions.map(([key, subStep]) => {
if (!isDefined(subStep)) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
buildManualTriggerMetadataNode,
BulkRecordsAvailability,
extractRawVariableNamePart,
getCurrentItemSchemaFromFlattenedArrayOutputSchema,
GlobalAvailability,
isBaseOutputSchemaV2,
navigateOutputSchemaProperty,
Expand Down Expand Up @@ -653,8 +654,21 @@ export class WorkflowSchemaWorkspaceService {
case WorkflowActionType.HTTP_REQUEST:
case WorkflowActionType.LOGIC_FUNCTION: {
const propertyPath = extractPropertyPathFromVariable(items);
const outputSchema = this.getOutputSchemaWithExpectedFallback(
step.settings,
);

const variableTargetsWholeStepOutput = propertyPath.length === 0;

if (variableTargetsWholeStepOutput) {
const wholeStepOutputItemSchema =
getCurrentItemSchemaFromFlattenedArrayOutputSchema(outputSchema);

return wholeStepOutputItemSchema ?? DEFAULT_ITERATOR_CURRENT_ITEM;
}

const schemaNode = navigateOutputSchemaProperty({
schema: this.getOutputSchemaWithExpectedFallback(step.settings),
schema: outputSchema,
propertyPath,
});

Expand Down
4 changes: 4 additions & 0 deletions packages/twenty-shared/src/workflow/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ export { buildManualTriggerMetadataNode } from './workflow-schema/utils/build-ma
export { collectOutputSchemaPaths } from './workflow-schema/utils/collect-output-schema-paths';
export type { OutputSchemaPathFailure } from './workflow-schema/utils/find-output-schema-path-failure';
export { findOutputSchemaPathFailure } from './workflow-schema/utils/find-output-schema-path-failure';
export {
isFlattenedArrayOutputSchema,
getCurrentItemSchemaFromFlattenedArrayOutputSchema,
} from './workflow-schema/utils/flattened-array-output-schema';
export { navigateOutputSchemaProperty } from './workflow-schema/utils/navigate-output-schema-property';
export type { ResolvedVariable } from './workflow-schema/utils/resolve-variable-path-in-output-schema';
export {
Expand Down
4 changes: 4 additions & 0 deletions packages/twenty-shared/src/workflow/workflow-schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export type {
NodeType,
} from './types/base-output-schema.type';
export { collectOutputSchemaPaths } from './utils/collect-output-schema-paths';
export {
getCurrentItemSchemaFromFlattenedArrayOutputSchema,
isFlattenedArrayOutputSchema,
} from './utils/flattened-array-output-schema';
export {
findOutputSchemaPathFailure,
type OutputSchemaPathFailure,
Expand Down
Loading
Loading