@topolo/sdk 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +111 -0
- package/dist/index.d.cts +87 -15
- package/dist/index.d.ts +87 -15
- package/dist/index.js +111 -0
- package/package.json +1 -1
- package/src/applications.generated.ts +71 -0
- package/src/index.ts +18 -11
- package/src/services.generated.ts +5 -0
- package/src/services.test.ts +3 -0
- package/src/widgets.test.ts +88 -49
- package/src/widgets.ts +115 -43
package/src/widgets.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export const TOPOLO_WIDGET_ENDPOINT =
|
|
1
|
+
export const TOPOLO_WIDGET_ENDPOINT = "/api/widget";
|
|
2
2
|
export const TOPOLO_WIDGET_REFRESH_INTERVAL_MS = 60_000;
|
|
3
3
|
|
|
4
4
|
export type TopoloWidgetType =
|
|
5
|
-
|
|
|
6
|
-
|
|
|
7
|
-
|
|
|
8
|
-
|
|
|
9
|
-
|
|
|
5
|
+
| "stats"
|
|
6
|
+
| "recent-activity"
|
|
7
|
+
| "quick-actions"
|
|
8
|
+
| "notifications"
|
|
9
|
+
| "chart";
|
|
10
10
|
|
|
11
11
|
export interface TopoloWidgetBase {
|
|
12
12
|
serviceId: string;
|
|
@@ -14,9 +14,11 @@ export interface TopoloWidgetBase {
|
|
|
14
14
|
serviceIcon?: string;
|
|
15
15
|
type: TopoloWidgetType;
|
|
16
16
|
lastUpdated: string;
|
|
17
|
+
snapshot?: TopoloWidgetSnapshotMetadata;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export interface TopoloWidgetMetric {
|
|
21
|
+
key?: string;
|
|
20
22
|
label: string;
|
|
21
23
|
value: string | number;
|
|
22
24
|
change?: number;
|
|
@@ -24,8 +26,29 @@ export interface TopoloWidgetMetric {
|
|
|
24
26
|
icon?: string;
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
export interface TopoloWidgetSnapshotMetric {
|
|
30
|
+
key: string;
|
|
31
|
+
label: string;
|
|
32
|
+
value: number;
|
|
33
|
+
unit?: string;
|
|
34
|
+
change?: number;
|
|
35
|
+
changeLabel?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TopoloWidgetSnapshotMetadata {
|
|
39
|
+
presetKey?: string;
|
|
40
|
+
audience?: string;
|
|
41
|
+
scope?: string;
|
|
42
|
+
scopeLabel?: string;
|
|
43
|
+
generatedAt?: string;
|
|
44
|
+
summary?: string;
|
|
45
|
+
defaultMetricKeys?: string[];
|
|
46
|
+
availableMetrics?: TopoloWidgetSnapshotMetric[];
|
|
47
|
+
highlights?: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
27
50
|
export interface TopoloStatsWidget extends TopoloWidgetBase {
|
|
28
|
-
type:
|
|
51
|
+
type: "stats";
|
|
29
52
|
stats: TopoloWidgetMetric[];
|
|
30
53
|
}
|
|
31
54
|
|
|
@@ -39,7 +62,7 @@ export interface TopoloWidgetActivityItem {
|
|
|
39
62
|
}
|
|
40
63
|
|
|
41
64
|
export interface TopoloRecentActivityWidget extends TopoloWidgetBase {
|
|
42
|
-
type:
|
|
65
|
+
type: "recent-activity";
|
|
43
66
|
activities: TopoloWidgetActivityItem[];
|
|
44
67
|
}
|
|
45
68
|
|
|
@@ -52,7 +75,7 @@ export interface TopoloWidgetActionItem {
|
|
|
52
75
|
}
|
|
53
76
|
|
|
54
77
|
export interface TopoloQuickActionsWidget extends TopoloWidgetBase {
|
|
55
|
-
type:
|
|
78
|
+
type: "quick-actions";
|
|
56
79
|
actions: TopoloWidgetActionItem[];
|
|
57
80
|
}
|
|
58
81
|
|
|
@@ -60,21 +83,21 @@ export interface TopoloWidgetNotificationItem {
|
|
|
60
83
|
id: string;
|
|
61
84
|
title: string;
|
|
62
85
|
message: string;
|
|
63
|
-
type:
|
|
86
|
+
type: "info" | "warning" | "error" | "success";
|
|
64
87
|
timestamp: string;
|
|
65
88
|
read: boolean;
|
|
66
89
|
link?: string;
|
|
67
90
|
}
|
|
68
91
|
|
|
69
92
|
export interface TopoloNotificationsWidget extends TopoloWidgetBase {
|
|
70
|
-
type:
|
|
93
|
+
type: "notifications";
|
|
71
94
|
notifications: TopoloWidgetNotificationItem[];
|
|
72
95
|
unreadCount: number;
|
|
73
96
|
}
|
|
74
97
|
|
|
75
98
|
export interface TopoloChartWidget extends TopoloWidgetBase {
|
|
76
|
-
type:
|
|
77
|
-
chartType:
|
|
99
|
+
type: "chart";
|
|
100
|
+
chartType: "line" | "bar" | "pie" | "donut";
|
|
78
101
|
title: string;
|
|
79
102
|
data: {
|
|
80
103
|
labels: string[];
|
|
@@ -91,9 +114,9 @@ export type TopoloWidget =
|
|
|
91
114
|
|
|
92
115
|
type TopoloWidgetWithOptionalMetadata<T extends TopoloWidget> = Omit<
|
|
93
116
|
T,
|
|
94
|
-
|
|
117
|
+
"serviceId" | "serviceName" | "lastUpdated"
|
|
95
118
|
> &
|
|
96
|
-
Partial<Pick<TopoloWidgetBase,
|
|
119
|
+
Partial<Pick<TopoloWidgetBase, "serviceId" | "serviceName" | "lastUpdated">>;
|
|
97
120
|
|
|
98
121
|
export type TopoloWidgetDraft = TopoloWidget extends infer Widget
|
|
99
122
|
? Widget extends TopoloWidget
|
|
@@ -129,33 +152,33 @@ export interface TopoloWidgetConfig {
|
|
|
129
152
|
}
|
|
130
153
|
|
|
131
154
|
export function defineTopoloStatsWidget(
|
|
132
|
-
widget: Omit<TopoloStatsWidget,
|
|
155
|
+
widget: Omit<TopoloStatsWidget, "type">,
|
|
133
156
|
): TopoloStatsWidget {
|
|
134
|
-
return { ...widget, type:
|
|
157
|
+
return { ...widget, type: "stats" };
|
|
135
158
|
}
|
|
136
159
|
|
|
137
160
|
export function defineTopoloRecentActivityWidget(
|
|
138
|
-
widget: Omit<TopoloRecentActivityWidget,
|
|
161
|
+
widget: Omit<TopoloRecentActivityWidget, "type">,
|
|
139
162
|
): TopoloRecentActivityWidget {
|
|
140
|
-
return { ...widget, type:
|
|
163
|
+
return { ...widget, type: "recent-activity" };
|
|
141
164
|
}
|
|
142
165
|
|
|
143
166
|
export function defineTopoloQuickActionsWidget(
|
|
144
|
-
widget: Omit<TopoloQuickActionsWidget,
|
|
167
|
+
widget: Omit<TopoloQuickActionsWidget, "type">,
|
|
145
168
|
): TopoloQuickActionsWidget {
|
|
146
|
-
return { ...widget, type:
|
|
169
|
+
return { ...widget, type: "quick-actions" };
|
|
147
170
|
}
|
|
148
171
|
|
|
149
172
|
export function defineTopoloNotificationsWidget(
|
|
150
|
-
widget: Omit<TopoloNotificationsWidget,
|
|
173
|
+
widget: Omit<TopoloNotificationsWidget, "type">,
|
|
151
174
|
): TopoloNotificationsWidget {
|
|
152
|
-
return { ...widget, type:
|
|
175
|
+
return { ...widget, type: "notifications" };
|
|
153
176
|
}
|
|
154
177
|
|
|
155
178
|
export function defineTopoloChartWidget(
|
|
156
|
-
widget: Omit<TopoloChartWidget,
|
|
179
|
+
widget: Omit<TopoloChartWidget, "type">,
|
|
157
180
|
): TopoloChartWidget {
|
|
158
|
-
return { ...widget, type:
|
|
181
|
+
return { ...widget, type: "chart" };
|
|
159
182
|
}
|
|
160
183
|
|
|
161
184
|
export function createTopoloWidgetResponse({
|
|
@@ -168,7 +191,7 @@ export function createTopoloWidgetResponse({
|
|
|
168
191
|
|
|
169
192
|
return {
|
|
170
193
|
success: true,
|
|
171
|
-
widgets: widgets.map(widget => ({
|
|
194
|
+
widgets: widgets.map((widget) => ({
|
|
172
195
|
...widget,
|
|
173
196
|
serviceId: widget.serviceId?.trim() || serviceId,
|
|
174
197
|
serviceName: widget.serviceName?.trim() || serviceName,
|
|
@@ -177,7 +200,9 @@ export function createTopoloWidgetResponse({
|
|
|
177
200
|
};
|
|
178
201
|
}
|
|
179
202
|
|
|
180
|
-
export function createTopoloWidgetError(
|
|
203
|
+
export function createTopoloWidgetError(
|
|
204
|
+
error: string,
|
|
205
|
+
): TopoloWidgetApiResponse {
|
|
181
206
|
return {
|
|
182
207
|
success: false,
|
|
183
208
|
widgets: [],
|
|
@@ -186,20 +211,20 @@ export function createTopoloWidgetError(error: string): TopoloWidgetApiResponse
|
|
|
186
211
|
}
|
|
187
212
|
|
|
188
213
|
export function validateTopoloWidgetResponse(
|
|
189
|
-
response: unknown
|
|
214
|
+
response: unknown,
|
|
190
215
|
): TopoloWidgetValidationResult {
|
|
191
216
|
const errors: string[] = [];
|
|
192
217
|
|
|
193
218
|
if (!isRecord(response)) {
|
|
194
|
-
return { ok: false, errors: [
|
|
219
|
+
return { ok: false, errors: ["response must be an object"] };
|
|
195
220
|
}
|
|
196
221
|
|
|
197
|
-
if (typeof response.success !==
|
|
198
|
-
errors.push(
|
|
222
|
+
if (typeof response.success !== "boolean") {
|
|
223
|
+
errors.push("success must be a boolean");
|
|
199
224
|
}
|
|
200
225
|
|
|
201
226
|
if (!Array.isArray(response.widgets)) {
|
|
202
|
-
errors.push(
|
|
227
|
+
errors.push("widgets must be an array");
|
|
203
228
|
return { ok: errors.length === 0, errors };
|
|
204
229
|
}
|
|
205
230
|
|
|
@@ -210,9 +235,9 @@ export function validateTopoloWidgetResponse(
|
|
|
210
235
|
if (
|
|
211
236
|
response.success === false &&
|
|
212
237
|
response.error !== undefined &&
|
|
213
|
-
typeof response.error !==
|
|
238
|
+
typeof response.error !== "string"
|
|
214
239
|
) {
|
|
215
|
-
errors.push(
|
|
240
|
+
errors.push("error must be a string when provided");
|
|
216
241
|
}
|
|
217
242
|
|
|
218
243
|
return { ok: errors.length === 0, errors };
|
|
@@ -227,24 +252,25 @@ function validateWidget(widget: unknown, path: string, errors: string[]) {
|
|
|
227
252
|
validateNonEmptyString(widget.serviceId, `${path}.serviceId`, errors);
|
|
228
253
|
validateNonEmptyString(widget.serviceName, `${path}.serviceName`, errors);
|
|
229
254
|
validateNonEmptyString(widget.lastUpdated, `${path}.lastUpdated`, errors);
|
|
255
|
+
validateSnapshotMetadata(widget.snapshot, `${path}.snapshot`, errors);
|
|
230
256
|
|
|
231
257
|
switch (widget.type) {
|
|
232
|
-
case
|
|
258
|
+
case "stats":
|
|
233
259
|
validateArray(widget.stats, `${path}.stats`, errors);
|
|
234
260
|
break;
|
|
235
|
-
case
|
|
261
|
+
case "recent-activity":
|
|
236
262
|
validateArray(widget.activities, `${path}.activities`, errors);
|
|
237
263
|
break;
|
|
238
|
-
case
|
|
264
|
+
case "quick-actions":
|
|
239
265
|
validateArray(widget.actions, `${path}.actions`, errors);
|
|
240
266
|
break;
|
|
241
|
-
case
|
|
267
|
+
case "notifications":
|
|
242
268
|
validateArray(widget.notifications, `${path}.notifications`, errors);
|
|
243
|
-
if (typeof widget.unreadCount !==
|
|
269
|
+
if (typeof widget.unreadCount !== "number") {
|
|
244
270
|
errors.push(`${path}.unreadCount must be a number`);
|
|
245
271
|
}
|
|
246
272
|
break;
|
|
247
|
-
case
|
|
273
|
+
case "chart":
|
|
248
274
|
validateNonEmptyString(widget.chartType, `${path}.chartType`, errors);
|
|
249
275
|
validateNonEmptyString(widget.title, `${path}.title`, errors);
|
|
250
276
|
if (!isRecord(widget.data)) {
|
|
@@ -259,12 +285,58 @@ function validateWidget(widget: unknown, path: string, errors: string[]) {
|
|
|
259
285
|
}
|
|
260
286
|
}
|
|
261
287
|
|
|
288
|
+
function validateSnapshotMetadata(
|
|
289
|
+
snapshot: unknown,
|
|
290
|
+
path: string,
|
|
291
|
+
errors: string[],
|
|
292
|
+
) {
|
|
293
|
+
if (snapshot === undefined) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (!isRecord(snapshot)) {
|
|
297
|
+
errors.push(`${path} must be an object when provided`);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (
|
|
301
|
+
snapshot.defaultMetricKeys !== undefined &&
|
|
302
|
+
!Array.isArray(snapshot.defaultMetricKeys)
|
|
303
|
+
) {
|
|
304
|
+
errors.push(`${path}.defaultMetricKeys must be an array when provided`);
|
|
305
|
+
}
|
|
306
|
+
if (
|
|
307
|
+
snapshot.highlights !== undefined &&
|
|
308
|
+
!Array.isArray(snapshot.highlights)
|
|
309
|
+
) {
|
|
310
|
+
errors.push(`${path}.highlights must be an array when provided`);
|
|
311
|
+
}
|
|
312
|
+
if (snapshot.availableMetrics === undefined) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (!Array.isArray(snapshot.availableMetrics)) {
|
|
316
|
+
errors.push(`${path}.availableMetrics must be an array when provided`);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
snapshot.availableMetrics.forEach((metric, index) => {
|
|
321
|
+
const metricPath = `${path}.availableMetrics[${index}]`;
|
|
322
|
+
if (!isRecord(metric)) {
|
|
323
|
+
errors.push(`${metricPath} must be an object`);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
validateNonEmptyString(metric.key, `${metricPath}.key`, errors);
|
|
327
|
+
validateNonEmptyString(metric.label, `${metricPath}.label`, errors);
|
|
328
|
+
if (typeof metric.value !== "number" || Number.isNaN(metric.value)) {
|
|
329
|
+
errors.push(`${metricPath}.value must be a number`);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
262
334
|
function validateNonEmptyString(
|
|
263
335
|
value: unknown,
|
|
264
336
|
path: string,
|
|
265
|
-
errors: string[]
|
|
337
|
+
errors: string[],
|
|
266
338
|
) {
|
|
267
|
-
if (typeof value !==
|
|
339
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
268
340
|
errors.push(`${path} must be a non-empty string`);
|
|
269
341
|
}
|
|
270
342
|
}
|
|
@@ -276,7 +348,7 @@ function validateArray(value: unknown, path: string, errors: string[]) {
|
|
|
276
348
|
}
|
|
277
349
|
|
|
278
350
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
279
|
-
return typeof value ===
|
|
351
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
280
352
|
}
|
|
281
353
|
|
|
282
354
|
function toIsoTimestamp(input: Date | string | undefined): string {
|