Highlight consumed variables in workflow runs (#10788)
- Create a function to resolve the variables used in the configuration of a step - Let the JSON tree components take a prop (`getNodeHighlighting`) to determine whether an element must be highlighted - Compute each element's keyPath recursively; the keyPath is passed as an argument to the `getNodeHighlighting` function ## Demo https://github.com/user-attachments/assets/8586f43d-53d1-41ba-ab48-08bb8c74e145 Closes https://github.com/twentyhq/core-team-issues/issues/435
This commit is contained in:
committed by
GitHub
parent
7e291f3cff
commit
ecf282ad99
@ -2,6 +2,7 @@ import { JsonNestedNode } from '@/workflow/components/json-visualizer/components
|
||||
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
||||
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
||||
import { getWorkflowRunStepContext } from '@/workflow/workflow-steps/utils/getWorkflowRunStepContext';
|
||||
import { getWorkflowVariablesUsedInStep } from '@/workflow/workflow-steps/utils/getWorkflowVariablesUsedInStep';
|
||||
import styled from '@emotion/styled';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { IconBrackets } from 'twenty-ui';
|
||||
@ -16,17 +17,25 @@ const StyledContainer = styled.div`
|
||||
export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
|
||||
const workflowRunId = useWorkflowRunIdOrThrow();
|
||||
const workflowRun = useWorkflowRun({ workflowRunId });
|
||||
const step = workflowRun?.output?.flow.steps.find(
|
||||
(step) => step.id === stepId,
|
||||
);
|
||||
|
||||
if (
|
||||
!(
|
||||
isDefined(workflowRun) &&
|
||||
isDefined(workflowRun.context) &&
|
||||
isDefined(workflowRun.output?.flow)
|
||||
isDefined(workflowRun.output?.flow) &&
|
||||
isDefined(step)
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const variablesUsedInStep = getWorkflowVariablesUsedInStep({
|
||||
step,
|
||||
});
|
||||
|
||||
const stepContext = getWorkflowRunStepContext({
|
||||
context: workflowRun.context,
|
||||
flow: workflowRun.output.flow,
|
||||
@ -48,6 +57,8 @@ export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
|
||||
Icon={IconBrackets}
|
||||
emptyElementsText=""
|
||||
depth={0}
|
||||
keyPath=""
|
||||
shouldHighlightNode={(keyPath) => variablesUsedInStep.has(keyPath)}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
@ -0,0 +1,173 @@
|
||||
import { WorkflowStep } from '@/workflow/types/Workflow';
|
||||
import { getWorkflowVariablesUsedInStep } from '@/workflow/workflow-steps/utils/getWorkflowVariablesUsedInStep';
|
||||
|
||||
describe('getWorkflowVariablesUsedInStep', () => {
|
||||
it('returns the variables used in a one-level object', () => {
|
||||
const step: WorkflowStep = {
|
||||
id: '42e8b60e-dd44-417a-875f-823d63f16819',
|
||||
name: 'Code - Serverless Function',
|
||||
type: 'CODE',
|
||||
valid: false,
|
||||
settings: {
|
||||
input: {
|
||||
serverlessFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
|
||||
serverlessFunctionInput: {
|
||||
a: '{{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a.b.c.d}}',
|
||||
},
|
||||
serverlessFunctionVersion: 'draft',
|
||||
},
|
||||
outputSchema: {},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
continueOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
getWorkflowVariablesUsedInStep({
|
||||
step,
|
||||
}),
|
||||
).toMatchInlineSnapshot(`
|
||||
Set {
|
||||
"5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a.b.c.d",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('returns the variables used in a computed field', () => {
|
||||
const step: WorkflowStep = {
|
||||
id: '42e8b60e-dd44-417a-875f-823d63f16819',
|
||||
name: 'Create Record',
|
||||
type: 'CREATE_RECORD',
|
||||
valid: false,
|
||||
settings: {
|
||||
input: {
|
||||
objectName: 'company',
|
||||
objectRecord: {
|
||||
name: 'Test',
|
||||
address: {
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
addressCity: '{{trigger.address.addressCity}}',
|
||||
addressState: '{{trigger.address.addressState}}',
|
||||
addressCountry: '{{trigger.address.addressCountry}}',
|
||||
addressStreet1: '{{trigger.address.addressStreet1}}',
|
||||
addressStreet2: '{{trigger.address.addressStreet2}}',
|
||||
addressPostcode: '{{trigger.address.addressPostcode}}',
|
||||
},
|
||||
domainName: {
|
||||
primaryLinkUrl: '',
|
||||
primaryLinkLabel: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
outputSchema: {},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
continueOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
getWorkflowVariablesUsedInStep({
|
||||
step,
|
||||
}),
|
||||
).toMatchInlineSnapshot(`
|
||||
Set {
|
||||
"trigger.address.addressCity",
|
||||
"trigger.address.addressState",
|
||||
"trigger.address.addressCountry",
|
||||
"trigger.address.addressStreet1",
|
||||
"trigger.address.addressStreet2",
|
||||
"trigger.address.addressPostcode",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('returns all the variables used in a single field', () => {
|
||||
const step: WorkflowStep = {
|
||||
id: '42e8b60e-dd44-417a-875f-823d63f16819',
|
||||
name: 'Code - Serverless Function',
|
||||
type: 'CODE',
|
||||
valid: false,
|
||||
settings: {
|
||||
input: {
|
||||
serverlessFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
|
||||
serverlessFunctionInput: {
|
||||
a: '{{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.b}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.c}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.d}}',
|
||||
},
|
||||
serverlessFunctionVersion: 'draft',
|
||||
},
|
||||
outputSchema: {},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
continueOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
getWorkflowVariablesUsedInStep({
|
||||
step,
|
||||
}),
|
||||
).toMatchInlineSnapshot(`
|
||||
Set {
|
||||
"5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a",
|
||||
"5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.b",
|
||||
"5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.c",
|
||||
"5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.d",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('returns the variables used many times only once', () => {
|
||||
const step: WorkflowStep = {
|
||||
id: '42e8b60e-dd44-417a-875f-823d63f16819',
|
||||
name: 'Code - Serverless Function',
|
||||
type: 'CODE',
|
||||
valid: false,
|
||||
settings: {
|
||||
input: {
|
||||
serverlessFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
|
||||
serverlessFunctionInput: {
|
||||
a: '{{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}}',
|
||||
},
|
||||
serverlessFunctionVersion: 'draft',
|
||||
},
|
||||
outputSchema: {},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
continueOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
getWorkflowVariablesUsedInStep({
|
||||
step,
|
||||
}),
|
||||
).toMatchInlineSnapshot(`
|
||||
Set {
|
||||
"5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,36 @@
|
||||
import { WorkflowStep } from '@/workflow/types/Workflow';
|
||||
import { CAPTURE_ALL_VARIABLE_TAG_INNER_REGEX } from '@/workflow/workflow-variables/constants/CaptureAllVariableTagInnerRegex';
|
||||
import { isObject, isString } from '@sniptt/guards';
|
||||
import { JsonValue } from 'type-fest';
|
||||
|
||||
function* resolveVariables(value: JsonValue): Generator<string> {
|
||||
if (isString(value)) {
|
||||
for (const [, variablePath] of value.matchAll(
|
||||
CAPTURE_ALL_VARIABLE_TAG_INNER_REGEX,
|
||||
)) {
|
||||
yield variablePath;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isObject(value)) {
|
||||
for (const nestedValue of Object.values(value)) {
|
||||
yield* resolveVariables(nestedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getWorkflowVariablesUsedInStep = ({
|
||||
step,
|
||||
}: {
|
||||
step: WorkflowStep;
|
||||
}) => {
|
||||
const variablesUsedInStep = new Set<string>();
|
||||
|
||||
for (const variable of resolveVariables(step.settings.input)) {
|
||||
variablesUsedInStep.add(variable);
|
||||
}
|
||||
|
||||
return variablesUsedInStep;
|
||||
};
|
||||
Reference in New Issue
Block a user