Set failed node's output as red (#11358)
| Error | Success | |--------|--------| |  |  | Closes https://github.com/twentyhq/core-team-issues/issues/716
This commit is contained in:
committed by
GitHub
parent
183dc40916
commit
bea75b9532
@ -11,6 +11,7 @@ import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/
|
|||||||
import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow';
|
import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import {
|
import {
|
||||||
IconBrackets,
|
IconBrackets,
|
||||||
JsonNestedNode,
|
JsonNestedNode,
|
||||||
@ -18,7 +19,6 @@ import {
|
|||||||
ShouldExpandNodeInitiallyProps,
|
ShouldExpandNodeInitiallyProps,
|
||||||
useIcons,
|
useIcons,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
|
export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
|
||||||
const { t, i18n } = useLingui();
|
const { t, i18n } = useLingui();
|
||||||
@ -105,7 +105,8 @@ 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`,
|
||||||
shouldHighlightNode: (keyPath) => variablesUsedInStep.has(keyPath),
|
getNodeHighlighting: (keyPath) =>
|
||||||
|
variablesUsedInStep.has(keyPath) ? 'blue' : undefined,
|
||||||
shouldExpandNodeInitially: isFirstNodeDepthOfPreviousStep,
|
shouldExpandNodeInitially: isFirstNodeDepthOfPreviousStep,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -9,7 +9,12 @@ import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-ac
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { isTwoFirstDepths, JsonTree, useIcons } from 'twenty-ui';
|
import {
|
||||||
|
GetJsonNodeHighlighting,
|
||||||
|
isTwoFirstDepths,
|
||||||
|
JsonTree,
|
||||||
|
useIcons,
|
||||||
|
} from 'twenty-ui';
|
||||||
|
|
||||||
export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => {
|
export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => {
|
||||||
const { t, i18n } = useLingui();
|
const { t, i18n } = useLingui();
|
||||||
@ -42,6 +47,8 @@ export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => {
|
|||||||
});
|
});
|
||||||
const headerType = getActionHeaderTypeOrThrow(stepDefinition.definition.type);
|
const headerType = getActionHeaderTypeOrThrow(stepDefinition.definition.type);
|
||||||
|
|
||||||
|
const setRedHighlightingForEveryNode: GetJsonNodeHighlighting = () => 'red';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WorkflowStepHeader
|
<WorkflowStepHeader
|
||||||
@ -61,6 +68,11 @@ export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => {
|
|||||||
emptyStringLabel={t`[empty string]`}
|
emptyStringLabel={t`[empty string]`}
|
||||||
arrowButtonCollapsedLabel={t`Expand`}
|
arrowButtonCollapsedLabel={t`Expand`}
|
||||||
arrowButtonExpandedLabel={t`Collapse`}
|
arrowButtonExpandedLabel={t`Collapse`}
|
||||||
|
getNodeHighlighting={
|
||||||
|
isDefined(stepOutput.error)
|
||||||
|
? setRedHighlightingForEveryNode
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</WorkflowRunStepJsonContainer>
|
</WorkflowRunStepJsonContainer>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -430,3 +430,35 @@ export const LongText: Story = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const BlueHighlighting: Story = {
|
||||||
|
args: {
|
||||||
|
value: {
|
||||||
|
name: 'John Doe',
|
||||||
|
age: 30,
|
||||||
|
},
|
||||||
|
getNodeHighlighting: () => 'blue',
|
||||||
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const ageElement = await canvas.findByText('age');
|
||||||
|
expect(ageElement).toBeVisible();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RedHighlighting: Story = {
|
||||||
|
args: {
|
||||||
|
value: {
|
||||||
|
name: 'John Doe',
|
||||||
|
age: 30,
|
||||||
|
},
|
||||||
|
getNodeHighlighting: () => 'red',
|
||||||
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const ageElement = await canvas.findByText('age');
|
||||||
|
expect(ageElement).toBeVisible();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@ -29,9 +29,9 @@ export const JsonNode = ({
|
|||||||
depth: number;
|
depth: number;
|
||||||
keyPath: string;
|
keyPath: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { shouldHighlightNode, emptyStringLabel } = useJsonTreeContextOrThrow();
|
const { getNodeHighlighting, emptyStringLabel } = useJsonTreeContextOrThrow();
|
||||||
|
|
||||||
const isHighlighted = shouldHighlightNode?.(keyPath) ?? false;
|
const highlighting = getNodeHighlighting?.(keyPath);
|
||||||
|
|
||||||
if (isNull(value)) {
|
if (isNull(value)) {
|
||||||
return (
|
return (
|
||||||
@ -39,7 +39,7 @@ export const JsonNode = ({
|
|||||||
label={label}
|
label={label}
|
||||||
valueAsString="null"
|
valueAsString="null"
|
||||||
Icon={IconCircleOff}
|
Icon={IconCircleOff}
|
||||||
isHighlighted={isHighlighted}
|
highlighting={highlighting}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ export const JsonNode = ({
|
|||||||
label={label}
|
label={label}
|
||||||
valueAsString={isNonEmptyString(value) ? value : emptyStringLabel}
|
valueAsString={isNonEmptyString(value) ? value : emptyStringLabel}
|
||||||
Icon={IconTypography}
|
Icon={IconTypography}
|
||||||
isHighlighted={isHighlighted}
|
highlighting={highlighting}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -61,7 +61,7 @@ export const JsonNode = ({
|
|||||||
label={label}
|
label={label}
|
||||||
valueAsString={String(value)}
|
valueAsString={String(value)}
|
||||||
Icon={IconNumber9}
|
Icon={IconNumber9}
|
||||||
isHighlighted={isHighlighted}
|
highlighting={highlighting}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ export const JsonNode = ({
|
|||||||
label={label}
|
label={label}
|
||||||
valueAsString={String(value)}
|
valueAsString={String(value)}
|
||||||
Icon={IconCheckbox}
|
Icon={IconCheckbox}
|
||||||
isHighlighted={isHighlighted}
|
highlighting={highlighting}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,12 @@ import { JsonList } from '@ui/json-visualizer/components/internal/JsonList';
|
|||||||
import { JsonNode } from '@ui/json-visualizer/components/JsonNode';
|
import { JsonNode } from '@ui/json-visualizer/components/JsonNode';
|
||||||
import { JsonTreeContextProvider } from '@ui/json-visualizer/components/JsonTreeContextProvider';
|
import { JsonTreeContextProvider } from '@ui/json-visualizer/components/JsonTreeContextProvider';
|
||||||
import { ShouldExpandNodeInitiallyProps } from '@ui/json-visualizer/contexts/JsonTreeContext';
|
import { ShouldExpandNodeInitiallyProps } from '@ui/json-visualizer/contexts/JsonTreeContext';
|
||||||
|
import { GetJsonNodeHighlighting } from '@ui/json-visualizer/types/GetJsonNodeHighlighting';
|
||||||
import { JsonValue } from 'type-fest';
|
import { JsonValue } from 'type-fest';
|
||||||
|
|
||||||
export const JsonTree = ({
|
export const JsonTree = ({
|
||||||
value,
|
value,
|
||||||
shouldHighlightNode,
|
getNodeHighlighting,
|
||||||
shouldExpandNodeInitially,
|
shouldExpandNodeInitially,
|
||||||
emptyArrayLabel,
|
emptyArrayLabel,
|
||||||
emptyObjectLabel,
|
emptyObjectLabel,
|
||||||
@ -15,7 +16,7 @@ export const JsonTree = ({
|
|||||||
arrowButtonExpandedLabel,
|
arrowButtonExpandedLabel,
|
||||||
}: {
|
}: {
|
||||||
value: JsonValue;
|
value: JsonValue;
|
||||||
shouldHighlightNode?: (keyPath: string) => boolean;
|
getNodeHighlighting?: GetJsonNodeHighlighting;
|
||||||
shouldExpandNodeInitially: (
|
shouldExpandNodeInitially: (
|
||||||
params: ShouldExpandNodeInitiallyProps,
|
params: ShouldExpandNodeInitiallyProps,
|
||||||
) => boolean;
|
) => boolean;
|
||||||
@ -28,7 +29,7 @@ export const JsonTree = ({
|
|||||||
return (
|
return (
|
||||||
<JsonTreeContextProvider
|
<JsonTreeContextProvider
|
||||||
value={{
|
value={{
|
||||||
shouldHighlightNode,
|
getNodeHighlighting,
|
||||||
shouldExpandNodeInitially,
|
shouldExpandNodeInitially,
|
||||||
emptyArrayLabel,
|
emptyArrayLabel,
|
||||||
emptyObjectLabel,
|
emptyObjectLabel,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { IconComponent } from '@ui/display';
|
|||||||
import { JsonListItem } from '@ui/json-visualizer/components/internal/JsonListItem';
|
import { JsonListItem } from '@ui/json-visualizer/components/internal/JsonListItem';
|
||||||
import { JsonNodeLabel } from '@ui/json-visualizer/components/internal/JsonNodeLabel';
|
import { JsonNodeLabel } from '@ui/json-visualizer/components/internal/JsonNodeLabel';
|
||||||
import { JsonNodeValue } from '@ui/json-visualizer/components/internal/JsonNodeValue';
|
import { JsonNodeValue } from '@ui/json-visualizer/components/internal/JsonNodeValue';
|
||||||
|
import { JsonNodeHighlighting } from '@ui/json-visualizer/types/JsonNodeHighlighting';
|
||||||
|
|
||||||
const StyledListItem = styled(JsonListItem)`
|
const StyledListItem = styled(JsonListItem)`
|
||||||
column-gap: ${({ theme }) => theme.spacing(2)};
|
column-gap: ${({ theme }) => theme.spacing(2)};
|
||||||
@ -10,7 +11,7 @@ const StyledListItem = styled(JsonListItem)`
|
|||||||
|
|
||||||
type JsonValueNodeProps = {
|
type JsonValueNodeProps = {
|
||||||
valueAsString: string;
|
valueAsString: string;
|
||||||
isHighlighted: boolean;
|
highlighting: JsonNodeHighlighting | undefined;
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
label: string;
|
label: string;
|
||||||
@ -29,13 +30,13 @@ export const JsonValueNode = (props: JsonValueNodeProps) => {
|
|||||||
<JsonNodeLabel
|
<JsonNodeLabel
|
||||||
label={props.label}
|
label={props.label}
|
||||||
Icon={props.Icon}
|
Icon={props.Icon}
|
||||||
isHighlighted={props.isHighlighted}
|
highlighting={props.highlighting}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<JsonNodeValue
|
<JsonNodeValue
|
||||||
valueAsString={props.valueAsString}
|
valueAsString={props.valueAsString}
|
||||||
isHighlighted={props.isHighlighted}
|
highlighting={props.highlighting}
|
||||||
/>
|
/>
|
||||||
</StyledListItem>
|
</StyledListItem>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,13 +1,30 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { IconComponent } from '@ui/display';
|
import { IconComponent } from '@ui/display';
|
||||||
|
import { JsonNodeHighlighting } from '@ui/json-visualizer/types/JsonNodeHighlighting';
|
||||||
|
|
||||||
const StyledLabelContainer = styled.span<{ isHighlighted?: boolean }>`
|
const StyledLabelContainer = styled.span<{
|
||||||
|
highlighting?: JsonNodeHighlighting;
|
||||||
|
}>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
background-color: ${({ theme, highlighting }) =>
|
||||||
border-color: ${({ theme }) => theme.border.color.medium};
|
highlighting === 'blue'
|
||||||
color: ${({ theme, isHighlighted }) =>
|
? theme.adaptiveColors.blue1
|
||||||
isHighlighted ? theme.color.blue : theme.font.color.primary};
|
: highlighting === 'red'
|
||||||
|
? theme.background.danger
|
||||||
|
: theme.background.transparent.lighter};
|
||||||
|
border-color: ${({ theme, highlighting }) =>
|
||||||
|
highlighting === 'blue'
|
||||||
|
? theme.adaptiveColors.blue2
|
||||||
|
: highlighting === 'red'
|
||||||
|
? theme.border.color.danger
|
||||||
|
: theme.border.color.medium};
|
||||||
|
color: ${({ theme, highlighting }) =>
|
||||||
|
highlighting === 'blue'
|
||||||
|
? theme.color.blue
|
||||||
|
: highlighting === 'red'
|
||||||
|
? theme.font.color.danger
|
||||||
|
: theme.font.color.primary};
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
@ -25,19 +42,25 @@ const StyledLabelContainer = styled.span<{ isHighlighted?: boolean }>`
|
|||||||
export const JsonNodeLabel = ({
|
export const JsonNodeLabel = ({
|
||||||
label,
|
label,
|
||||||
Icon,
|
Icon,
|
||||||
isHighlighted,
|
highlighting,
|
||||||
}: {
|
}: {
|
||||||
label: string;
|
label: string;
|
||||||
Icon: IconComponent;
|
Icon: IconComponent;
|
||||||
isHighlighted?: boolean;
|
highlighting?: JsonNodeHighlighting | undefined;
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledLabelContainer isHighlighted={isHighlighted}>
|
<StyledLabelContainer highlighting={highlighting}>
|
||||||
<Icon
|
<Icon
|
||||||
size={theme.icon.size.md}
|
size={theme.icon.size.md}
|
||||||
color={isHighlighted ? theme.color.blue : theme.font.color.tertiary}
|
color={
|
||||||
|
highlighting === 'blue'
|
||||||
|
? theme.color.blue
|
||||||
|
: highlighting === 'red'
|
||||||
|
? theme.font.color.danger
|
||||||
|
: theme.font.color.tertiary
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
|
|||||||
@ -1,16 +1,23 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { JsonNodeHighlighting } from '@ui/json-visualizer/types/JsonNodeHighlighting';
|
||||||
|
|
||||||
const StyledText = styled.span<{ isHighlighted?: boolean }>`
|
const StyledText = styled.span<{
|
||||||
color: ${({ theme, isHighlighted }) =>
|
highlighting: JsonNodeHighlighting | undefined;
|
||||||
isHighlighted ? theme.adaptiveColors.blue4 : theme.font.color.tertiary};
|
}>`
|
||||||
|
color: ${({ theme, highlighting }) =>
|
||||||
|
highlighting === 'blue'
|
||||||
|
? theme.adaptiveColors.blue4
|
||||||
|
: highlighting === 'red'
|
||||||
|
? theme.font.color.danger
|
||||||
|
: theme.font.color.tertiary};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const JsonNodeValue = ({
|
export const JsonNodeValue = ({
|
||||||
valueAsString,
|
valueAsString,
|
||||||
isHighlighted,
|
highlighting,
|
||||||
}: {
|
}: {
|
||||||
valueAsString: string;
|
valueAsString: string;
|
||||||
isHighlighted?: boolean;
|
highlighting?: JsonNodeHighlighting | undefined;
|
||||||
}) => {
|
}) => {
|
||||||
return <StyledText isHighlighted={isHighlighted}>{valueAsString}</StyledText>;
|
return <StyledText highlighting={highlighting}>{valueAsString}</StyledText>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
|
import { GetJsonNodeHighlighting } from '@ui/json-visualizer/types/GetJsonNodeHighlighting';
|
||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
|
||||||
export type ShouldExpandNodeInitiallyProps = { keyPath: string; depth: number };
|
export type ShouldExpandNodeInitiallyProps = { keyPath: string; depth: number };
|
||||||
|
|
||||||
export type JsonTreeContextType = {
|
export type JsonTreeContextType = {
|
||||||
shouldHighlightNode?: (keyPath: string) => boolean;
|
getNodeHighlighting?: GetJsonNodeHighlighting;
|
||||||
shouldExpandNodeInitially: (
|
shouldExpandNodeInitially: (
|
||||||
params: ShouldExpandNodeInitiallyProps,
|
params: ShouldExpandNodeInitiallyProps,
|
||||||
) => boolean;
|
) => boolean;
|
||||||
|
|||||||
@ -7,5 +7,7 @@ export * from './components/JsonTreeContextProvider';
|
|||||||
export * from './components/JsonValueNode';
|
export * from './components/JsonValueNode';
|
||||||
export * from './contexts/JsonTreeContext';
|
export * from './contexts/JsonTreeContext';
|
||||||
export * from './hooks/useJsonTreeContextOrThrow';
|
export * from './hooks/useJsonTreeContextOrThrow';
|
||||||
|
export * from './types/GetJsonNodeHighlighting';
|
||||||
|
export * from './types/JsonNodeHighlighting';
|
||||||
export * from './utils/isArray';
|
export * from './utils/isArray';
|
||||||
export * from './utils/isTwoFirstDepths';
|
export * from './utils/isTwoFirstDepths';
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
import { JsonNodeHighlighting } from '@ui/json-visualizer/types/JsonNodeHighlighting';
|
||||||
|
|
||||||
|
export type GetJsonNodeHighlighting = (
|
||||||
|
keyPath: string,
|
||||||
|
) => JsonNodeHighlighting | undefined;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import { ThemeColor } from '@ui/theme';
|
||||||
|
|
||||||
|
export type JsonNodeHighlighting = Extract<ThemeColor, 'blue' | 'red'>;
|
||||||
Reference in New Issue
Block a user