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