Order the workflow run's output properly in the JsonFieldDisplay (#11583)
In this PR: - Order the workflow run's output in the JsonField Display; the order should be: error, stepsOutput, flow - Ensure the special characters are hidden in the JSON visualizer - Add missing scenarios to Json Tree's stories as it ensures Chromatic checks them https://github.com/user-attachments/assets/2ca5ae1d-fdba-4327-bad2-246fd9d23cb9 Closes https://github.com/twentyhq/core-team-issues/issues/804
This commit is contained in:
committed by
GitHub
parent
8bd7b78825
commit
c23942ce6f
@ -0,0 +1,25 @@
|
|||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { orderWorkflowRunOutput } from '@/object-record/record-field/meta-types/utils/orderWorkflowRunOutput';
|
||||||
|
import { FieldJsonValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
export const useFormattedJsonFieldValue = ({
|
||||||
|
fieldValue,
|
||||||
|
}: {
|
||||||
|
fieldValue: FieldJsonValue | undefined;
|
||||||
|
}): FieldJsonValue | undefined => {
|
||||||
|
const { fieldDefinition } = useContext(FieldContext);
|
||||||
|
|
||||||
|
if (
|
||||||
|
fieldDefinition.metadata.objectMetadataNameSingular ===
|
||||||
|
CoreObjectNameSingular.WorkflowRun &&
|
||||||
|
fieldDefinition.metadata.fieldName === 'output' &&
|
||||||
|
isDefined(fieldValue)
|
||||||
|
) {
|
||||||
|
return orderWorkflowRunOutput(fieldValue) as FieldJsonValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldValue;
|
||||||
|
};
|
||||||
@ -3,6 +3,7 @@ import { useContext } from 'react';
|
|||||||
import { FieldJsonValue } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldJsonValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||||
|
|
||||||
|
import { useFormattedJsonFieldValue } from '@/object-record/record-field/meta-types/hooks/useFormattedJsonFieldValue';
|
||||||
import { FieldContext } from '../../contexts/FieldContext';
|
import { FieldContext } from '../../contexts/FieldContext';
|
||||||
|
|
||||||
export const useJsonFieldDisplay = () => {
|
export const useJsonFieldDisplay = () => {
|
||||||
@ -15,9 +16,13 @@ export const useJsonFieldDisplay = () => {
|
|||||||
fieldName,
|
fieldName,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const formattedFieldValue = useFormattedJsonFieldValue({
|
||||||
|
fieldValue,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
maxWidth,
|
maxWidth,
|
||||||
fieldDefinition,
|
fieldDefinition,
|
||||||
fieldValue,
|
fieldValue: formattedFieldValue,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { WorkflowRunOutput } from '@/workflow/types/Workflow';
|
import { orderWorkflowRunOutput } from '@/object-record/record-field/meta-types/utils/orderWorkflowRunOutput';
|
||||||
import { workflowRunOutputSchema } from '@/workflow/validation-schemas/workflowSchema';
|
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { JsonObject, JsonValue } from 'type-fest';
|
import { JsonObject, JsonValue } from 'type-fest';
|
||||||
@ -22,26 +21,7 @@ export const usePrecomputedJsonDraftValue = ({
|
|||||||
fieldDefinition.metadata.fieldName === 'output' &&
|
fieldDefinition.metadata.fieldName === 'output' &&
|
||||||
isDefined(draftValue)
|
isDefined(draftValue)
|
||||||
) {
|
) {
|
||||||
const parsedValue = workflowRunOutputSchema.safeParse(parsedJsonValue);
|
return orderWorkflowRunOutput(parsedJsonValue) as JsonObject;
|
||||||
if (!parsedValue.success) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderedWorkflowRunOutput: WorkflowRunOutput = {
|
|
||||||
...(isDefined(parsedValue.data.error)
|
|
||||||
? {
|
|
||||||
error: parsedValue.data.error,
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
...(isDefined(parsedValue.data.stepsOutput)
|
|
||||||
? {
|
|
||||||
stepsOutput: parsedValue.data.stepsOutput,
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
flow: parsedValue.data.flow,
|
|
||||||
};
|
|
||||||
|
|
||||||
return orderedWorkflowRunOutput as JsonObject;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsedJsonValue;
|
return parsedJsonValue;
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { WorkflowRunOutput } from '@/workflow/types/Workflow';
|
||||||
|
import { workflowRunOutputSchema } from '@/workflow/validation-schemas/workflowSchema';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { JsonValue } from 'type-fest';
|
||||||
|
|
||||||
|
export const orderWorkflowRunOutput = (value: JsonValue) => {
|
||||||
|
const parsedValue = workflowRunOutputSchema.safeParse(value);
|
||||||
|
if (!parsedValue.success) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderedWorkflowRunOutput: WorkflowRunOutput = {
|
||||||
|
...(isDefined(parsedValue.data.error)
|
||||||
|
? {
|
||||||
|
error: parsedValue.data.error,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(isDefined(parsedValue.data.stepsOutput)
|
||||||
|
? {
|
||||||
|
stepsOutput: parsedValue.data.stepsOutput,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
flow: parsedValue.data.flow,
|
||||||
|
};
|
||||||
|
|
||||||
|
return orderedWorkflowRunOutput;
|
||||||
|
};
|
||||||
@ -32,30 +32,85 @@ export const String: Story = {
|
|||||||
args: {
|
args: {
|
||||||
value: 'Hello',
|
value: 'Hello',
|
||||||
},
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const node = await canvas.findByText('Hello');
|
||||||
|
|
||||||
|
expect(node).toBeVisible();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StringWithSpecialCharacters: Story = {
|
||||||
|
args: {
|
||||||
|
value: 'Merry \n Christmas \t 🎄',
|
||||||
|
onNodeValueClick: fn(),
|
||||||
|
},
|
||||||
|
play: async ({ canvasElement, args }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const node = await canvas.findByText('Merry Christmas 🎄');
|
||||||
|
|
||||||
|
await userEvent.click(node);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(args.onNodeValueClick).toHaveBeenCalledWith(
|
||||||
|
'Merry \n Christmas \t 🎄',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Number: Story = {
|
export const Number: Story = {
|
||||||
args: {
|
args: {
|
||||||
value: 42,
|
value: 42,
|
||||||
},
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const node = await canvas.findByText('42');
|
||||||
|
|
||||||
|
expect(node).toBeVisible();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Boolean: Story = {
|
export const Boolean: Story = {
|
||||||
args: {
|
args: {
|
||||||
value: true,
|
value: true,
|
||||||
},
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const node = await canvas.findByText('true');
|
||||||
|
|
||||||
|
expect(node).toBeVisible();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Null: Story = {
|
export const Null: Story = {
|
||||||
args: {
|
args: {
|
||||||
value: null,
|
value: null,
|
||||||
},
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const node = await canvas.findByText('null');
|
||||||
|
|
||||||
|
expect(node).toBeVisible();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ArraySimple: Story = {
|
export const ArraySimple: Story = {
|
||||||
args: {
|
args: {
|
||||||
value: [1, 2, 3],
|
value: [1, 2, 3],
|
||||||
},
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const node = await canvas.findByText('[3]');
|
||||||
|
|
||||||
|
expect(node).toBeVisible();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ArrayEmpty: Story = {
|
export const ArrayEmpty: Story = {
|
||||||
@ -130,6 +185,15 @@ export const ObjectSimple: Story = {
|
|||||||
age: 30,
|
age: 30,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const name = await canvas.findByText('John Doe');
|
||||||
|
expect(name).toBeVisible();
|
||||||
|
|
||||||
|
const age = await canvas.findByText('30');
|
||||||
|
expect(age).toBeVisible();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ObjectEmpty: Story = {
|
export const ObjectEmpty: Story = {
|
||||||
@ -199,6 +263,15 @@ export const ObjectWithArray: Story = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const nestedArrayCount = await canvas.findByText('[2]');
|
||||||
|
expect(nestedArrayCount).toBeVisible();
|
||||||
|
|
||||||
|
const nestedObjectCounts = await canvas.findAllByText('{2}');
|
||||||
|
expect(nestedObjectCounts).toHaveLength(3);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NestedElementCanBeCollapsed: Story = {
|
export const NestedElementCanBeCollapsed: Story = {
|
||||||
@ -422,6 +495,15 @@ export const ReallyDeepNestedObject: Story = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const finalNodes = await canvas.findAllByText('end');
|
||||||
|
|
||||||
|
expect(finalNodes).toHaveLength(2);
|
||||||
|
expect(finalNodes[0]).toBeVisible();
|
||||||
|
expect(finalNodes[1]).toBeVisible();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LongText: Story = {
|
export const LongText: Story = {
|
||||||
@ -431,6 +513,21 @@ export const LongText: Story = {
|
|||||||
'Ut lobortis ultricies purus, sit amet porta eros. Suspendisse efficitur quam vitae diam imperdiet feugiat. Etiam vel bibendum elit.',
|
'Ut lobortis ultricies purus, sit amet porta eros. Suspendisse efficitur quam vitae diam imperdiet feugiat. Etiam vel bibendum elit.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const label = await canvas.findByText(
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum iaculis est tincidunt, sagittis neque vitae, sodales purus.',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(label).toBeVisible();
|
||||||
|
|
||||||
|
const value = await canvas.findByText(
|
||||||
|
'Ut lobortis ultricies purus, sit amet porta eros. Suspendisse efficitur quam vitae diam imperdiet feugiat. Etiam vel bibendum elit.',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(value).toBeVisible();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BlueHighlighting: Story = {
|
export const BlueHighlighting: Story = {
|
||||||
|
|||||||
Reference in New Issue
Block a user