Skip to content

Commit de54003

Browse files
jsilllpan-kot
andauthored
feat: add isUserAction argument to api events (#48)
* feat: add isUserAction argument to api events * address comments * chore: IsApiCall cb detail (#51) --------- Co-authored-by: Andrei Zhaleznichenka <[email protected]>
1 parent 432a314 commit de54003

14 files changed

+272
-135
lines changed

pages/03-core/simple-zooming.page.tsx

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4+
import { useCallback, useEffect, useRef, useState } from "react";
55
import Highcharts from "highcharts";
66
import { omit } from "lodash";
77

@@ -12,7 +12,6 @@ import SpaceBetween from "@cloudscape-design/components/space-between";
1212
import { colorChartsBlue1400, colorChartsLineTick } from "@cloudscape-design/design-tokens";
1313

1414
import { CoreChartAPI, CoreChartProps } from "../../lib/components/core/interfaces";
15-
import { DebouncedCall } from "../../lib/components/internal/utils/utils";
1615
import CoreChart from "../../lib/components/internal-do-not-use/core-chart";
1716
import { dateFormatter } from "../common/formatters";
1817
import { PageSettings, PageSettingsForm, useChartSettings } from "../common/page-settings";
@@ -200,35 +199,34 @@ function Charts() {
200199
};
201200
}, [onNavigationClick, onNavigationKeydown]);
202201

203-
const highlightLock = useRef(false);
204-
const highlightLockControl = useMemo(() => new DebouncedCall(), []);
205-
const withLock = (callback: () => void) => {
206-
if (!highlightLock.current) {
207-
highlightLock.current = true;
208-
highlightLockControl.call(() => (highlightLock.current = false), 0);
209-
callback();
202+
const onScatterHighlight: CoreChartProps["onHighlight"] = ({ group, isApiCall }) => {
203+
if (isApiCall) {
204+
return;
205+
}
206+
highlightChartGroup(group[0], getNavigatorChart());
207+
highlightedPoint.current = group[0];
208+
};
209+
const onScatterClearHighlight = ({ isApiCall }: { isApiCall: boolean }) => {
210+
if (isApiCall) {
211+
return;
212+
}
213+
getScatterChart().clearChartHighlight();
214+
highlightedPoint.current = null;
215+
};
216+
const onNavigatorHighlight: CoreChartProps["onHighlight"] = ({ group, isApiCall }) => {
217+
if (isApiCall) {
218+
return;
219+
}
220+
highlightChartGroup(group[0], getScatterChart());
221+
highlightedPoint.current = group[0];
222+
};
223+
const onNavigatorClearHighlight = ({ isApiCall }: { isApiCall: boolean }) => {
224+
if (isApiCall) {
225+
return;
210226
}
227+
getScatterChart().clearChartHighlight();
228+
highlightedPoint.current = null;
211229
};
212-
const onScatterHighlight: CoreChartProps["onHighlight"] = ({ group }) =>
213-
withLock(() => {
214-
highlightChartGroup(group[0], getNavigatorChart());
215-
highlightedPoint.current = group[0];
216-
});
217-
const onScatterClearHighlight = () =>
218-
withLock(() => {
219-
getScatterChart().clearChartHighlight();
220-
highlightedPoint.current = null;
221-
});
222-
const onNavigatorHighlight: CoreChartProps["onHighlight"] = ({ group }) =>
223-
withLock(() => {
224-
highlightChartGroup(group[0], getScatterChart());
225-
highlightedPoint.current = group[0];
226-
});
227-
const onNavigatorClearHighlight = () =>
228-
withLock(() => {
229-
getScatterChart().clearChartHighlight();
230-
highlightedPoint.current = null;
231-
});
232230

233231
return (
234232
<SpaceBetween size="s">

src/cartesian-chart/chart-cartesian-internal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const InternalCartesianChart = forwardRef(
4343
// We keep local visible series state to compute threshold series data, that depends on series visibility.
4444
const [visibleSeriesLocal, setVisibleSeriesLocal] = useState(props.visibleSeries ?? allSeriesIds);
4545
const visibleSeriesState = props.visibleSeries ?? visibleSeriesLocal;
46-
const onVisibleSeriesChange: CoreChartProps["onVisibleItemsChange"] = (items) => {
46+
const onVisibleSeriesChange: CoreChartProps["onVisibleItemsChange"] = ({ items }) => {
4747
const visibleSeries = items.filter((i) => i.visible).map((i) => i.id);
4848
if (props.visibleSeries) {
4949
fireNonCancelableEvent(props.onVisibleSeriesChange, { visibleSeries });
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { act } from "react";
5+
import highcharts from "highcharts";
6+
import { vi } from "vitest";
7+
8+
import { CoreChartAPI } from "../../../lib/components/core/interfaces";
9+
import { renderChart, selectLegendItem } from "./common";
10+
import { HighchartsTestHelper } from "./highcharts-utils";
11+
12+
const clearHighlightPause = () => new Promise((resolve) => setTimeout(resolve, 100));
13+
14+
const hc = new HighchartsTestHelper(highcharts);
15+
16+
const series: Highcharts.SeriesOptionsType[] = [
17+
{ type: "line", name: "Line 1", data: [1, 2, 3] },
18+
{ type: "line", name: "Line 2", data: [4, 5, 6] },
19+
];
20+
21+
describe("CoreChart: API tests", () => {
22+
test("passes isApiCall=false to onHighlight when triggered by an user interaction", () => {
23+
const onHighlight = vi.fn();
24+
renderChart({ highcharts, options: { series }, onHighlight });
25+
26+
act(() => hc.highlightChartPoint(0, 0));
27+
28+
expect(onHighlight).toHaveBeenCalledWith(
29+
expect.objectContaining({ point: hc.getChartPoint(0, 0), isApiCall: false }),
30+
);
31+
});
32+
33+
test("passes isApiCall=true to onHighlight when triggered programmatically through API", () => {
34+
const onHighlight = vi.fn();
35+
let chartApi: CoreChartAPI | null = null;
36+
37+
renderChart({ highcharts, onHighlight, options: { series }, callback: (api) => (chartApi = api) });
38+
39+
const point = hc.getChartPoint(0, 0);
40+
41+
act(() => chartApi!.highlightChartPoint(point));
42+
expect(onHighlight).toHaveBeenCalledWith(expect.objectContaining({ point, isApiCall: true }));
43+
});
44+
45+
test("passes isApiCall=false to onClearHighlight when triggered by user interaction", async () => {
46+
const onClearHighlight = vi.fn();
47+
renderChart({ highcharts, options: { series }, onClearHighlight });
48+
49+
act(() => hc.highlightChartPoint(0, 0));
50+
act(() => hc.leaveChartPoint(0, 0));
51+
await clearHighlightPause();
52+
53+
expect(onClearHighlight).toHaveBeenCalledWith({ isApiCall: false });
54+
});
55+
56+
test("passes isApiCall=true to onClearHighlight when triggered programmatically through API", () => {
57+
const onClearHighlight = vi.fn();
58+
let chartApi: CoreChartAPI | null = null;
59+
60+
renderChart({ highcharts, onClearHighlight, options: { series }, callback: (api) => (chartApi = api) });
61+
62+
act(() => chartApi!.clearChartHighlight());
63+
expect(onClearHighlight).toHaveBeenCalledWith({ isApiCall: true });
64+
});
65+
66+
test("passes isApiCall=false to onVisibleItemsChange when triggered by user interaction", () => {
67+
const onVisibleItemsChange = vi.fn();
68+
const { wrapper } = renderChart({ highcharts, options: { series }, onVisibleItemsChange });
69+
70+
selectLegendItem(0, wrapper);
71+
72+
expect(onVisibleItemsChange).toHaveBeenCalledWith({ items: expect.any(Array), isApiCall: false });
73+
});
74+
75+
test("passes isApiCall=true to onVisibleItemsChange when triggered programmatically through API", () => {
76+
const onVisibleItemsChange = vi.fn();
77+
let chartApi: CoreChartAPI | null = null;
78+
79+
renderChart({ highcharts, options: { series }, onVisibleItemsChange, callback: (api) => (chartApi = api) });
80+
81+
act(() => chartApi!.setItemsVisible(["Line 1"]));
82+
expect(onVisibleItemsChange).toHaveBeenCalledWith({ items: expect.any(Array), isApiCall: true });
83+
});
84+
});

src/core/__tests__/chart-core-series-filter.test.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,12 @@ describe("CoreChart: series filter", () => {
5959
});
6060
createChartWrapper().findFilter()!.findSeriesFilter()!.findMultiselect()!.openDropdown();
6161
createChartWrapper().findFilter()!.findSeriesFilter()!.findMultiselect()!.selectOption(1);
62-
expect(onVisibleItemsChange).toHaveBeenCalledWith([
63-
{ id: "L1", name: "L1", marker: expect.anything(), visible: false, highlighted: false },
64-
{ id: "L2", name: "L2", marker: expect.anything(), visible: true, highlighted: false },
65-
]);
62+
expect(onVisibleItemsChange).toHaveBeenCalledWith({
63+
items: [
64+
{ id: "L1", name: "L1", marker: expect.anything(), visible: false, highlighted: false },
65+
{ id: "L2", name: "L2", marker: expect.anything(), visible: true, highlighted: false },
66+
],
67+
isApiCall: false,
68+
});
6669
});
6770
});

src/core/__tests__/chart-core-tooltip.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ describe("CoreChart: tooltip", () => {
298298

299299
const point = hc.getChartPoint(0, i);
300300
await waitFor(() => {
301-
expect(onHighlight).toHaveBeenCalledWith({ point, group: expect.arrayContaining([point]) });
301+
expect(onHighlight).toHaveBeenCalledWith({ point, group: expect.arrayContaining([point]), isApiCall: false });
302302
expect(getTooltipContent).toHaveBeenCalledWith({ point, group: expect.arrayContaining([point]) });
303303
});
304304
}

src/core/__tests__/chart-core-visibility.test.tsx

Lines changed: 76 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -131,31 +131,40 @@ describe("CoreChart: visibility", () => {
131131

132132
expect(getVisibilityState()).toEqual(expectedLineItems(["L2", "L3"]));
133133

134-
expect(onVisibleItemsChange).toHaveBeenCalledWith([
135-
{ id: "L1", name: "L1", marker: expect.anything(), visible: false, highlighted: false },
136-
{ id: "L2", name: "L2", marker: expect.anything(), visible: true, highlighted: false },
137-
{ id: "L3", name: "L3", marker: expect.anything(), visible: true, highlighted: false },
138-
]);
134+
expect(onVisibleItemsChange).toHaveBeenCalledWith({
135+
items: [
136+
{ id: "L1", name: "L1", marker: expect.anything(), visible: false, highlighted: false },
137+
{ id: "L2", name: "L2", marker: expect.anything(), visible: true, highlighted: false },
138+
{ id: "L3", name: "L3", marker: expect.anything(), visible: true, highlighted: false },
139+
],
140+
isApiCall: false,
141+
});
139142

140143
selectLegendItem(1);
141144

142145
expect(getVisibilityState()).toEqual(expectedLineItems(["L2"]));
143146

144-
expect(onVisibleItemsChange).toHaveBeenCalledWith([
145-
{ id: "L1", name: "L1", marker: expect.anything(), visible: false, highlighted: false },
146-
{ id: "L2", name: "L2", marker: expect.anything(), visible: true, highlighted: false },
147-
{ id: "L3", name: "L3", marker: expect.anything(), visible: false, highlighted: false },
148-
]);
147+
expect(onVisibleItemsChange).toHaveBeenCalledWith({
148+
items: [
149+
{ id: "L1", name: "L1", marker: expect.anything(), visible: false, highlighted: false },
150+
{ id: "L2", name: "L2", marker: expect.anything(), visible: true, highlighted: false },
151+
{ id: "L3", name: "L3", marker: expect.anything(), visible: false, highlighted: false },
152+
],
153+
isApiCall: false,
154+
});
149155

150156
selectLegendItem(1);
151157

152158
expect(getVisibilityState()).toEqual(expectedLineItems(["L1", "L2", "L3"]));
153159

154-
expect(onVisibleItemsChange).toHaveBeenCalledWith([
155-
{ id: "L1", name: "L1", marker: expect.anything(), visible: true, highlighted: false },
156-
{ id: "L2", name: "L2", marker: expect.anything(), visible: true, highlighted: false },
157-
{ id: "L3", name: "L3", marker: expect.anything(), visible: true, highlighted: false },
158-
]);
160+
expect(onVisibleItemsChange).toHaveBeenCalledWith({
161+
items: [
162+
{ id: "L1", name: "L1", marker: expect.anything(), visible: true, highlighted: false },
163+
{ id: "L2", name: "L2", marker: expect.anything(), visible: true, highlighted: false },
164+
{ id: "L3", name: "L3", marker: expect.anything(), visible: true, highlighted: false },
165+
],
166+
isApiCall: false,
167+
});
159168
});
160169

161170
test("changes series visibility from the outside", () => {
@@ -209,17 +218,23 @@ describe("CoreChart: visibility", () => {
209218

210219
toggleLegendItem(0);
211220

212-
expect(onVisibleItemsChange).toHaveBeenCalledWith([
213-
{ id: "1", name: "Line", marker: expect.anything(), visible: true, highlighted: false },
214-
{ id: "2", name: "Line", marker: expect.anything(), visible: true, highlighted: false },
215-
]);
221+
expect(onVisibleItemsChange).toHaveBeenCalledWith({
222+
items: [
223+
{ id: "1", name: "Line", marker: expect.anything(), visible: true, highlighted: false },
224+
{ id: "2", name: "Line", marker: expect.anything(), visible: true, highlighted: false },
225+
],
226+
isApiCall: false,
227+
});
216228

217229
toggleLegendItem(1);
218230

219-
expect(onVisibleItemsChange).toHaveBeenCalledWith([
220-
{ id: "1", name: "Line", marker: expect.anything(), visible: false, highlighted: false },
221-
{ id: "2", name: "Line", marker: expect.anything(), visible: false, highlighted: false },
222-
]);
231+
expect(onVisibleItemsChange).toHaveBeenCalledWith({
232+
items: [
233+
{ id: "1", name: "Line", marker: expect.anything(), visible: false, highlighted: false },
234+
{ id: "2", name: "Line", marker: expect.anything(), visible: false, highlighted: false },
235+
],
236+
isApiCall: false,
237+
});
223238
});
224239

225240
test.each([false, true])("hides items on the first render, legend=%s", (legend) => {
@@ -253,31 +268,40 @@ describe("CoreChart: visibility", () => {
253268

254269
expect(getVisibilityState()).toEqual(expectedPieItems(["A", "C"]));
255270

256-
expect(onVisibleItemsChange).toHaveBeenCalledWith([
257-
{ id: "A", name: "A", marker: expect.anything(), visible: true, highlighted: false },
258-
{ id: "B", name: "B", marker: expect.anything(), visible: false, highlighted: false },
259-
{ id: "C", name: "C", marker: expect.anything(), visible: true, highlighted: false },
260-
]);
271+
expect(onVisibleItemsChange).toHaveBeenCalledWith({
272+
items: [
273+
{ id: "A", name: "A", marker: expect.anything(), visible: true, highlighted: false },
274+
{ id: "B", name: "B", marker: expect.anything(), visible: false, highlighted: false },
275+
{ id: "C", name: "C", marker: expect.anything(), visible: true, highlighted: false },
276+
],
277+
isApiCall: false,
278+
});
261279

262280
selectLegendItem(0);
263281

264282
expect(getVisibilityState()).toEqual(expectedPieItems(["A"]));
265283

266-
expect(onVisibleItemsChange).toHaveBeenCalledWith([
267-
{ id: "A", name: "A", marker: expect.anything(), visible: true, highlighted: false },
268-
{ id: "B", name: "B", marker: expect.anything(), visible: false, highlighted: false },
269-
{ id: "C", name: "C", marker: expect.anything(), visible: false, highlighted: false },
270-
]);
284+
expect(onVisibleItemsChange).toHaveBeenCalledWith({
285+
items: [
286+
{ id: "A", name: "A", marker: expect.anything(), visible: true, highlighted: false },
287+
{ id: "B", name: "B", marker: expect.anything(), visible: false, highlighted: false },
288+
{ id: "C", name: "C", marker: expect.anything(), visible: false, highlighted: false },
289+
],
290+
isApiCall: false,
291+
});
271292

272293
selectLegendItem(0);
273294

274295
expect(getVisibilityState()).toEqual(expectedPieItems(["A", "B", "C"]));
275296

276-
expect(onVisibleItemsChange).toHaveBeenCalledWith([
277-
{ id: "A", name: "A", marker: expect.anything(), visible: true, highlighted: false },
278-
{ id: "B", name: "B", marker: expect.anything(), visible: true, highlighted: false },
279-
{ id: "C", name: "C", marker: expect.anything(), visible: true, highlighted: false },
280-
]);
297+
expect(onVisibleItemsChange).toHaveBeenCalledWith({
298+
items: [
299+
{ id: "A", name: "A", marker: expect.anything(), visible: true, highlighted: false },
300+
{ id: "B", name: "B", marker: expect.anything(), visible: true, highlighted: false },
301+
{ id: "C", name: "C", marker: expect.anything(), visible: true, highlighted: false },
302+
],
303+
isApiCall: false,
304+
});
281305
});
282306

283307
test("changes items visibility from the outside", () => {
@@ -338,16 +362,22 @@ describe("CoreChart: visibility", () => {
338362

339363
toggleLegendItem(0);
340364

341-
expect(onVisibleItemsChange).toHaveBeenCalledWith([
342-
{ id: "1", name: "Segment", marker: expect.anything(), visible: true, highlighted: false },
343-
{ id: "2", name: "Segment", marker: expect.anything(), visible: true, highlighted: false },
344-
]);
365+
expect(onVisibleItemsChange).toHaveBeenCalledWith({
366+
items: [
367+
{ id: "1", name: "Segment", marker: expect.anything(), visible: true, highlighted: false },
368+
{ id: "2", name: "Segment", marker: expect.anything(), visible: true, highlighted: false },
369+
],
370+
isApiCall: false,
371+
});
345372

346373
toggleLegendItem(1);
347374

348-
expect(onVisibleItemsChange).toHaveBeenCalledWith([
349-
{ id: "1", name: "Segment", marker: expect.anything(), visible: false, highlighted: false },
350-
{ id: "2", name: "Segment", marker: expect.anything(), visible: false, highlighted: false },
351-
]);
375+
expect(onVisibleItemsChange).toHaveBeenCalledWith({
376+
items: [
377+
{ id: "1", name: "Segment", marker: expect.anything(), visible: false, highlighted: false },
378+
{ id: "2", name: "Segment", marker: expect.anything(), visible: false, highlighted: false },
379+
],
380+
isApiCall: false,
381+
});
352382
});
353383
});

src/core/__tests__/common.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ export function StatefulChart(props: CoreChartProps) {
2323
<CoreChart
2424
{...props}
2525
visibleItems={visibleItems}
26-
onVisibleItemsChange={(legendItems) => {
26+
onVisibleItemsChange={({ items: legendItems, isApiCall }) => {
2727
const visibleItems = legendItems.filter((i) => i.visible).map((i) => i.id);
2828
setVisibleItems(visibleItems);
29-
props.onVisibleItemsChange?.(legendItems);
29+
props.onVisibleItemsChange?.({ items: legendItems, isApiCall });
3030
}}
3131
/>
3232
);

0 commit comments

Comments
 (0)