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
This commit is contained in:
@@ -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<BaseOutputSchema>(
|
||||
const newOutputSchema = fields.reduce<BaseOutputSchemaDeprecated>(
|
||||
(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;
|
||||
|
||||
+2
-6
@@ -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',
|
||||
},
|
||||
};
|
||||
|
||||
+8
-9
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
+2
-2
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+29
-19
@@ -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 (
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
|
||||
@@ -200,22 +210,22 @@ export const WorkflowDropdownStepOutputItems = ({
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{shouldDisplayObject && (
|
||||
{shouldDisplaySubStepObject && (
|
||||
<MenuItemSelect
|
||||
selected={false}
|
||||
focused={false}
|
||||
onClick={handleSelectObject}
|
||||
text={displayedSubStepObject?.label || ''}
|
||||
text={objectLabel || ''}
|
||||
hasSubMenu={false}
|
||||
LeftIcon={
|
||||
displayedSubStepObject.icon
|
||||
? getIcon(displayedSubStepObject.icon)
|
||||
subStepObjectMetadataItem?.icon
|
||||
? getIcon(subStepObjectMetadataItem.icon)
|
||||
: undefined
|
||||
}
|
||||
contextualText={t`Pick a ${nameSingular} record`}
|
||||
contextualText={t`Pick a ${objectLabel} record`}
|
||||
/>
|
||||
)}
|
||||
{filteredOptions.length > 0 && shouldDisplayObject && (
|
||||
{filteredOptions.length > 0 && shouldDisplaySubStepObject && (
|
||||
<DropdownMenuSeparator />
|
||||
)}
|
||||
{filteredOptions.map(([key, subStep]) => (
|
||||
|
||||
+4
-4
@@ -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) ? (
|
||||
<WorkflowVariablesDropdownWorkflowStepItems
|
||||
<WorkflowVariablesDropdownSteps
|
||||
dropdownId={dropdownId}
|
||||
steps={availableVariablesInWorkflowStep}
|
||||
onSelect={handleStepSelect}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { type WorkflowHttpRequestAction } from '@/workflow/types/Workflow';
|
||||
import { parseAndValidateVariableFriendlyStringifiedJson } from '@/workflow/utils/parseAndValidateVariableFriendlyStringifiedJson';
|
||||
import { type BaseOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema';
|
||||
import { type BaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useState } from 'react';
|
||||
import { convertOutputSchemaToJson } from '../utils/convertOutputSchemaToJson';
|
||||
@@ -21,7 +21,7 @@ export const useHttpRequestOutputSchema = ({
|
||||
Object.keys(action.settings.outputSchema).length
|
||||
? JSON.stringify(
|
||||
convertOutputSchemaToJson(
|
||||
action.settings.outputSchema as BaseOutputSchema,
|
||||
action.settings.outputSchema as BaseOutputSchemaV2,
|
||||
),
|
||||
null,
|
||||
2,
|
||||
|
||||
+23
-53
@@ -1,29 +1,26 @@
|
||||
import { type BaseOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema';
|
||||
import { type BaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2';
|
||||
import { convertOutputSchemaToJson } from '../convertOutputSchemaToJson';
|
||||
|
||||
describe('convertOutputSchemaToJson', () => {
|
||||
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 = {
|
||||
|
||||
+15
-100
@@ -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);
|
||||
|
||||
+6
-4
@@ -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<string, unknown> | 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,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+11
-8
@@ -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;
|
||||
}
|
||||
|
||||
+7
-13
@@ -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) ? (
|
||||
<WorkflowVariablesDropdownWorkflowStepItems
|
||||
<WorkflowVariablesDropdownSteps
|
||||
dropdownId={dropdownId}
|
||||
steps={availableVariablesInWorkflowStep}
|
||||
onSelect={handleStepSelect}
|
||||
/>
|
||||
) : shouldDisplayRecordObjects ? (
|
||||
<WorkflowVariablesDropdownAllItems
|
||||
step={selectedStep}
|
||||
onSelect={handleSubItemSelect}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
) : (
|
||||
<WorkflowVariablesDropdownFieldItems
|
||||
<WorkflowVariablesDropdownStepItems
|
||||
step={selectedStep}
|
||||
onSelect={handleSubItemSelect}
|
||||
onBack={handleBack}
|
||||
shouldDisplayRecordObjects={shouldDisplayRecordObjects}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
-82
@@ -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 (
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
onClick={goBack}
|
||||
Icon={IconChevronLeft}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<OverflowingTextWithTooltip
|
||||
text={getStepHeaderLabel(step, currentPath)}
|
||||
/>
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
value={searchInputValue}
|
||||
onChange={(event) => setSearchInputValue(event.target.value)}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{filteredOptions.map(([key, option]) => (
|
||||
<MenuItemSelect
|
||||
key={key}
|
||||
selected={false}
|
||||
focused={false}
|
||||
onClick={() => handleSelectField(key)}
|
||||
text={option.label || key}
|
||||
hasSubMenu={!option.isLeaf}
|
||||
LeftIcon={option.icon ? getIcon(option.icon) : undefined}
|
||||
contextualText={
|
||||
option.isLeaf ? option?.value?.toString() : undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
+44
-22
@@ -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 (
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
|
||||
@@ -105,22 +119,22 @@ export const WorkflowVariablesDropdownAllItems = ({
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{shouldDisplayObject && (
|
||||
{shouldDisplaySubStepObject && (
|
||||
<MenuItemSelect
|
||||
selected={false}
|
||||
focused={false}
|
||||
onClick={handleSelectObject}
|
||||
text={displayedSubStepObject?.label || ''}
|
||||
text={objectLabel || ''}
|
||||
hasSubMenu={false}
|
||||
LeftIcon={
|
||||
displayedSubStepObject.icon
|
||||
? getIcon(displayedSubStepObject.icon)
|
||||
displayedSubStepObjectMetadata?.icon
|
||||
? getIcon(displayedSubStepObjectMetadata.icon)
|
||||
: undefined
|
||||
}
|
||||
contextualText={t`Pick a ${nameSingular} record`}
|
||||
contextualText={t`Pick a ${objectLabel} record`}
|
||||
/>
|
||||
)}
|
||||
{filteredOptions.length > 0 && shouldDisplayObject && (
|
||||
{filteredOptions.length > 0 && shouldDisplaySubStepObject && (
|
||||
<DropdownMenuSeparator />
|
||||
)}
|
||||
{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
|
||||
}
|
||||
+5
-5
@@ -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('');
|
||||
|
||||
+5
-7
@@ -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,
|
||||
};
|
||||
})
|
||||
|
||||
+4
-65
@@ -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,
|
||||
});
|
||||
|
||||
+15
-14
@@ -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<string, FieldOutputSchemaV2>,
|
||||
) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
+17
-29
@@ -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<string, Leaf | Node>;
|
||||
|
||||
export type LeafDeprecated = {
|
||||
isLeaf: true;
|
||||
type: InputSchemaPropertyType | undefined;
|
||||
label: string;
|
||||
value: any;
|
||||
};
|
||||
|
||||
export type BaseOutputSchemaDeprecated = Record<string, LeafDeprecated | Node>;
|
||||
|
||||
+1
-11
@@ -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;
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import { type RecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/RecordOutputSchemaV2';
|
||||
|
||||
export type DatabaseEventTriggerOutputSchema = RecordOutputSchemaV2;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
type Link = {
|
||||
isLeaf: true;
|
||||
tab?: string;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export type LinkOutputSchema = {
|
||||
link: Link;
|
||||
_outputSchemaType: 'LINK';
|
||||
};
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import { type BaseOutputSchemaV2 } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2';
|
||||
|
||||
export type OpenStepOutputSchema = BaseOutputSchemaV2;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import { type RecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/RecordOutputSchemaV2';
|
||||
|
||||
export type RecordActionOutputSchema = RecordOutputSchemaV2;
|
||||
-64
@@ -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<string, Leaf | Node>;
|
||||
|
||||
export type FieldOutputSchema = (Leaf | Node) & {
|
||||
fieldMetadataId: string;
|
||||
};
|
||||
|
||||
export type RecordOutputSchema = {
|
||||
object: {
|
||||
nameSingular: string;
|
||||
fieldIdName: string;
|
||||
objectMetadataId: string;
|
||||
isRelationField?: boolean;
|
||||
} & Leaf;
|
||||
fields: Record<string, FieldOutputSchema>;
|
||||
_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;
|
||||
};
|
||||
+24
@@ -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;
|
||||
};
|
||||
+9
@@ -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);
|
||||
};
|
||||
+13
@@ -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';
|
||||
};
|
||||
+13
@@ -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';
|
||||
};
|
||||
+13
@@ -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';
|
||||
};
|
||||
+13
@@ -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';
|
||||
};
|
||||
+13
@@ -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'
|
||||
);
|
||||
};
|
||||
+25
@@ -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)
|
||||
);
|
||||
};
|
||||
+15
@@ -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);
|
||||
};
|
||||
+13
@@ -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'
|
||||
);
|
||||
};
|
||||
+6
-7
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
+7
-7
@@ -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', () => {
|
||||
|
||||
+16
-7
@@ -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', () => {
|
||||
|
||||
-14
@@ -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);
|
||||
});
|
||||
});
|
||||
-373
@@ -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',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+32
-26
@@ -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<string, FieldOutputSchema> = {};
|
||||
}): RecordOutputSchemaV2 | undefined => {
|
||||
const filteredFields: Record<string, FieldOutputSchemaV2> = {};
|
||||
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<string, FieldOutputSchema> = {};
|
||||
}): RecordOutputSchemaV2 => {
|
||||
const filteredFields: Record<string, FieldOutputSchemaV2> = {};
|
||||
|
||||
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,
|
||||
|
||||
+9
-9
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
-26
@@ -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';
|
||||
}
|
||||
};
|
||||
+6
-6
@@ -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;
|
||||
|
||||
+38
@@ -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;
|
||||
};
|
||||
-10
@@ -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;
|
||||
};
|
||||
-10
@@ -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';
|
||||
};
|
||||
-10
@@ -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';
|
||||
};
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
import {
|
||||
type RecordFieldLeaf,
|
||||
type RecordOutputSchemaV2,
|
||||
} from '@/workflow/workflow-variables/types/RecordOutputSchemaV2';
|
||||
|
||||
export const isRecordOutputSchemaV2 = (
|
||||
outputSchema: RecordOutputSchemaV2 | Record<string, RecordFieldLeaf>,
|
||||
): outputSchema is RecordOutputSchemaV2 => {
|
||||
return outputSchema._outputSchemaType === 'RECORD';
|
||||
};
|
||||
-200
@@ -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,
|
||||
});
|
||||
};
|
||||
+83
@@ -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,
|
||||
});
|
||||
};
|
||||
+1
-1
@@ -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 = (
|
||||
|
||||
+3
-1
@@ -6,13 +6,15 @@ const isManyToOneRelationField = (field: FieldMetadataEntity) =>
|
||||
(field as FieldMetadataEntity<FieldMetadataType.RELATION>).settings
|
||||
?.relationType === 'MANY_TO_ONE';
|
||||
|
||||
const EXCLUDED_SYSTEM_FIELDS = ['searchVector', 'position'];
|
||||
|
||||
// TODO refactor
|
||||
export const shouldGenerateFieldFakeValue = <T extends FieldMetadataType>(
|
||||
field: FieldMetadataEntity<T>,
|
||||
) => {
|
||||
return (
|
||||
field.isActive &&
|
||||
field.name !== 'searchVector' &&
|
||||
!(EXCLUDED_SYSTEM_FIELDS.includes(field.name) && field.isSystem) &&
|
||||
(field.type !== FieldMetadataType.RELATION ||
|
||||
isManyToOneRelationField(field as unknown as FieldMetadataEntity))
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user