JSON visualizer: Highlight the parent nodes of in-use nodes (#11373)
https://github.com/user-attachments/assets/5f31023d-b24f-40c8-a061-ffc0d02b63b0 Closes https://github.com/twentyhq/core-team-issues/issues/715
This commit is contained in:
committed by
GitHub
parent
4a4e65fe4a
commit
144a326709
@ -14,6 +14,7 @@ import { useLingui } from '@lingui/react/macro';
|
|||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { IconBrackets, useIcons } from 'twenty-ui/display';
|
import { IconBrackets, useIcons } from 'twenty-ui/display';
|
||||||
import {
|
import {
|
||||||
|
GetJsonNodeHighlighting,
|
||||||
JsonNestedNode,
|
JsonNestedNode,
|
||||||
JsonTreeContextProvider,
|
JsonTreeContextProvider,
|
||||||
ShouldExpandNodeInitiallyProps,
|
ShouldExpandNodeInitiallyProps,
|
||||||
@ -70,6 +71,7 @@ export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
|
|||||||
const variablesUsedInStep = getWorkflowVariablesUsedInStep({
|
const variablesUsedInStep = getWorkflowVariablesUsedInStep({
|
||||||
step,
|
step,
|
||||||
});
|
});
|
||||||
|
const allVariablesUsedInStep = Array.from(variablesUsedInStep);
|
||||||
|
|
||||||
const stepContext = getWorkflowRunStepContext({
|
const stepContext = getWorkflowRunStepContext({
|
||||||
context: workflowRun.context,
|
context: workflowRun.context,
|
||||||
@ -80,6 +82,21 @@ export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
|
|||||||
throw new Error('The input tab must be rendered with a non-empty context.');
|
throw new Error('The input tab must be rendered with a non-empty context.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getNodeHighlighting: GetJsonNodeHighlighting = (keyPath: string) => {
|
||||||
|
if (variablesUsedInStep.has(keyPath)) {
|
||||||
|
return 'blue';
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUsedVariableParent = allVariablesUsedInStep.some((variable) =>
|
||||||
|
variable.startsWith(keyPath),
|
||||||
|
);
|
||||||
|
if (isUsedVariableParent) {
|
||||||
|
return 'partial-blue';
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
const isFirstNodeDepthOfPreviousStep = ({
|
const isFirstNodeDepthOfPreviousStep = ({
|
||||||
keyPath,
|
keyPath,
|
||||||
depth,
|
depth,
|
||||||
@ -104,8 +121,7 @@ export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
|
|||||||
emptyStringLabel: t`[empty string]`,
|
emptyStringLabel: t`[empty string]`,
|
||||||
arrowButtonCollapsedLabel: t`Expand`,
|
arrowButtonCollapsedLabel: t`Expand`,
|
||||||
arrowButtonExpandedLabel: t`Collapse`,
|
arrowButtonExpandedLabel: t`Collapse`,
|
||||||
getNodeHighlighting: (keyPath) =>
|
getNodeHighlighting,
|
||||||
variablesUsedInStep.has(keyPath) ? 'blue' : undefined,
|
|
||||||
shouldExpandNodeInitially: isFirstNodeDepthOfPreviousStep,
|
shouldExpandNodeInitially: isFirstNodeDepthOfPreviousStep,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -447,6 +447,26 @@ export const BlueHighlighting: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PartialBlueHighlighting: Story = {
|
||||||
|
args: {
|
||||||
|
value: {
|
||||||
|
name: 'John Doe',
|
||||||
|
age: 30,
|
||||||
|
address: {
|
||||||
|
city: 'Paris',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getNodeHighlighting: (keyPath: string) =>
|
||||||
|
keyPath === 'address' ? 'partial-blue' : undefined,
|
||||||
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const ageElement = await canvas.findByText('age');
|
||||||
|
expect(ageElement).toBeVisible();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const RedHighlighting: Story = {
|
export const RedHighlighting: Story = {
|
||||||
args: {
|
args: {
|
||||||
value: {
|
value: {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { IconBrackets } from '@ui/display';
|
import { IconBrackets } from '@ui/display';
|
||||||
import { JsonNestedNode } from '@ui/json-visualizer/components/JsonNestedNode';
|
import { JsonNestedNode } from '@ui/json-visualizer/components/JsonNestedNode';
|
||||||
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
||||||
|
import { JsonNodeHighlighting } from '@ui/json-visualizer/types/JsonNodeHighlighting';
|
||||||
import { JsonArray } from 'type-fest';
|
import { JsonArray } from 'type-fest';
|
||||||
|
|
||||||
export const JsonArrayNode = ({
|
export const JsonArrayNode = ({
|
||||||
@ -8,11 +9,13 @@ export const JsonArrayNode = ({
|
|||||||
value,
|
value,
|
||||||
depth,
|
depth,
|
||||||
keyPath,
|
keyPath,
|
||||||
|
highlighting,
|
||||||
}: {
|
}: {
|
||||||
label?: string;
|
label?: string;
|
||||||
value: JsonArray;
|
value: JsonArray;
|
||||||
depth: number;
|
depth: number;
|
||||||
keyPath: string;
|
keyPath: string;
|
||||||
|
highlighting: JsonNodeHighlighting | undefined;
|
||||||
}) => {
|
}) => {
|
||||||
const { emptyArrayLabel } = useJsonTreeContextOrThrow();
|
const { emptyArrayLabel } = useJsonTreeContextOrThrow();
|
||||||
|
|
||||||
@ -29,6 +32,7 @@ export const JsonArrayNode = ({
|
|||||||
depth={depth}
|
depth={depth}
|
||||||
emptyElementsText={emptyArrayLabel}
|
emptyElementsText={emptyArrayLabel}
|
||||||
keyPath={keyPath}
|
keyPath={keyPath}
|
||||||
|
highlighting={highlighting}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,11 +6,12 @@ import { JsonList } from '@ui/json-visualizer/components/internal/JsonList';
|
|||||||
import { JsonNodeLabel } from '@ui/json-visualizer/components/internal/JsonNodeLabel';
|
import { JsonNodeLabel } from '@ui/json-visualizer/components/internal/JsonNodeLabel';
|
||||||
import { JsonNode } from '@ui/json-visualizer/components/JsonNode';
|
import { JsonNode } from '@ui/json-visualizer/components/JsonNode';
|
||||||
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
||||||
|
import { JsonNodeHighlighting } from '@ui/json-visualizer/types/JsonNodeHighlighting';
|
||||||
import { ANIMATION } from '@ui/theme';
|
import { ANIMATION } from '@ui/theme';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { JsonValue } from 'type-fest';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { JsonValue } from 'type-fest';
|
||||||
|
|
||||||
const StyledContainer = styled.li`
|
const StyledContainer = styled.li`
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -41,6 +42,7 @@ export const JsonNestedNode = ({
|
|||||||
emptyElementsText,
|
emptyElementsText,
|
||||||
depth,
|
depth,
|
||||||
keyPath,
|
keyPath,
|
||||||
|
highlighting,
|
||||||
}: {
|
}: {
|
||||||
label?: string;
|
label?: string;
|
||||||
Icon: IconComponent;
|
Icon: IconComponent;
|
||||||
@ -49,6 +51,7 @@ export const JsonNestedNode = ({
|
|||||||
emptyElementsText: string;
|
emptyElementsText: string;
|
||||||
depth: number;
|
depth: number;
|
||||||
keyPath: string;
|
keyPath: string;
|
||||||
|
highlighting?: JsonNodeHighlighting | undefined;
|
||||||
}) => {
|
}) => {
|
||||||
const { shouldExpandNodeInitially } = useJsonTreeContextOrThrow();
|
const { shouldExpandNodeInitially } = useJsonTreeContextOrThrow();
|
||||||
|
|
||||||
@ -115,7 +118,11 @@ export const JsonNestedNode = ({
|
|||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<StyledLabelContainer>
|
<StyledLabelContainer>
|
||||||
<JsonArrow isOpen={isOpen} onClick={handleArrowClick} />
|
<JsonArrow
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClick={handleArrowClick}
|
||||||
|
variant={highlighting === 'partial-blue' ? 'blue' : undefined}
|
||||||
|
/>
|
||||||
|
|
||||||
<JsonNodeLabel label={label} Icon={Icon} />
|
<JsonNodeLabel label={label} Icon={Icon} />
|
||||||
|
|
||||||
|
|||||||
@ -84,6 +84,7 @@ export const JsonNode = ({
|
|||||||
value={value}
|
value={value}
|
||||||
depth={depth}
|
depth={depth}
|
||||||
keyPath={keyPath}
|
keyPath={keyPath}
|
||||||
|
highlighting={highlighting}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -94,6 +95,7 @@ export const JsonNode = ({
|
|||||||
value={value}
|
value={value}
|
||||||
depth={depth}
|
depth={depth}
|
||||||
keyPath={keyPath}
|
keyPath={keyPath}
|
||||||
|
highlighting={highlighting}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { IconCube } from '@ui/display';
|
import { IconCube } from '@ui/display';
|
||||||
import { JsonNestedNode } from '@ui/json-visualizer/components/JsonNestedNode';
|
import { JsonNestedNode } from '@ui/json-visualizer/components/JsonNestedNode';
|
||||||
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
||||||
|
import { JsonNodeHighlighting } from '@ui/json-visualizer/types/JsonNodeHighlighting';
|
||||||
import { JsonObject } from 'type-fest';
|
import { JsonObject } from 'type-fest';
|
||||||
|
|
||||||
export const JsonObjectNode = ({
|
export const JsonObjectNode = ({
|
||||||
@ -8,11 +9,13 @@ export const JsonObjectNode = ({
|
|||||||
value,
|
value,
|
||||||
depth,
|
depth,
|
||||||
keyPath,
|
keyPath,
|
||||||
|
highlighting,
|
||||||
}: {
|
}: {
|
||||||
label?: string;
|
label?: string;
|
||||||
value: JsonObject;
|
value: JsonObject;
|
||||||
depth: number;
|
depth: number;
|
||||||
keyPath: string;
|
keyPath: string;
|
||||||
|
highlighting: JsonNodeHighlighting | undefined;
|
||||||
}) => {
|
}) => {
|
||||||
const { emptyObjectLabel } = useJsonTreeContextOrThrow();
|
const { emptyObjectLabel } = useJsonTreeContextOrThrow();
|
||||||
|
|
||||||
@ -29,6 +32,7 @@ export const JsonObjectNode = ({
|
|||||||
depth={depth}
|
depth={depth}
|
||||||
emptyElementsText={emptyObjectLabel}
|
emptyElementsText={emptyObjectLabel}
|
||||||
keyPath={keyPath}
|
keyPath={keyPath}
|
||||||
|
highlighting={highlighting}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -27,9 +27,11 @@ const MotionIconChevronDown = motion.create(IconChevronDown);
|
|||||||
export const JsonArrow = ({
|
export const JsonArrow = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
onClick,
|
onClick,
|
||||||
|
variant,
|
||||||
}: {
|
}: {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
|
variant?: 'blue';
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@ -44,7 +46,9 @@ export const JsonArrow = ({
|
|||||||
|
|
||||||
<MotionIconChevronDown
|
<MotionIconChevronDown
|
||||||
size={theme.icon.size.md}
|
size={theme.icon.size.md}
|
||||||
color={theme.font.color.secondary}
|
color={
|
||||||
|
variant === 'blue' ? theme.color.blue : theme.font.color.secondary
|
||||||
|
}
|
||||||
initial={false}
|
initial={false}
|
||||||
animate={{ rotate: isOpen ? 0 : -90 }}
|
animate={{ rotate: isOpen ? 0 : -90 }}
|
||||||
transition={{ duration: ANIMATION.duration.normal }}
|
transition={{ duration: ANIMATION.duration.normal }}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ const StyledText = styled.span<{
|
|||||||
highlighting === 'blue'
|
highlighting === 'blue'
|
||||||
? theme.adaptiveColors.blue4
|
? theme.adaptiveColors.blue4
|
||||||
: highlighting === 'red'
|
: highlighting === 'red'
|
||||||
? theme.font.color.danger
|
? theme.adaptiveColors.red4
|
||||||
: theme.font.color.tertiary};
|
: theme.font.color.tertiary};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
import { ThemeColor } from '@ui/theme';
|
import { ThemeColor } from '@ui/theme';
|
||||||
|
|
||||||
export type JsonNodeHighlighting = Extract<ThemeColor, 'blue' | 'red'>;
|
export type JsonNodeHighlighting =
|
||||||
|
| Extract<ThemeColor, 'blue' | 'red'>
|
||||||
|
| 'partial-blue';
|
||||||
|
|||||||
Reference in New Issue
Block a user