@checkstack/dashboard-frontend 0.3.2 → 0.3.4
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/CHANGELOG.md +20 -0
- package/package.json +2 -1
- package/src/Dashboard.tsx +14 -17
- package/src/notification-group-ids.test.ts +115 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @checkstack/dashboard-frontend
|
|
2
2
|
|
|
3
|
+
## 0.3.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [10aa9fb]
|
|
8
|
+
- Updated dependencies [d94121b]
|
|
9
|
+
- @checkstack/auth-frontend@0.5.0
|
|
10
|
+
- @checkstack/ui@0.2.4
|
|
11
|
+
- @checkstack/catalog-frontend@0.3.4
|
|
12
|
+
- @checkstack/command-frontend@0.2.3
|
|
13
|
+
- @checkstack/queue-frontend@0.2.3
|
|
14
|
+
|
|
15
|
+
## 0.3.3
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- cad3073: Fixed notification group subscription for catalog groups:
|
|
20
|
+
- Fixed group ID format using colon separator instead of dots and missing entity type prefix
|
|
21
|
+
- Fixed subscription button state not updating after subscribe/unsubscribe by using refetch instead of invalidateQueries
|
|
22
|
+
|
|
3
23
|
## 0.3.2
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/dashboard-frontend",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.tsx",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"test": "bun test",
|
|
7
8
|
"typecheck": "tsc --noEmit",
|
|
8
9
|
"lint": "bun run lint:code",
|
|
9
10
|
"lint:code": "eslint . --max-warnings 0"
|
package/src/Dashboard.tsx
CHANGED
|
@@ -3,7 +3,6 @@ import { useNavigate } from "react-router-dom";
|
|
|
3
3
|
import {
|
|
4
4
|
useApi,
|
|
5
5
|
usePluginClient,
|
|
6
|
-
useQueryClient,
|
|
7
6
|
ExtensionSlot,
|
|
8
7
|
} from "@checkstack/frontend-api";
|
|
9
8
|
import {
|
|
@@ -57,10 +56,10 @@ interface GroupWithSystems extends Group {
|
|
|
57
56
|
systems: System[];
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
const getGroupId = (groupId: string) => `${CATALOG_PLUGIN_ID}
|
|
59
|
+
const getGroupId = (groupId: string) => `${CATALOG_PLUGIN_ID}.group.${groupId}`;
|
|
61
60
|
|
|
62
61
|
const statusToVariant = (
|
|
63
|
-
status: string
|
|
62
|
+
status: string,
|
|
64
63
|
): "default" | "success" | "warning" | "error" => {
|
|
65
64
|
switch (status) {
|
|
66
65
|
case "healthy": {
|
|
@@ -87,7 +86,6 @@ export const Dashboard: React.FC = () => {
|
|
|
87
86
|
const navigate = useNavigate();
|
|
88
87
|
const toast = useToast();
|
|
89
88
|
const authApi = useApi(authApiRef);
|
|
90
|
-
const queryClient = useQueryClient();
|
|
91
89
|
const { data: session } = authApi.useSession();
|
|
92
90
|
|
|
93
91
|
// Terminal feed entries from real healthcheck signals
|
|
@@ -112,7 +110,7 @@ export const Dashboard: React.FC = () => {
|
|
|
112
110
|
const { data: incidentsData, isLoading: incidentsLoading } =
|
|
113
111
|
incidentClient.listIncidents.useQuery(
|
|
114
112
|
{ includeResolved: false },
|
|
115
|
-
{ staleTime: 30_000 }
|
|
113
|
+
{ staleTime: 30_000 },
|
|
116
114
|
);
|
|
117
115
|
const incidents = incidentsData?.incidents ?? [];
|
|
118
116
|
|
|
@@ -120,15 +118,15 @@ export const Dashboard: React.FC = () => {
|
|
|
120
118
|
const { data: maintenancesData, isLoading: maintenancesLoading } =
|
|
121
119
|
maintenanceClient.listMaintenances.useQuery(
|
|
122
120
|
{ status: "in_progress" },
|
|
123
|
-
{ staleTime: 30_000 }
|
|
121
|
+
{ staleTime: 30_000 },
|
|
124
122
|
);
|
|
125
123
|
const maintenances = maintenancesData?.maintenances ?? [];
|
|
126
124
|
|
|
127
125
|
// Fetch subscriptions (only when logged in)
|
|
128
|
-
const { data: subscriptions = [] } =
|
|
126
|
+
const { data: subscriptions = [], refetch: refetchSubscriptions } =
|
|
129
127
|
notificationClient.getSubscriptions.useQuery(
|
|
130
128
|
{},
|
|
131
|
-
{ enabled: !!session, staleTime: 60_000 }
|
|
129
|
+
{ enabled: !!session, staleTime: 60_000 },
|
|
132
130
|
);
|
|
133
131
|
|
|
134
132
|
// Combined loading state
|
|
@@ -141,8 +139,7 @@ export const Dashboard: React.FC = () => {
|
|
|
141
139
|
const subscribeMutation = notificationClient.subscribe.useMutation({
|
|
142
140
|
onSuccess: () => {
|
|
143
141
|
toast.success("Subscribed to group notifications");
|
|
144
|
-
|
|
145
|
-
queryClient.invalidateQueries({ queryKey: ["notification"] });
|
|
142
|
+
void refetchSubscriptions();
|
|
146
143
|
},
|
|
147
144
|
onError: (error: Error) => {
|
|
148
145
|
toast.error(error.message || "Failed to subscribe");
|
|
@@ -152,7 +149,7 @@ export const Dashboard: React.FC = () => {
|
|
|
152
149
|
const unsubscribeMutation = notificationClient.unsubscribe.useMutation({
|
|
153
150
|
onSuccess: () => {
|
|
154
151
|
toast.success("Unsubscribed from group notifications");
|
|
155
|
-
|
|
152
|
+
void refetchSubscriptions();
|
|
156
153
|
},
|
|
157
154
|
onError: (error: Error) => {
|
|
158
155
|
toast.error(error.message || "Failed to unsubscribe");
|
|
@@ -195,9 +192,9 @@ export const Dashboard: React.FC = () => {
|
|
|
195
192
|
};
|
|
196
193
|
|
|
197
194
|
setTerminalEntries((prev) =>
|
|
198
|
-
[newEntry, ...prev].slice(0, MAX_TERMINAL_ENTRIES)
|
|
195
|
+
[newEntry, ...prev].slice(0, MAX_TERMINAL_ENTRIES),
|
|
199
196
|
);
|
|
200
|
-
}
|
|
197
|
+
},
|
|
201
198
|
);
|
|
202
199
|
|
|
203
200
|
// -------------------------------------------------------------------------
|
|
@@ -211,7 +208,7 @@ export const Dashboard: React.FC = () => {
|
|
|
211
208
|
const isSubscribed = (groupId: string) => {
|
|
212
209
|
const fullId = getGroupId(groupId);
|
|
213
210
|
return subscriptions.some(
|
|
214
|
-
(s: EnrichedSubscription) => s.groupId === fullId
|
|
211
|
+
(s: EnrichedSubscription) => s.groupId === fullId,
|
|
215
212
|
);
|
|
216
213
|
};
|
|
217
214
|
|
|
@@ -224,7 +221,7 @@ export const Dashboard: React.FC = () => {
|
|
|
224
221
|
onSettled: () => {
|
|
225
222
|
setSubscriptionLoading((prev) => ({ ...prev, [groupId]: false }));
|
|
226
223
|
},
|
|
227
|
-
}
|
|
224
|
+
},
|
|
228
225
|
);
|
|
229
226
|
};
|
|
230
227
|
|
|
@@ -237,7 +234,7 @@ export const Dashboard: React.FC = () => {
|
|
|
237
234
|
onSettled: () => {
|
|
238
235
|
setSubscriptionLoading((prev) => ({ ...prev, [groupId]: false }));
|
|
239
236
|
},
|
|
240
|
-
}
|
|
237
|
+
},
|
|
241
238
|
);
|
|
242
239
|
};
|
|
243
240
|
|
|
@@ -262,7 +259,7 @@ export const Dashboard: React.FC = () => {
|
|
|
262
259
|
|
|
263
260
|
// Collect all system IDs for bulk data fetching
|
|
264
261
|
const allSystemIds = groupsWithSystems.flatMap((g) =>
|
|
265
|
-
g.systems.map((s) => s.id)
|
|
262
|
+
g.systems.map((s) => s.id),
|
|
266
263
|
);
|
|
267
264
|
|
|
268
265
|
return (
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Notification Group ID Format Tests
|
|
5
|
+
*
|
|
6
|
+
* These tests ensure the correct format for notification group IDs.
|
|
7
|
+
* The format must match what the backend creates:
|
|
8
|
+
* - Format: `{pluginId}.{entityType}.{entityId}`
|
|
9
|
+
* - Example: `catalog.group.855f7a1f-7287-4650-abf3-f91117e3bde1`
|
|
10
|
+
*
|
|
11
|
+
* Regression test for: Dashboard subscription failures due to incorrect
|
|
12
|
+
* group ID format (was using colon separator and missing entity type prefix).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const CATALOG_PLUGIN_ID = "catalog";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Constructs the full notification group ID for a catalog group.
|
|
19
|
+
* Must match the format created by catalog-backend notification group creation.
|
|
20
|
+
*/
|
|
21
|
+
export const getCatalogGroupNotificationId = (groupId: string) =>
|
|
22
|
+
`${CATALOG_PLUGIN_ID}.group.${groupId}`;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Constructs the full notification group ID for a catalog system.
|
|
26
|
+
* Must match the format created by catalog-backend notification group creation.
|
|
27
|
+
*/
|
|
28
|
+
export const getCatalogSystemNotificationId = (systemId: string) =>
|
|
29
|
+
`${CATALOG_PLUGIN_ID}.system.${systemId}`;
|
|
30
|
+
|
|
31
|
+
describe("Notification Group ID Format", () => {
|
|
32
|
+
describe("getCatalogGroupNotificationId", () => {
|
|
33
|
+
test("uses dot separators, not colons", () => {
|
|
34
|
+
const groupId = "test-uuid";
|
|
35
|
+
const result = getCatalogGroupNotificationId(groupId);
|
|
36
|
+
|
|
37
|
+
// Must use dots, not colons
|
|
38
|
+
expect(result).not.toContain(":");
|
|
39
|
+
expect(result).toBe("catalog.group.test-uuid");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("includes 'group' type prefix", () => {
|
|
43
|
+
const groupId = "855f7a1f-7287-4650-abf3-f91117e3bde1";
|
|
44
|
+
const result = getCatalogGroupNotificationId(groupId);
|
|
45
|
+
|
|
46
|
+
// Must include the entity type
|
|
47
|
+
expect(result).toContain(".group.");
|
|
48
|
+
expect(result).toBe("catalog.group.855f7a1f-7287-4650-abf3-f91117e3bde1");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("follows {pluginId}.{entityType}.{entityId} format", () => {
|
|
52
|
+
const groupId = "my-group-id";
|
|
53
|
+
const result = getCatalogGroupNotificationId(groupId);
|
|
54
|
+
|
|
55
|
+
const parts = result.split(".");
|
|
56
|
+
expect(parts).toHaveLength(3);
|
|
57
|
+
expect(parts[0]).toBe("catalog"); // pluginId
|
|
58
|
+
expect(parts[1]).toBe("group"); // entityType
|
|
59
|
+
expect(parts[2]).toBe("my-group-id"); // entityId
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("getCatalogSystemNotificationId", () => {
|
|
64
|
+
test("uses dot separators, not colons", () => {
|
|
65
|
+
const systemId = "test-uuid";
|
|
66
|
+
const result = getCatalogSystemNotificationId(systemId);
|
|
67
|
+
|
|
68
|
+
// Must use dots, not colons
|
|
69
|
+
expect(result).not.toContain(":");
|
|
70
|
+
expect(result).toBe("catalog.system.test-uuid");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("includes 'system' type prefix", () => {
|
|
74
|
+
const systemId = "855f7a1f-7287-4650-abf3-f91117e3bde1";
|
|
75
|
+
const result = getCatalogSystemNotificationId(systemId);
|
|
76
|
+
|
|
77
|
+
// Must include the entity type
|
|
78
|
+
expect(result).toContain(".system.");
|
|
79
|
+
expect(result).toBe(
|
|
80
|
+
"catalog.system.855f7a1f-7287-4650-abf3-f91117e3bde1",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("follows {pluginId}.{entityType}.{entityId} format", () => {
|
|
85
|
+
const systemId = "my-system-id";
|
|
86
|
+
const result = getCatalogSystemNotificationId(systemId);
|
|
87
|
+
|
|
88
|
+
const parts = result.split(".");
|
|
89
|
+
expect(parts).toHaveLength(3);
|
|
90
|
+
expect(parts[0]).toBe("catalog"); // pluginId
|
|
91
|
+
expect(parts[1]).toBe("system"); // entityType
|
|
92
|
+
expect(parts[2]).toBe("my-system-id"); // entityId
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("Format consistency between group and system", () => {
|
|
97
|
+
test("both use same separator and structure", () => {
|
|
98
|
+
const id = "test-id";
|
|
99
|
+
const groupResult = getCatalogGroupNotificationId(id);
|
|
100
|
+
const systemResult = getCatalogSystemNotificationId(id);
|
|
101
|
+
|
|
102
|
+
// Same plugin prefix
|
|
103
|
+
expect(groupResult.startsWith("catalog.")).toBe(true);
|
|
104
|
+
expect(systemResult.startsWith("catalog.")).toBe(true);
|
|
105
|
+
|
|
106
|
+
// Same structure (3 parts)
|
|
107
|
+
expect(groupResult.split(".")).toHaveLength(3);
|
|
108
|
+
expect(systemResult.split(".")).toHaveLength(3);
|
|
109
|
+
|
|
110
|
+
// Different entity types
|
|
111
|
+
expect(groupResult).toContain(".group.");
|
|
112
|
+
expect(systemResult).toContain(".system.");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|