Skip to content

Commit 1c518f5

Browse files
authored
Merge pull request #311 from eccenca/bugfix/remove-errors-CMEM-6724
Extend activity control widget to include other interactions beside actions and context menu (CMEM-6724)
2 parents d667c94 + 86ea778 commit 1c518f5

File tree

4 files changed

+190
-14
lines changed

4 files changed

+190
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
99
### Added
1010

1111
- Extended existing height and readOnly props from `CodeEditorProps` to `AutoSuggestionProps` & `ExtendedCodeEditorProps` to be configurable from `<CodeAutocompleteField />`
12+
- Added additional actions to `<ActivityControlWidget />`
1213

1314
## [24.3.0] - 2025-06-05
1415

src/cmem/ActivityControl/ActivityControlWidget.stories.tsx

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1-
import React from "react";
1+
import React, { useMemo, useState } from "react";
22
import { loremIpsum } from "react-lorem-ipsum";
3+
import { OverlaysProvider } from "@blueprintjs/core";
34
import { Meta, StoryFn } from "@storybook/react";
45

56
import { helpersArgTypes } from "../../../.storybook/helpers";
6-
import { ActivityControlWidget, Tag, TagList } from "../../../index";
7+
import {
8+
ActivityControlWidget,
9+
ActivityControlWidgetAction,
10+
IconButton,
11+
SimpleDialog,
12+
Tag,
13+
TagList,
14+
} from "../../../index";
715

816
export default {
917
title: "Cmem/ActivityControlWidget",
@@ -19,7 +27,7 @@ const Template: StoryFn<typeof ActivityControlWidget> = (args) => <ActivityContr
1927

2028
export const FullExample = Template.bind({});
2129

22-
const actions = [
30+
const actions: ActivityControlWidgetAction[] = [
2331
{
2432
"data-test-id": "activity-reload-activity",
2533
icon: "item-reload",
@@ -57,8 +65,8 @@ const commonWidgetArgs = {
5765
progressSpinner: {
5866
intent: "none",
5967
value: 0.5,
60-
},
61-
};
68+
} as const,
69+
} as const;
6270

6371
FullExample.args = {
6472
...commonWidgetArgs,
@@ -80,3 +88,33 @@ WidgetWithTags.args = {
8088
...commonWidgetArgs,
8189
tags: widgetTags,
8290
};
91+
92+
export const WidgetWithAdditionalActions: StoryFn<typeof ActivityControlWidget> = (args) => {
93+
const [isOpen, setIsOpen] = useState(false);
94+
95+
const params = useMemo(
96+
() => ({
97+
...commonWidgetArgs,
98+
...args,
99+
additionalActions: args.additionalActions ?? [
100+
<IconButton name="application-explore" onClick={() => setIsOpen(true)} />,
101+
],
102+
}),
103+
[]
104+
);
105+
106+
return (
107+
<OverlaysProvider>
108+
<ActivityControlWidget {...params} />
109+
<SimpleDialog
110+
title="Additional actions dialog"
111+
isOpen={isOpen}
112+
onClose={() => setIsOpen(false)}
113+
canOutsideClickClose
114+
canEscapeKeyClose
115+
>
116+
Modal content
117+
</SimpleDialog>
118+
</OverlaysProvider>
119+
);
120+
};

src/cmem/ActivityControl/ActivityControlWidget.tsx

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ export interface ActivityControlWidgetProps extends TestableComponent {
8181
* execution timer messages for waiting and running times.
8282
*/
8383
timerExecutionMsg?: JSX.Element | null;
84+
/**
85+
* additional actions that can serve as a complex component, positioned between the default actions and the context menu
86+
*/
87+
additionalActions?: React.ReactElement<unknown>[];
8488
}
8589

8690
interface IActivityContextMenu extends TestableComponent {
@@ -110,11 +114,13 @@ interface IActivityMenuAction extends ActivityControlWidgetAction {
110114
/** Shows the status of activities and supports actions on these activities. */
111115
export function ActivityControlWidget(props: ActivityControlWidgetProps) {
112116
const {
113-
"data-test-id": dataTestId,
117+
"data-test-id": dataTestIdLegacy,
118+
"data-testid": dataTestId,
114119
progressBar,
115120
progressSpinner,
116121
activityActions,
117122
activityContextMenu,
123+
additionalActions,
118124
small,
119125
border,
120126
hasSpacing,
@@ -126,10 +132,19 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) {
126132
} = props;
127133
const spinnerClassNames = (progressSpinner?.className ?? "") + ` ${eccgui}-spinner--permanent`;
128134
const widget = (
129-
<OverviewItem data-test-id={dataTestId} hasSpacing={border || hasSpacing} densityHigh={small}>
135+
<OverviewItem
136+
data-test-id={dataTestIdLegacy}
137+
data-testid={dataTestId}
138+
hasSpacing={border || hasSpacing}
139+
densityHigh={small}
140+
>
130141
{progressBar && <ProgressBar {...progressBar} />}
131142
{(progressSpinner || progressSpinnerFinishedIcon) && (
132-
<OverviewItemDepiction keepColors>
143+
<OverviewItemDepiction
144+
data-testid={dataTestId ? `${dataTestId}-progress-spinner` : undefined}
145+
data-test-id={dataTestIdLegacy ? `${dataTestIdLegacy}-progress-spinner` : undefined}
146+
keepColors
147+
>
133148
{progressSpinnerFinishedIcon ? (
134149
React.cloneElement(progressSpinnerFinishedIcon as JSX.Element, { small, large: !small })
135150
) : (
@@ -145,13 +160,21 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) {
145160
)}
146161
<OverviewItemDescription>
147162
{props.label && (
148-
<OverviewItemLine small={small}>
163+
<OverviewItemLine
164+
data-testid={dataTestId ? `${dataTestId}-label` : undefined}
165+
data-test-id={dataTestIdLegacy ? `${dataTestIdLegacy}-label` : undefined}
166+
small={small}
167+
>
149168
{React.cloneElement(labelWrapper, {}, props.label)}
150169
{timerExecutionMsg && (props.statusMessage || tags) && <>&nbsp;({timerExecutionMsg})</>}
151170
</OverviewItemLine>
152171
)}
153172
{(props.statusMessage || tags) && (
154-
<OverviewItemLine small>
173+
<OverviewItemLine
174+
data-testid={dataTestId ? `${dataTestId}-status-message` : undefined}
175+
data-test-id={dataTestIdLegacy ? `${dataTestIdLegacy}-status-message` : undefined}
176+
small
177+
>
155178
{tags}
156179
{props.statusMessage && (
157180
<OverflowText passDown>
@@ -172,28 +195,43 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) {
172195
</OverviewItemLine>
173196
)}
174197
{timerExecutionMsg && !(props.statusMessage || tags) && (
175-
<OverviewItemLine small>{timerExecutionMsg}</OverviewItemLine>
198+
<OverviewItemLine
199+
data-testid={dataTestId ? `${dataTestId}-status-message` : undefined}
200+
data-test-id={dataTestIdLegacy ? `${dataTestIdLegacy}-status-message` : undefined}
201+
small
202+
>
203+
{timerExecutionMsg}
204+
</OverviewItemLine>
176205
)}
177206
</OverviewItemDescription>
178-
<OverviewItemActions>
207+
<OverviewItemActions
208+
data-testid={dataTestId ? `${dataTestId}-actions` : undefined}
209+
data-test-id={dataTestIdLegacy ? `${dataTestIdLegacy}-actions` : undefined}
210+
>
179211
{activityActions &&
180212
activityActions.map((action, idx) => {
181213
return (
182214
<IconButton
183-
key={typeof action.icon === "string" ? action.icon : action["data-test-id"] ?? idx}
215+
key={
216+
typeof action.icon === "string"
217+
? action.icon
218+
: action["data-test-id"] ?? action["data-testid"] ?? idx
219+
}
184220
data-test-id={action["data-test-id"]}
221+
data-testid={action["data-testid"]}
185222
name={action.icon}
186223
text={action.tooltip}
187224
onClick={action.action}
188225
disabled={action.disabled}
189-
hasStateWarning={action.hasStateWarning}
226+
intent={action.hasStateWarning ? "warning" : undefined}
190227
tooltipProps={{
191228
hoverOpenDelay: 200,
192229
placement: "bottom",
193230
}}
194231
/>
195232
);
196233
})}
234+
{additionalActions}
197235
{activityContextMenu && activityContextMenu.menuItems.length > 0 && (
198236
<ContextMenu
199237
data-test-id={activityContextMenu["data-test-id"]}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React from "react";
2+
import { fireEvent, render, screen } from "@testing-library/react";
3+
4+
import "@testing-library/jest-dom";
5+
6+
import { IconButton, Tag, TagList } from "../../../index";
7+
import { ActivityControlWidget, ActivityControlWidgetAction } from "../ActivityControlWidget";
8+
9+
describe("ActivityControlWidget", () => {
10+
it("Renders basic widget with actions and handles clicks", () => {
11+
const mockAction1 = jest.fn();
12+
const mockAction2 = jest.fn();
13+
const actions: ActivityControlWidgetAction[] = [
14+
{
15+
"data-testid": "action-1",
16+
icon: "item-reload",
17+
action: mockAction1,
18+
tooltip: "Action 1",
19+
},
20+
{
21+
"data-testid": "action-2",
22+
icon: "item-start",
23+
action: mockAction2,
24+
tooltip: "Action 2",
25+
},
26+
];
27+
28+
render(
29+
<ActivityControlWidget
30+
label="Basic widget"
31+
data-testid="basic-widget"
32+
activityActions={actions}
33+
statusMessage="Status message"
34+
/>
35+
);
36+
37+
const button1 = screen.getByTestId("action-1");
38+
const button2 = screen.getByTestId("action-2");
39+
40+
const label = screen.getByTestId("basic-widget-label");
41+
const statusMessage = screen.getByTestId("basic-widget-status-message");
42+
const actionsContainer = screen.getByTestId("basic-widget-actions");
43+
44+
expect(label).toBeInTheDocument();
45+
expect(statusMessage).toBeInTheDocument();
46+
expect(actionsContainer).toBeInTheDocument();
47+
48+
expect(label).toHaveTextContent("Basic widget");
49+
expect(statusMessage).toHaveTextContent("Status message");
50+
51+
expect(button1).toBeInTheDocument();
52+
expect(button2).toBeInTheDocument();
53+
54+
fireEvent.click(button1);
55+
expect(mockAction1).toHaveBeenCalledTimes(1);
56+
57+
fireEvent.click(button2);
58+
expect(mockAction2).toHaveBeenCalledTimes(1);
59+
});
60+
61+
it("Renders widget with tags", () => {
62+
const tags = (
63+
<TagList>
64+
<Tag>Tag one</Tag>
65+
<Tag>Other tag</Tag>
66+
</TagList>
67+
);
68+
render(<ActivityControlWidget label="Widget with tags" tags={tags} data-testid="widget-with-tags" />);
69+
70+
const label = screen.getByTestId("widget-with-tags-label");
71+
const statusMessage = screen.getByTestId("widget-with-tags-status-message");
72+
73+
expect(label).toBeInTheDocument();
74+
expect(statusMessage).toBeInTheDocument();
75+
76+
expect(label).toHaveTextContent("Widget with tags");
77+
expect(statusMessage).toHaveTextContent("Tag one");
78+
expect(statusMessage).toHaveTextContent("Other tag");
79+
});
80+
81+
it("Renders widget with additional actions and handles click", () => {
82+
const mockAction = jest.fn();
83+
const additionalActions = [
84+
<IconButton
85+
key="add-btn"
86+
name="application-explore"
87+
onClick={mockAction}
88+
data-testid="additional-action"
89+
/>,
90+
];
91+
render(<ActivityControlWidget additionalActions={additionalActions} />);
92+
93+
const customButton = screen.getByTestId("additional-action");
94+
expect(customButton).toBeInTheDocument();
95+
96+
fireEvent.click(customButton);
97+
expect(mockAction).toHaveBeenCalledTimes(1);
98+
});
99+
});

0 commit comments

Comments
 (0)