Skip to content

Commit d2453e1

Browse files
committed
Add cli command modal
1 parent 9d8e758 commit d2453e1

File tree

21 files changed

+681
-223
lines changed

21 files changed

+681
-223
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react';
2+
3+
import copy from 'copy-to-clipboard';
4+
5+
import { render, fireEvent, screen, act } from '@/test-utils/rtl';
6+
7+
import CopyTextButton from '../copy-text-button';
8+
9+
jest.mock('copy-to-clipboard', jest.fn);
10+
11+
describe('CopyTextButton', () => {
12+
const text = 'text to copy';
13+
14+
it('copies JSON to clipboard', () => {
15+
render(<CopyTextButton textToCopy={text} />);
16+
17+
const copyButton = screen.getByRole('button');
18+
fireEvent.click(copyButton);
19+
20+
expect(copy).toHaveBeenCalledWith(text);
21+
});
22+
23+
it('show tooltip for 1 second and remove it', () => {
24+
jest.useFakeTimers();
25+
26+
render(<CopyTextButton textToCopy={text} />);
27+
28+
const copyButton = screen.getByRole('button');
29+
fireEvent.click(copyButton);
30+
const visibleTooltip = screen.getByText('Copied');
31+
expect(visibleTooltip).toBeInTheDocument();
32+
33+
act(() => {
34+
jest.advanceTimersByTime(1000 + 500); //hide + animation duration
35+
});
36+
37+
// Ensure the tooltip is hidden after 1000ms
38+
const hiddenTooltip = screen.queryByText('Copied');
39+
expect(hiddenTooltip).not.toBeInTheDocument();
40+
41+
jest.useRealTimers();
42+
});
43+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use client';
2+
import React, { useEffect, useState } from 'react';
3+
4+
import { Button, SHAPE, SIZE, KIND } from 'baseui/button';
5+
import { ACCESSIBILITY_TYPE, Tooltip } from 'baseui/tooltip';
6+
import copy from 'copy-to-clipboard';
7+
import { MdCopyAll } from 'react-icons/md';
8+
9+
import type { Props } from './copy-text-button.types';
10+
11+
export default function CopyTextButton({
12+
textToCopy,
13+
children,
14+
...buttonProps
15+
}: Props) {
16+
const [showTooltip, setShowTooltip] = useState(false);
17+
18+
const buttonChildren = children === undefined ? <MdCopyAll /> : children;
19+
20+
useEffect(() => {
21+
if (showTooltip) {
22+
const timer = setTimeout(() => {
23+
setShowTooltip(false);
24+
}, 1000);
25+
return () => clearTimeout(timer);
26+
}
27+
}, [showTooltip]);
28+
return (
29+
<Tooltip
30+
animateOutTime={400}
31+
isOpen={showTooltip}
32+
showArrow
33+
placement="bottom"
34+
accessibilityType={ACCESSIBILITY_TYPE.tooltip}
35+
content={() => <>Copied</>}
36+
>
37+
<Button
38+
onClick={() => {
39+
copy(textToCopy);
40+
setShowTooltip(true);
41+
}}
42+
size={SIZE.mini}
43+
shape={SHAPE.circle}
44+
kind={KIND.secondary}
45+
{...buttonProps}
46+
>
47+
{buttonChildren}
48+
</Button>
49+
</Tooltip>
50+
);
51+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { type ButtonProps } from 'baseui/button';
2+
3+
export type Props = {
4+
textToCopy: string;
5+
} & ButtonProps;

src/components/page-tabs/page-tabs.styles.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,16 @@ export const overrides = {
2828
alignSelf: 'center',
2929
...getMediaQueryMargins($theme, (margin) => ({
3030
maxWidth: `${$theme.grid.maxWidth + 2 * margin}px`,
31+
paddingRight: `${margin}px`,
32+
paddingLeft: `${margin}px`,
3133
})),
3234
}),
3335
},
3436
TabList: {
35-
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
36-
...getMediaQueryMargins($theme),
37+
style: {
3738
paddingBottom: 0,
3839
marginBottom: 0,
39-
}),
40+
},
4041
},
4142
TabBorder: {
4243
style: {

src/components/page-tabs/page-tabs.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default function PageTabs({
99
tabList,
1010
selectedTab,
1111
setSelectedTab,
12+
endEnhancer,
1213
}: Props) {
1314
return (
1415
<Tabs
@@ -17,6 +18,7 @@ export default function PageTabs({
1718
setSelectedTab(activeKey);
1819
}}
1920
overrides={overrides.tabs}
21+
endEnhancer={endEnhancer}
2022
>
2123
{tabList.map((tab) => (
2224
<Tab

src/components/page-tabs/page-tabs.types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type React from 'react';
22

33
import { type IconProps } from 'baseui/icon';
4+
import { type TabsProps } from 'baseui/tabs-motion';
45

56
export type PageTab = {
67
key: string;
@@ -18,4 +19,5 @@ export type Props = {
1819
tabList: PageTabsList;
1920
selectedTab: React.Key;
2021
setSelectedTab: (value: React.Key) => void;
22+
endEnhancer?: TabsProps['endEnhancer'];
2123
};
Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import React from 'react';
22

3-
import copy from 'copy-to-clipboard';
4-
5-
import { render, fireEvent, screen, act } from '@/test-utils/rtl';
3+
import { render } from '@/test-utils/rtl';
64

75
import WorkflowSummaryTabJsonView from '../workflow-history-event-details-json';
86

9-
// Mock dependencies
10-
jest.mock('copy-to-clipboard', jest.fn);
7+
jest.mock('@/components/copy-text-button/copy-text-button', () =>
8+
jest.fn(({ textToCopy }) => <div>Copy Button: {textToCopy}</div>)
9+
);
1110

1211
jest.mock('@/components/pretty-json/pretty-json', () =>
1312
jest.fn(() => <div>PrettyJson Mock</div>)
@@ -24,33 +23,13 @@ describe('WorkflowSummaryTabJsonView Component', () => {
2423
expect(getByText('PrettyJson Mock')).toBeInTheDocument();
2524
});
2625

27-
it('copies JSON to clipboard', () => {
28-
render(<WorkflowSummaryTabJsonView entryValue={inputJson} />);
29-
30-
const copyButton = screen.getByRole('button');
31-
fireEvent.click(copyButton);
32-
33-
expect(copy).toHaveBeenCalledWith(JSON.stringify(inputJson, null, '\t'));
34-
});
35-
36-
it('show tooltip for 1 second and remove it', () => {
37-
jest.useFakeTimers();
38-
39-
render(<WorkflowSummaryTabJsonView entryValue={inputJson} />);
40-
41-
const copyButton = screen.getByRole('button');
42-
fireEvent.click(copyButton);
43-
const visibleTooltip = screen.getByText('Copied');
44-
expect(visibleTooltip).toBeInTheDocument();
45-
46-
act(() => {
47-
jest.advanceTimersByTime(1000 + 500); //hide + animation duration
48-
});
49-
50-
// Ensure the tooltip is hidden after 1000ms
51-
const hiddenTooltip = screen.queryByText('Copied');
52-
expect(hiddenTooltip).not.toBeInTheDocument();
26+
it('renders copy text button and pass the correct text', () => {
27+
const { getByText } = render(
28+
<WorkflowSummaryTabJsonView entryValue={inputJson} />
29+
);
5330

54-
jest.useRealTimers();
31+
const copyButton = getByText(/Copy Button/);
32+
expect(copyButton).toBeInTheDocument();
33+
expect(copyButton.innerHTML).toMatch(JSON.stringify(inputJson, null, '\t'));
5534
});
5635
});

src/views/workflow-history/workflow-history-event-details-json/workflow-history-event-details-json.tsx

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
'use client';
2-
import React, { useEffect, useState } from 'react';
3-
4-
import { Button, KIND as BUTTON_KIND, SHAPE, SIZE } from 'baseui/button';
5-
import { ACCESSIBILITY_TYPE, Tooltip } from 'baseui/tooltip';
6-
import copy from 'copy-to-clipboard';
7-
import { MdCopyAll } from 'react-icons/md';
2+
import React, { useMemo } from 'react';
83

4+
import CopyTextButton from '@/components/copy-text-button/copy-text-button';
95
import PrettyJson from '@/components/pretty-json/pretty-json';
106
import useStyletronClasses from '@/hooks/use-styletron-classes';
117

@@ -14,40 +10,15 @@ import type { Props } from './workflow-history-event-details-json.types';
1410

1511
export default function WorkflowHistoryEventDetailsJson({ entryValue }: Props) {
1612
const { cls } = useStyletronClasses(cssStyles);
17-
const [showTooltip, setShowTooltip] = useState(false);
1813

19-
useEffect(() => {
20-
if (showTooltip) {
21-
const timer = setTimeout(() => {
22-
setShowTooltip(false);
23-
}, 1000);
24-
return () => clearTimeout(timer);
25-
}
26-
}, [showTooltip]);
14+
const textToCopy = useMemo(() => {
15+
return JSON.stringify(entryValue, null, '\t');
16+
}, [entryValue]);
2717
return (
2818
<div className={cls.jsonViewWrapper}>
2919
<div className={cls.jsonViewContainer}>
3020
<div className={cls.jsonViewHeader}>
31-
<Tooltip
32-
animateOutTime={400}
33-
isOpen={showTooltip}
34-
showArrow
35-
placement="bottom"
36-
accessibilityType={ACCESSIBILITY_TYPE.tooltip}
37-
content={() => <>Copied</>}
38-
>
39-
<Button
40-
onClick={() => {
41-
copy(JSON.stringify(entryValue, null, '\t'));
42-
setShowTooltip(true);
43-
}}
44-
size={SIZE.mini}
45-
shape={SHAPE.circle}
46-
kind={BUTTON_KIND.secondary}
47-
>
48-
<MdCopyAll />
49-
</Button>
50-
</Tooltip>
21+
<CopyTextButton textToCopy={textToCopy} />
5122
</div>
5223
<PrettyJson json={entryValue} />
5324
</div>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { type CliCommandGroupConfig } from '../workflow-page-cli-commands-modal/workflow-page-cli-commands-modal.types';
2+
3+
const workflowPageCliCommandsGroupsConfig = [
4+
{
5+
name: 'domain',
6+
title: 'Domain',
7+
},
8+
{
9+
name: 'workflow',
10+
title: 'Workflow',
11+
},
12+
] as const satisfies CliCommandGroupConfig[];
13+
14+
export default workflowPageCliCommandsGroupsConfig;
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { type CliCommandConfig } from '../workflow-page-cli-commands-modal/workflow-page-cli-commands-modal.types';
2+
3+
const workflowPageCliCommandsConfig: CliCommandConfig[] = [
4+
{
5+
label: 'Register a domain (local only)',
6+
command:
7+
'cadence --domain {domain-name} domain register --global_domain false',
8+
group: 'domain',
9+
},
10+
{
11+
label: 'List domain settings',
12+
command:
13+
'cadence --env {staging|prod|prod02} --domain {domain-name} domain describe',
14+
group: 'domain',
15+
},
16+
{
17+
label: 'Update domain active cluster',
18+
description: 'Make sure the domain_data has UberIgnoringLisa:true',
19+
command:
20+
'cadence --env {staging|prod|prod02} --domain {domain-name} domain update -active_cluster {cluster-name}',
21+
group: 'domain',
22+
},
23+
{
24+
label: 'Update domain bad binary',
25+
command:
26+
'cadence --env {staging|prod|prod02} --domain {domain-name} domain update --add_bad_binary {bad-binary-SHA} --reason \'"{reason}"\'',
27+
group: 'domain',
28+
},
29+
// workflow commands
30+
{
31+
label: 'Run a workflow',
32+
command:
33+
'cadence --env {staging|prod|prod02} --domain {domain-name} workflow run --tl {task-list-name} --wt {workflow-type-name} --et 60 -i \'"{input-string}"\'',
34+
group: 'workflow',
35+
},
36+
{
37+
label: 'See workflow settings',
38+
command:
39+
'cadence --env {staging|prod|prod02} --domain {domain-name} workflow describe -w {workflow-id} -r {run-id}',
40+
group: 'workflow',
41+
},
42+
{
43+
label: 'See workflow history',
44+
command:
45+
'cadence --env {staging|prod|prod02} --domain {domain-name} workflow show -w {workflow-id} -r {run-id}',
46+
group: 'workflow',
47+
},
48+
{
49+
label: 'Signal a workflow',
50+
command:
51+
'cadence --env {staging|prod|prod02} --domain {domain-name} workflow signal -w {workflow-id} -r {run-id} --name {signal-name} --input \'"{signal-payload}"\'',
52+
group: 'workflow',
53+
},
54+
{
55+
label: 'Reset a workflow',
56+
command:
57+
'cadence workflow reset -w {workflow-id} -r {run-id} --event_id {event-id} --reason \'"{reason}"\' --reset_type {reset-type} --reset_bad_binary_checksum {bad-binary-SHA}',
58+
description: `Use event_id to reset from specific event or reset_type with one of the following values BadBinary, DecisionCompletedTime, FirstDecisionScheduled, FirstDecisionCompleted, LastDecisionScheduled, LastDecisionCompleted, LastContinuedAsNew`,
59+
group: 'workflow',
60+
},
61+
{
62+
label: 'Reset a batch of workflows',
63+
command:
64+
'cadence workflow reset-batch --query \'"{query}"\' --only_non_deterministic --reason \'"{reason}"\' --reset_type {reset-type}',
65+
description: `Where query can be any query that returns a list of workflows and reset_type can be BadBinary, DecisionCompletedTime, FirstDecisionScheduled, FirstDecisionCompleted, LastDecisionScheduled, LastDecisionCompleted, LastContinuedAsNew`,
66+
group: 'workflow',
67+
},
68+
{
69+
label: 'Restart a workflow',
70+
command: 'cadence workflow restart -w {workflow-id} -r {run-id}',
71+
description:
72+
'Starts a new execution using the same input & terminates previous execution',
73+
group: 'workflow',
74+
},
75+
{
76+
label: 'List closed workflows',
77+
command:
78+
'cadence --env {staging|prod|prod02} --domain {domain-name} workflow {list|listall}',
79+
group: 'workflow',
80+
},
81+
{
82+
label: 'List open workflows',
83+
command:
84+
'cadence --env {staging|prod|prod02} --domain {domain-name} workflow {list|listall} --open',
85+
group: 'workflow',
86+
},
87+
{
88+
label: 'Query for a workflow',
89+
command:
90+
'cadence workflow {list|listall} --query \'(CustomKeywordField = "keyword1" and CustomIntField >= 5) or CustomKeywordField = "keyword2" and CloseTime = missing\'',
91+
group: 'workflow',
92+
},
93+
];
94+
95+
export default workflowPageCliCommandsConfig;

0 commit comments

Comments
 (0)