Skip to content

Commit 63204c5

Browse files
committed
move everything progress related into helpers/pendingProgress.ts
1 parent 13ca6d6 commit 63204c5

4 files changed

Lines changed: 361 additions & 149 deletions

File tree

chartlets.js/packages/lib/src/actions/helpers/invokeCallbacks.test.ts

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
88

99
import { store } from "@/store";
10+
import type { CallbackRequest, StateChangeRequest } from "@/types/model/callback";
1011
import type { ComponentState } from "@/types/state/component";
11-
import type { StateChangeRequest } from "@/types/model/callback";
1212
import { invokeCallbacks } from "./invokeCallbacks";
1313

1414
function createDeferred<T>() {
@@ -24,6 +24,14 @@ function getProgressComponent() {
2424
.children![0] as ComponentState);
2525
}
2626

27+
const callbackRequest: CallbackRequest = {
28+
contribPoint: "panels",
29+
contribIndex: 0,
30+
callbackIndex: 0,
31+
inputIndex: 0,
32+
inputValues: [true],
33+
};
34+
2735
describe("invokeCallbacks", () => {
2836
beforeEach(() => {
2937
store.setState({
@@ -66,38 +74,70 @@ describe("invokeCallbacks", () => {
6674
vi.restoreAllMocks();
6775
});
6876

69-
it("shows progress while a callback with a progress visibility output is pending", async () => {
77+
it("shows pending progress and applies callback results", async () => {
7078
const deferred = createDeferred<Response>();
7179
globalThis.fetch = vi.fn().mockReturnValue(deferred.promise);
7280

73-
invokeCallbacks([
74-
{
75-
contribPoint: "panels",
76-
contribIndex: 0,
77-
callbackIndex: 0,
78-
inputIndex: 0,
79-
inputValues: [true],
80-
},
81-
]);
81+
invokeCallbacks([callbackRequest]);
8282

8383
expect(getProgressComponent().visible).toBe(true);
8484

85-
const callbackResult: StateChangeRequest[] = [
85+
deferred.resolve(createCallbackResponse([
8686
{
8787
contribPoint: "panels",
8888
contribIndex: 0,
8989
stateChanges: [{ id: "progress", property: "visible", value: false }],
9090
},
91-
];
91+
]));
92+
93+
await vi.waitFor(() => {
94+
expect(getProgressComponent().visible).toBe(false);
95+
});
96+
});
97+
98+
it("logs and releases pending progress when a callback fails", async () => {
99+
const deferred = createDeferred<Response>();
100+
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
101+
globalThis.fetch = vi.fn().mockReturnValue(deferred.promise);
102+
103+
invokeCallbacks([callbackRequest]);
104+
105+
expect(getProgressComponent().visible).toBe(true);
106+
92107
deferred.resolve({
93108
ok: true,
94109
status: 200,
95110
statusText: "ok",
96-
json: vi.fn().mockResolvedValue({ result: callbackResult }),
111+
json: vi.fn().mockResolvedValue({ message: "unexpected" }),
97112
} as unknown as Response);
98113

99114
await vi.waitFor(() => {
100115
expect(getProgressComponent().visible).toBe(false);
101116
});
117+
expect(consoleError).toHaveBeenCalledOnce();
102118
});
103-
});
119+
120+
it("logs callback requests and results when logging is enabled", async () => {
121+
const deferred = createDeferred<Response>();
122+
const consoleInfo = vi.spyOn(console, "info").mockImplementation(() => {});
123+
globalThis.fetch = vi.fn().mockReturnValue(deferred.promise);
124+
store.setState({ configuration: { logging: { enabled: true } } });
125+
126+
invokeCallbacks([callbackRequest]);
127+
128+
deferred.resolve(createCallbackResponse([]));
129+
130+
await vi.waitFor(() => {
131+
expect(consoleInfo).toHaveBeenCalledTimes(2);
132+
});
133+
});
134+
});
135+
136+
function createCallbackResponse(result: StateChangeRequest[]) {
137+
return {
138+
ok: true,
139+
status: 200,
140+
statusText: "ok",
141+
json: vi.fn().mockResolvedValue({ result }),
142+
} as unknown as Response;
143+
}

chartlets.js/packages/lib/src/actions/helpers/invokeCallbacks.ts

Lines changed: 7 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,14 @@
55
*/
66

77
import { store } from "@/store";
8-
import type {
9-
CallbackRequest,
10-
StateChangeRequest,
11-
} from "@/types/model/callback";
12-
import type { Output } from "@/types/model/channel";
13-
import type { ComponentState } from "@/types/state/component";
8+
import type { CallbackRequest } from "@/types/model/callback";
149
import { fetchCallback } from "@/api/fetchCallback";
1510
import { applyStateChangeRequests } from "@/actions/helpers/applyStateChangeRequests";
16-
import { formatObjPath } from "@/utils/objPath";
17-
18-
interface PendingProgressTarget {
19-
contribPoint: string;
20-
contribIndex: number;
21-
id: string;
22-
output: Output;
23-
}
24-
25-
const progressComponentTypes = new Set([
26-
"CircularProgress",
27-
"CircularProgressWithLabel",
28-
"LinearProgress",
29-
"LinearProgressWithLabel",
30-
]);
31-
32-
const pendingProgressCounts: Record<string, number> = {};
11+
import {
12+
getPendingProgressTargets,
13+
releasePendingProgressTargets,
14+
showPendingProgressTargets,
15+
} from "@/actions/helpers/pendingProgress";
3316

3417
export function invokeCallbacks(callbackRequests: CallbackRequest[]) {
3518
const { configuration } = store.getState();
@@ -67,118 +50,8 @@ export function invokeCallbacks(callbackRequests: CallbackRequest[]) {
6750
);
6851
}
6952

70-
function getPendingProgressTargets(
71-
callbackRequests: CallbackRequest[],
72-
): PendingProgressTarget[] {
73-
const { contributionsRecord } = store.getState();
74-
const targets: PendingProgressTarget[] = [];
75-
const targetKeys = new Set<string>();
76-
77-
callbackRequests.forEach(({ contribPoint, contribIndex, callbackIndex }) => {
78-
const contribution = contributionsRecord[contribPoint]?.[contribIndex];
79-
const callback = contribution?.callbacks?.[callbackIndex];
80-
callback?.outputs?.forEach((output) => {
81-
if (
82-
formatObjPath(output.property) === "visible" &&
83-
isProgressComponent(contribution.component, output.id)
84-
) {
85-
const target = { contribPoint, contribIndex, id: output.id, output };
86-
const key = getPendingProgressTargetKey(target);
87-
if (!targetKeys.has(key)) {
88-
targetKeys.add(key);
89-
targets.push(target);
90-
}
91-
}
92-
});
93-
});
94-
95-
return targets;
96-
}
97-
98-
function showPendingProgressTargets(targets: PendingProgressTarget[]) {
99-
incrementPendingProgressCounts(targets);
100-
applyPendingProgressTargets(targets, true);
101-
}
102-
103-
function releasePendingProgressTargets(
104-
targets: PendingProgressTarget[],
105-
callbackSucceeded: boolean,
106-
) {
107-
decrementPendingProgressCounts(targets);
108-
const stillPendingTargets = targets.filter(
109-
(target) => pendingProgressCounts[getPendingProgressTargetKey(target)] > 0,
110-
);
111-
applyPendingProgressTargets(stillPendingTargets, true);
112-
113-
if (!callbackSucceeded) {
114-
const completedTargets = targets.filter(
115-
(target) => !pendingProgressCounts[getPendingProgressTargetKey(target)],
116-
);
117-
applyPendingProgressTargets(completedTargets, false);
118-
}
119-
}
120-
121-
function incrementPendingProgressCounts(targets: PendingProgressTarget[]) {
122-
targets.forEach((target) => {
123-
const key = getPendingProgressTargetKey(target);
124-
pendingProgressCounts[key] = (pendingProgressCounts[key] || 0) + 1;
125-
});
126-
}
127-
128-
function decrementPendingProgressCounts(targets: PendingProgressTarget[]) {
129-
targets.forEach((target) => {
130-
const key = getPendingProgressTargetKey(target);
131-
const count = (pendingProgressCounts[key] || 0) - 1;
132-
if (count > 0) {
133-
pendingProgressCounts[key] = count;
134-
} else {
135-
delete pendingProgressCounts[key];
136-
}
137-
});
138-
}
139-
140-
function applyPendingProgressTargets(
141-
targets: PendingProgressTarget[],
142-
visible: boolean,
143-
) {
144-
if (targets.length === 0) {
145-
return;
146-
}
147-
applyStateChangeRequests(
148-
targets.map<StateChangeRequest>((target) => ({
149-
contribPoint: target.contribPoint,
150-
contribIndex: target.contribIndex,
151-
stateChanges: [{ ...target.output, value: visible }],
152-
})),
153-
);
154-
}
155-
156-
function getPendingProgressTargetKey(target: PendingProgressTarget) {
157-
return `${target.contribPoint}-${target.contribIndex}-${target.id}`;
158-
}
159-
160-
function isProgressComponent(
161-
component: ComponentState | undefined,
162-
id: string,
163-
): boolean {
164-
if (!component) {
165-
return false;
166-
}
167-
if (component.id === id) {
168-
return progressComponentTypes.has(component.type);
169-
}
170-
return Boolean(
171-
component.children?.some(
172-
(child) =>
173-
typeof child === "object" &&
174-
child !== null &&
175-
isProgressComponent(child, id),
176-
),
177-
);
178-
}
179-
18053
let invocationCounter = 0;
18154

18255
function getInvocationId() {
18356
return invocationCounter++;
184-
}
57+
}

0 commit comments

Comments
 (0)