Baptiste Devessier
2025-04-04 11:20:57 +02:00
committed by GitHub
parent bb40bc9929
commit 9353e777ea
10 changed files with 91 additions and 3 deletions

View File

@ -0,0 +1,31 @@
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
import { IconCopy, IconExclamationCircle } from 'twenty-ui/display';
export const useCopyToClipboard = () => {
const theme = useTheme();
const { enqueueSnackBar } = useSnackBar();
const { t } = useLingui();
const copyToClipboard = async (valueAsString: string) => {
try {
await navigator.clipboard.writeText(valueAsString);
enqueueSnackBar(t`Copied to clipboard`, {
variant: SnackBarVariant.Success,
icon: <IconCopy size={theme.icon.size.md} />,
duration: 2000,
});
} catch {
enqueueSnackBar(t`Couldn't copy to clipboard`, {
variant: SnackBarVariant.Error,
icon: <IconExclamationCircle size={16} color="red" />,
duration: 2000,
});
}
};
return { copyToClipboard };
};

View File

@ -2,9 +2,10 @@ import { SettingsAdminIndicatorHealthContext } from '@/settings/admin-panel/heal
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useContext } from 'react';
import { AdminPanelHealthServiceStatus } from '~/generated/graphql';
import { JsonTree } from 'twenty-ui/json-visualizer';
import { Section } from 'twenty-ui/layout';
import { AdminPanelHealthServiceStatus } from '~/generated/graphql';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
const StyledDetailsContainer = styled.div`
background-color: ${({ theme }) => theme.background.secondary};
@ -23,6 +24,7 @@ const StyledErrorMessage = styled.div`
export const JsonDataIndicatorHealthStatus = () => {
const { t } = useLingui();
const { copyToClipboard } = useCopyToClipboard();
const { indicatorHealth } = useContext(SettingsAdminIndicatorHealthContext);
@ -54,6 +56,7 @@ export const JsonDataIndicatorHealthStatus = () => {
emptyStringLabel={t`[empty string]`}
arrowButtonCollapsedLabel={t`Expand`}
arrowButtonExpandedLabel={t`Collapse`}
onNodeValueClick={copyToClipboard}
/>
</StyledDetailsContainer>
)}

View File

@ -19,11 +19,13 @@ import {
JsonTreeContextProvider,
ShouldExpandNodeInitiallyProps,
} from 'twenty-ui/json-visualizer';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
const { t, i18n } = useLingui();
const { getIcon } = useIcons();
const theme = useTheme();
const { copyToClipboard } = useCopyToClipboard();
const workflowRunId = useWorkflowRunIdOrThrow();
const workflowRun = useWorkflowRun({ workflowRunId });
@ -123,6 +125,7 @@ export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
arrowButtonExpandedLabel: t`Collapse`,
getNodeHighlighting,
shouldExpandNodeInitially: isFirstNodeDepthOfPreviousStep,
onNodeValueClick: copyToClipboard,
}}
>
<JsonNestedNode

View File

@ -9,17 +9,19 @@ import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-ac
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
import {
GetJsonNodeHighlighting,
isTwoFirstDepths,
JsonTree,
} from 'twenty-ui/json-visualizer';
import { useIcons } from 'twenty-ui/display';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => {
const { t, i18n } = useLingui();
const theme = useTheme();
const { getIcon } = useIcons();
const { copyToClipboard } = useCopyToClipboard();
const workflowRunId = useWorkflowRunIdOrThrow();
const workflowRun = useWorkflowRun({ workflowRunId });
@ -73,6 +75,7 @@ export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => {
? setRedHighlightingForEveryNode
: undefined
}
onNodeValueClick={copyToClipboard}
/>
</WorkflowRunStepJsonContainer>
</>

View File

@ -125,6 +125,7 @@ export {
IconDotsVertical,
IconDownload,
IconEditCircle,
IconExclamationCircle,
IconExternalLink,
IconEye,
IconEyeOff,

View File

@ -186,6 +186,7 @@ export {
IconDotsVertical,
IconDownload,
IconEditCircle,
IconExclamationCircle,
IconExternalLink,
IconEye,
IconEyeOff,

View File

@ -1,7 +1,9 @@
import { Meta, StoryObj } from '@storybook/react';
import {
expect,
fn,
userEvent,
waitFor,
waitForElementToBeRemoved,
within,
} from '@storybook/test';
@ -482,3 +484,32 @@ export const RedHighlighting: Story = {
expect(ageElement).toBeVisible();
},
};
export const CopyJsonNodeValue: Story = {
args: {
value: {
name: 'John Doe',
age: 30,
},
onNodeValueClick: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const nameValue = await canvas.findByText('John Doe');
await userEvent.click(nameValue);
await waitFor(() => {
expect(args.onNodeValueClick).toHaveBeenCalledWith('John Doe');
});
const ageValue = await canvas.findByText('30');
await userEvent.click(ageValue);
await waitFor(() => {
expect(args.onNodeValueClick).toHaveBeenCalledWith('30');
});
},
};

View File

@ -14,6 +14,7 @@ export const JsonTree = ({
emptyStringLabel,
arrowButtonCollapsedLabel,
arrowButtonExpandedLabel,
onNodeValueClick,
}: {
value: JsonValue;
getNodeHighlighting?: GetJsonNodeHighlighting;
@ -25,6 +26,7 @@ export const JsonTree = ({
emptyStringLabel: string;
arrowButtonCollapsedLabel: string;
arrowButtonExpandedLabel: string;
onNodeValueClick?: (valueAsString: string) => void;
}) => {
return (
<JsonTreeContextProvider
@ -36,6 +38,7 @@ export const JsonTree = ({
emptyStringLabel,
arrowButtonCollapsedLabel,
arrowButtonExpandedLabel,
onNodeValueClick,
}}
>
<JsonList depth={0}>

View File

@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
import { JsonNodeHighlighting } from '@ui/json-visualizer/types/JsonNodeHighlighting';
const StyledText = styled.span<{
@ -19,5 +20,15 @@ export const JsonNodeValue = ({
valueAsString: string;
highlighting?: JsonNodeHighlighting | undefined;
}) => {
return <StyledText highlighting={highlighting}>{valueAsString}</StyledText>;
const { onNodeValueClick } = useJsonTreeContextOrThrow();
const handleClick = () => {
onNodeValueClick?.(valueAsString);
};
return (
<StyledText highlighting={highlighting} onClick={handleClick}>
{valueAsString}
</StyledText>
);
};

View File

@ -13,6 +13,7 @@ export type JsonTreeContextType = {
emptyObjectLabel: string;
arrowButtonCollapsedLabel: string;
arrowButtonExpandedLabel: string;
onNodeValueClick?: (valueAsString: string) => void;
};
export const JsonTreeContext = createContext<JsonTreeContextType | undefined>(