Search record action - allow to search more than 1 record (#14769)
https://github.com/user-attachments/assets/426da6af-6c6b-4d49-9034-0aa041cb331f
This commit is contained in:
@@ -1,18 +1,47 @@
|
||||
import { stepsOutputSchemaFamilyState } from '@/workflow/states/stepsOutputSchemaFamilyState';
|
||||
import { type WorkflowVersion } from '@/workflow/types/Workflow';
|
||||
import {
|
||||
type WorkflowActionType,
|
||||
type WorkflowVersion,
|
||||
} from '@/workflow/types/Workflow';
|
||||
import { getStepOutputSchemaFamilyStateKey } from '@/workflow/utils/getStepOutputSchemaFamilyStateKey';
|
||||
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
||||
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerDefaultLabel';
|
||||
import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
||||
import { isFindRecordsOutputSchema } from '@/workflow/workflow-variables/types/guards/isFindRecordsOutputSchema';
|
||||
import {
|
||||
type OutputSchemaV2,
|
||||
type StepOutputSchemaV2,
|
||||
} from '@/workflow/workflow-variables/types/StepOutputSchemaV2';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
const getFilteredOutputSchema = ({
|
||||
stepType,
|
||||
outputSchema,
|
||||
isIteratorEnabled,
|
||||
}: {
|
||||
stepType: WorkflowActionType;
|
||||
outputSchema: OutputSchemaV2;
|
||||
isIteratorEnabled: boolean;
|
||||
}) => {
|
||||
if (!isIteratorEnabled && isFindRecordsOutputSchema(stepType, outputSchema)) {
|
||||
const filteredOutputSchema = {
|
||||
...outputSchema,
|
||||
all: undefined,
|
||||
};
|
||||
|
||||
return filteredOutputSchema;
|
||||
}
|
||||
|
||||
return outputSchema;
|
||||
};
|
||||
export const useStepsOutputSchema = () => {
|
||||
const isIteratorEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKFLOW_ITERATOR_ENABLED,
|
||||
);
|
||||
const populateStepsOutputSchema = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(workflowVersion: WorkflowVersion) => {
|
||||
@@ -22,7 +51,11 @@ export const useStepsOutputSchema = () => {
|
||||
name: step.name,
|
||||
type: step.type,
|
||||
icon: getActionIcon(step.type),
|
||||
outputSchema: step.settings?.outputSchema as OutputSchemaV2,
|
||||
outputSchema: getFilteredOutputSchema({
|
||||
stepType: step.type,
|
||||
outputSchema: step.settings?.outputSchema as OutputSchemaV2,
|
||||
isIteratorEnabled,
|
||||
}),
|
||||
};
|
||||
|
||||
set(
|
||||
@@ -59,7 +92,7 @@ export const useStepsOutputSchema = () => {
|
||||
);
|
||||
}
|
||||
},
|
||||
[],
|
||||
[isIteratorEnabled],
|
||||
);
|
||||
|
||||
const deleteStepsOutputSchema = useRecoilCallback(
|
||||
|
||||
+15
-2
@@ -19,6 +19,7 @@ import { WorkflowFindRecordsFilters } from '@/workflow/workflow-steps/workflow-a
|
||||
import { WorkflowFindRecordsFiltersEffect } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsFiltersEffect';
|
||||
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isNumber } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { HorizontalSeparator, useIcons } from 'twenty-ui/display';
|
||||
import { type SelectOption } from 'twenty-ui/input';
|
||||
@@ -230,8 +231,20 @@ export const WorkflowEditActionFindRecords = ({
|
||||
label="Limit"
|
||||
defaultValue={formData.limit}
|
||||
placeholder="Enter limit"
|
||||
onChange={() => {}}
|
||||
readonly
|
||||
onChange={(limit) => {
|
||||
if (isFormDisabled === true || !isNumber(limit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newFormData: FindRecordsFormData = {
|
||||
...formData,
|
||||
limit,
|
||||
};
|
||||
|
||||
setFormData(newFormData);
|
||||
|
||||
saveAction(newFormData);
|
||||
}}
|
||||
/>
|
||||
</WorkflowStepBody>
|
||||
{!actionOptions.readonly && <WorkflowActionFooter stepId={action.id} />}
|
||||
|
||||
+3
-2
@@ -1,7 +1,8 @@
|
||||
import type { Leaf } from '@/workflow/workflow-variables/types/BaseOutputSchemaV2';
|
||||
import { type RecordNode } from '@/workflow/workflow-variables/types/RecordNode';
|
||||
|
||||
export type FindRecordsOutputSchema = {
|
||||
first: RecordNode;
|
||||
last: RecordNode;
|
||||
totalCount: number;
|
||||
all: Leaf | undefined;
|
||||
totalCount: Leaf;
|
||||
};
|
||||
|
||||
+16
-12
@@ -36,12 +36,18 @@ describe('searchVariableThroughFindRecordsOutputSchema', () => {
|
||||
label: 'First',
|
||||
value: mockRecordSchema,
|
||||
},
|
||||
last: {
|
||||
isLeaf: false,
|
||||
label: 'Last',
|
||||
value: mockRecordSchema,
|
||||
all: {
|
||||
isLeaf: true,
|
||||
label: 'All',
|
||||
value: 'Returns an array of records',
|
||||
type: 'array',
|
||||
},
|
||||
totalCount: {
|
||||
isLeaf: true,
|
||||
label: 'Total Count',
|
||||
value: 42,
|
||||
type: 'number',
|
||||
},
|
||||
totalCount: 42,
|
||||
};
|
||||
|
||||
it('should handle totalCount variable correctly', () => {
|
||||
@@ -76,20 +82,18 @@ describe('searchVariableThroughFindRecordsOutputSchema', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle last record field access correctly', () => {
|
||||
it('should handle all records access correctly', () => {
|
||||
const result = searchVariableThroughFindRecordsOutputSchema({
|
||||
stepName: 'Find Companies',
|
||||
searchRecordOutputSchema: mockSearchRecordSchema,
|
||||
rawVariableName: '{{step1.last.revenue}}',
|
||||
rawVariableName: '{{step1.all}}',
|
||||
isFullRecord: false,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
variableLabel: 'Revenue',
|
||||
variablePathLabel: 'Find Companies > Last > Revenue',
|
||||
variableType: FieldMetadataType.NUMBER,
|
||||
fieldMetadataId: 'company-revenue-metadata-id',
|
||||
compositeFieldSubFieldName: undefined,
|
||||
variableLabel: 'All',
|
||||
variablePathLabel: 'Find Companies > All',
|
||||
variableType: FieldMetadataType.ARRAY,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+21
-11
@@ -5,7 +5,7 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { CAPTURE_ALL_VARIABLE_TAG_INNER_REGEX } from 'twenty-shared/workflow';
|
||||
|
||||
type SearchResultKey = 'first' | 'last' | 'totalCount';
|
||||
type SearchResultKey = 'first' | 'all' | 'totalCount';
|
||||
|
||||
/**
|
||||
* Parses a variable name to extract its components for SearchRecord outputs
|
||||
@@ -68,15 +68,7 @@ export const searchVariableThroughFindRecordsOutputSchema = ({
|
||||
};
|
||||
}
|
||||
|
||||
if (searchResultKey === 'totalCount') {
|
||||
return {
|
||||
variableLabel: 'Total Count',
|
||||
variablePathLabel: `${stepName} > Total Count`,
|
||||
variableType: FieldMetadataType.NUMBER,
|
||||
};
|
||||
}
|
||||
|
||||
if (searchResultKey === 'first' || searchResultKey === 'last') {
|
||||
if (searchResultKey === 'first') {
|
||||
const recordSchema = searchRecordOutputSchema[searchResultKey]?.value;
|
||||
|
||||
if (!isDefined(recordSchema) || !isDefined(fieldName)) {
|
||||
@@ -87,7 +79,7 @@ export const searchVariableThroughFindRecordsOutputSchema = ({
|
||||
}
|
||||
|
||||
return searchRecordOutputSchemaUtil({
|
||||
stepName: `${stepName} > ${searchResultKey === 'first' ? 'First' : 'Last'}`,
|
||||
stepName: `${stepName} > ${searchRecordOutputSchema[searchResultKey]?.label ?? 'First'}`,
|
||||
recordOutputSchema: recordSchema,
|
||||
selectedField: fieldName,
|
||||
path: pathSegments,
|
||||
@@ -95,6 +87,24 @@ export const searchVariableThroughFindRecordsOutputSchema = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (searchResultKey === 'totalCount') {
|
||||
return {
|
||||
variableLabel:
|
||||
searchRecordOutputSchema[searchResultKey]?.label ?? 'Total Count',
|
||||
variablePathLabel: `${stepName} > ${searchRecordOutputSchema[searchResultKey]?.label ?? 'Total Count'}`,
|
||||
variableType: FieldMetadataType.NUMBER,
|
||||
};
|
||||
}
|
||||
|
||||
if (searchResultKey === 'all') {
|
||||
return {
|
||||
variableLabel:
|
||||
searchRecordOutputSchema[searchResultKey]?.label ?? 'All Records',
|
||||
variablePathLabel: `${stepName} > ${searchRecordOutputSchema[searchResultKey]?.label ?? 'All Records'}`,
|
||||
variableType: FieldMetadataType.ARRAY,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
variableLabel: undefined,
|
||||
variablePathLabel: undefined,
|
||||
|
||||
+8
@@ -76,6 +76,14 @@ export const searchVariableThroughIteratorOutputSchema = ({
|
||||
|
||||
if (iteratorResultKey === 'currentItem') {
|
||||
const schema = iteratorOutputSchema.currentItem.value;
|
||||
|
||||
if (!isDefined(schema)) {
|
||||
return {
|
||||
variableLabel: undefined,
|
||||
variablePathLabel: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (isRecordOutputSchemaV2(schema) && isDefined(fieldName)) {
|
||||
return searchRecordOutputSchema({
|
||||
stepName: `${stepName} > Current Item`,
|
||||
|
||||
Reference in New Issue
Block a user