github-issue-tower-defence-management 1.167.0 → 1.168.0
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/.github/workflows/console-ui.yml +1 -0
- package/CHANGELOG.md +13 -0
- package/bin/adapter/entry-points/console/ui-dist/assets/index-DkAuY63u.js +86 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/adapter/entry-points/console/ui-dist/sw.js +60 -0
- package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +7 -1
- package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/ci/consoleUiWorkflowTimeout.test.ts +61 -0
- package/src/adapter/entry-points/console/ui/public/sw.js +60 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +9 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +31 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.tsx +8 -1
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +111 -15
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +100 -15
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +70 -1
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +42 -6
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +2 -0
- package/src/adapter/entry-points/console/ui/src/main.tsx +6 -0
- package/src/adapter/entry-points/console/ui-dist/assets/index-DkAuY63u.js +86 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/adapter/entry-points/console/ui-dist/sw.js +60 -0
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +13 -0
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +6 -1
- package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-CrSqMAVR.js +0 -86
- package/src/adapter/entry-points/console/ui-dist/assets/index-CrSqMAVR.js +0 -86
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>TDPM Console</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-DkAuY63u.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-U8I8ESZj.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const SHELL_CACHE = 'console-shell-v1';
|
|
2
|
+
|
|
3
|
+
self.addEventListener('install', (event) => {
|
|
4
|
+
event.waitUntil(caches.open(SHELL_CACHE).then((cache) => cache.add('/')));
|
|
5
|
+
self.skipWaiting();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
self.addEventListener('activate', (event) => {
|
|
9
|
+
event.waitUntil(
|
|
10
|
+
caches
|
|
11
|
+
.keys()
|
|
12
|
+
.then((names) =>
|
|
13
|
+
Promise.all(
|
|
14
|
+
names
|
|
15
|
+
.filter((name) => name !== SHELL_CACHE)
|
|
16
|
+
.map((name) => caches.delete(name)),
|
|
17
|
+
),
|
|
18
|
+
),
|
|
19
|
+
);
|
|
20
|
+
self.clients.claim();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
self.addEventListener('fetch', (event) => {
|
|
24
|
+
if (event.request.method !== 'GET') {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const url = new URL(event.request.url);
|
|
28
|
+
if (url.origin !== self.location.origin) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (
|
|
32
|
+
url.pathname.startsWith('/projects/') ||
|
|
33
|
+
url.pathname.startsWith('/api/')
|
|
34
|
+
) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
event.respondWith(
|
|
38
|
+
fetch(event.request)
|
|
39
|
+
.then((response) => {
|
|
40
|
+
if (response.ok) {
|
|
41
|
+
caches
|
|
42
|
+
.open(SHELL_CACHE)
|
|
43
|
+
.then((cache) => cache.put(event.request, response.clone()))
|
|
44
|
+
.catch((e) => console.warn('Shell cache put failed:', e));
|
|
45
|
+
}
|
|
46
|
+
return response;
|
|
47
|
+
})
|
|
48
|
+
.catch(async () => {
|
|
49
|
+
const cached = await caches.match(event.request);
|
|
50
|
+
if (cached !== undefined) {
|
|
51
|
+
return cached;
|
|
52
|
+
}
|
|
53
|
+
const root = await caches.match('/');
|
|
54
|
+
if (root !== undefined) {
|
|
55
|
+
return root;
|
|
56
|
+
}
|
|
57
|
+
return Response.error();
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
@@ -160,7 +160,13 @@ class GenerateConsoleListsUseCase {
|
|
|
160
160
|
position,
|
|
161
161
|
sortKey: indexByStory.get(item.story) ?? UNKNOWN_STORY_SORT_INDEX,
|
|
162
162
|
}))
|
|
163
|
-
.sort((a, b) =>
|
|
163
|
+
.sort((a, b) => {
|
|
164
|
+
if (a.sortKey !== b.sortKey)
|
|
165
|
+
return a.sortKey - b.sortKey;
|
|
166
|
+
if (a.item.story !== b.item.story)
|
|
167
|
+
return a.item.story.localeCompare(b.item.story);
|
|
168
|
+
return a.position - b.position;
|
|
169
|
+
})
|
|
164
170
|
.map((entry) => entry.item);
|
|
165
171
|
};
|
|
166
172
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GenerateConsoleListsUseCase.js","sourceRoot":"","sources":["../../../../src/domain/usecases/console/GenerateConsoleListsUseCase.ts"],"names":[],"mappings":";;;AAEA,kEAKuC;AA6FvC,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAExC,MAAa,2BAA2B;IAAxC;QACE,QAAG,GAAG,CAAC,KAAgC,EAAgB,EAAE;YACvD,MAAM,EACJ,OAAO,EACP,MAAM,EACN,MAAM,EACN,aAAa,EACb,WAAW,EACX,wBAAwB,GACzB,GAAG,KAAK,CAAC;YAEV,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YAChE,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC7D,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC;YAE9C,MAAM,oCAAoC,GACxC,IAAI,CAAC,yCAAyC,CAAC,MAAM,CAAC,CAAC;YAEzD,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CACjC,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,MAAM,EAAE,WAAW,EAAE;gBAC3B,6CAA4B,CAAC,WAAW,EAAE,CAC7C,CAAC;YAEF,MAAM,gBAAgB,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CACtD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,aAAa,CAAC,CACxC,CAAC;YAEF,MAAM,wBAAwB,GAAG,CAC/B,YAAqB,EACrB,QAAmC,EACnC,mBAA6B,EACX,EAAE,CAAC,CAAC;gBACtB,MAAM;gBACN,WAAW;gBACX,aAAa,EAAE,IAAI,CAAC,iBAAiB,CAAC,aAAa,EAAE,mBAAmB,CAAC;gBACzE,UAAU;gBACV,WAAW,EAAE,IAAI,CAAC,sBAAsB,CAAC,YAAY,CAAC;gBACtD,KAAK,EAAE,IAAI,CAAC,gBAAgB,CAC1B,YAAY;qBACT,MAAM,CAAC,QAAQ,CAAC;qBAChB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACb,IAAI,CAAC,WAAW,CACd,KAAK,EACL,oCAAoC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAC1D,CACF,EACH,UAAU,CACX;aACF,CAAC,CAAC;YAEH,MAAM,cAAc,GAAG,CACrB,QAAmC,EACnC,mBAA6B,EACX,EAAE,CACpB,wBAAwB,CAAC,gBAAgB,EAAE,QAAQ,EAAE,mBAAmB,CAAC,CAAC;YAE5E,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAkB,CAAC;YACvD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;oBAC5C,oBAAoB,CAAC,GAAG,CACtB,KAAK,CAAC,KAAK,EACX,CAAC,oBAAoB,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CACjD,CAAC;gBACJ,CAAC;YACH,CAAC;YAED,MAAM,oBAAoB,GACxB,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,aAAa,KAAK,EAAE,CAAC,EAAE,aAAa,IAAI,IAAI,CAAC;YAE5E,MAAM,YAAY,GAAwB,YAAY;iBACnD,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC;iBAC3C,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBAChB,SAAS,EAAE,MAAM,CAAC,IAAI;gBACtB,aAAa,EAAE,MAAM,CAAC,EAAE;gBACxB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,aAAa,EAAE,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;aAC1D,CAAC,CAAC,CAAC;YAEN,OAAO;gBACL,kBAAkB,EAAE,wBAAwB,CAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,KAAK,CAAC,EAClD,IAAI,CAAC,uBAAuB,CAAC,wBAAwB,CAAC,EACtD,CAAC,MAAM,CAAC,CACT;gBACD,GAAG,EAAE,cAAc,CACjB,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,MAAM,KAAK,IAAI;oBACrB,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,wBAAwB,EACzD,CAAC,wBAAwB,EAAE,MAAM,CAAC,CACnC;gBACD,MAAM,EAAE,cAAc,CACpB,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,QAAQ,EAClE,CAAC,QAAQ,EAAE,MAAM,CAAC,CACnB;gBACD,oBAAoB,EAAE,cAAc,CAClC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,oBAAoB,EAChD;oBACE,oBAAoB;oBACpB,MAAM;oBACN,aAAa;oBACb,QAAQ;oBACR,QAAQ;oBACR,kBAAkB;oBAClB,kBAAkB;oBAClB,eAAe;iBAChB,CACF;gBACD,eAAe,EAAE,cAAc,CAC7B,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,MAAM,KAAK,iCAAgB;oBACjC,KAAK,CAAC,MAAM,KAAK,wCAAuB,EAC1C,CAAC,iCAAgB,CAAC,WAAW,EAAE,EAAE,MAAM,CAAC,CACzC;gBACD,eAAe,EAAE,cAAc,CAC7B,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,0CAAyB,EACrD,CAAC,0CAAyB,CAAC,WAAW,EAAE,EAAE,MAAM,CAAC,CAClD;gBACD,MAAM,EAAE;oBACN,MAAM;oBACN,WAAW;oBACX,YAAY,EAAE,IAAI,CAAC,iBAAiB,CAAC,YAAY,EAAE,EAAE,CAAC;oBACtD,UAAU;oBACV,WAAW,EAAE,IAAI,CAAC,sBAAsB,CAAC,YAAY,CAAC;oBACtD,KAAK,EAAE,IAAI,CAAC,gBAAgB,CAC1B,MAAM;yBACH,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;yBACxD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACb,IAAI,CAAC,WAAW,CACd,KAAK,EACL,oCAAoC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAC1D,CACF,EACH,UAAU,CACX;iBACF;gBACD,OAAO,EAAE;oBACP,MAAM;oBACN,WAAW;oBACX,OAAO,EAAE,YAAY;oBACrB,UAAU;oBACV,WAAW,EAAE,IAAI,CAAC,sBAAsB,CAAC,YAAY,CAAC;oBACtD,oBAAoB;iBACrB;aACF,CAAC;QACJ,CAAC,CAAC;QAEM,eAAU,GAAG,CAAC,KAAY,EAAE,aAAqB,EAAW,EAAE,CACpE,KAAK,CAAC,QAAQ,KAAK,KAAK;YACxB,KAAK,CAAC,IAAI,KAAK,KAAK;YACpB,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC;YACvC,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;QAEnE,iBAAY,GAAG,CAAC,KAAY,EAAE,aAAqB,EAAW,EAAE,CACtE,KAAK,CAAC,QAAQ,KAAK,KAAK;YACxB,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC;YACvC,KAAK,CAAC,iBAAiB,CAAC,MAAM,KAAK,CAAC;YACpC,KAAK,CAAC,cAAc,KAAK,IAAI;YAC7B,KAAK,CAAC,cAAc,KAAK,IAAI,CAAC;QAExB,4BAAuB,GAAG,CAChC,wBAAuC,EACV,EAAE;YAC/B,MAAM,MAAM,GAAG,wBAAwB,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;YAC7D,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;gBAClB,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC;YACrB,CAAC;YACD,OAAO,CAAC,KAAY,EAAW,EAAE,CAC/B,KAAK,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;QACjE,CAAC,CAAC;QAEM,8CAAyC,GAAG,CAClD,MAAe,EACQ,EAAE;YACzB,MAAM,cAAc,GAAG,IAAI,GAAG,EAAoB,CAAC;YACnD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;oBAClC,SAAS;gBACX,CAAC;gBACD,KAAK,MAAM,kBAAkB,IAAI,KAAK,CAAC,yBAAyB,EAAE,CAAC;oBACjE,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;oBACxD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;wBAC3B,cAAc,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;wBACpD,SAAS;oBACX,CAAC;oBACD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;wBAClC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;oBAC3B,CAAC;gBACH,CAAC;YACH,CAAC;YACD,OAAO,cAAc,CAAC;QACxB,CAAC,CAAC;QAEM,gBAAW,GAAG,CACpB,KAAY,EACZ,0BAAoC,EACnB,EAAE,CAAC,CAAC;YACrB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,IAAI,EAAE,KAAK,CAAC,aAAa;YACzB,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,aAAa,EAAE,KAAK,CAAC,MAAM;YAC3B,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE;YACxB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,cAAc,EACZ,KAAK,CAAC,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,WAAW,EAAE;YAC3E,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;YAC1C,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE;YACxC,0BAA0B;SAC3B,CAAC,CAAC;QAEK,sBAAiB,GAAG,CAC1B,OAAsB,EACtB,sBAAgC,EACV,EAAE,CACxB,OAAO;aACJ,MAAM,CACL,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,sBAAsB,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CACxE;aACA,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAChB,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC,CAAC,CAAC;QAEA,2BAAsB,GAAG,CAC/B,OAAsB,EACmB,EAAE;YAC3C,MAAM,MAAM,GAA4C,EAAE,CAAC;YAC3D,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;YAChD,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC;QAEM,2BAAsB,GAAG,CAC/B,OAAsB,EACQ,EAAE;YAChC,MAAM,MAAM,GAAiC,EAAE,CAAC;YAChD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC;YACrC,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC;QAEM,qBAAgB,GAAG,CACzB,KAAwB,EACxB,UAAoB,EACD,EAAE;YACrB,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAC/C,CAAC;YACF,OAAO,KAAK;iBACT,GAAG,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;gBACxB,IAAI;gBACJ,QAAQ;gBACR,OAAO,EAAE,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,wBAAwB;aAClE,CAAC,CAAC;iBACF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"GenerateConsoleListsUseCase.js","sourceRoot":"","sources":["../../../../src/domain/usecases/console/GenerateConsoleListsUseCase.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { parse } from 'yaml';
|
|
4
|
+
|
|
5
|
+
const repositoryRoot = path.resolve(__dirname, '..', '..', '..');
|
|
6
|
+
const workflowDirectory = path.join(repositoryRoot, '.github', 'workflows');
|
|
7
|
+
const consoleUiWorkflowFileName = 'console-ui.yml';
|
|
8
|
+
const consoleUiJobId = 'console-ui';
|
|
9
|
+
const playwrightInstallStepName =
|
|
10
|
+
'Install Playwright browsers (console UI E2E)';
|
|
11
|
+
|
|
12
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
13
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
|
+
|
|
15
|
+
const readWorkflow = (fileName: string): Record<string, unknown> => {
|
|
16
|
+
const workflow: unknown = parse(
|
|
17
|
+
fs.readFileSync(path.join(workflowDirectory, fileName), 'utf8'),
|
|
18
|
+
);
|
|
19
|
+
if (!isRecord(workflow)) {
|
|
20
|
+
throw new Error(`${fileName} does not parse to a workflow mapping`);
|
|
21
|
+
}
|
|
22
|
+
return workflow;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const workflowJob = (
|
|
26
|
+
workflow: Record<string, unknown>,
|
|
27
|
+
jobId: string,
|
|
28
|
+
): Record<string, unknown> => {
|
|
29
|
+
const jobs = workflow.jobs;
|
|
30
|
+
if (!isRecord(jobs)) {
|
|
31
|
+
throw new Error(`workflow does not declare a jobs mapping`);
|
|
32
|
+
}
|
|
33
|
+
const job = jobs[jobId];
|
|
34
|
+
if (!isRecord(job)) {
|
|
35
|
+
throw new Error(`workflow does not declare job ${jobId}`);
|
|
36
|
+
}
|
|
37
|
+
return job;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const consoleUiJob = (): Record<string, unknown> =>
|
|
41
|
+
workflowJob(readWorkflow(consoleUiWorkflowFileName), consoleUiJobId);
|
|
42
|
+
|
|
43
|
+
describe('console-ui workflow timeout', () => {
|
|
44
|
+
it('declares a job-level timeout-minutes so a stalled apt phase in Playwright browser installation cannot hold the check pending for hours', () => {
|
|
45
|
+
const timeoutMinutes = consoleUiJob()['timeout-minutes'];
|
|
46
|
+
expect(typeof timeoutMinutes).toBe('number');
|
|
47
|
+
expect(Number(timeoutMinutes)).toBeGreaterThan(0);
|
|
48
|
+
expect(Number(timeoutMinutes)).toBeLessThanOrEqual(60);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('names the Playwright browser installation step so it is identifiable in run logs', () => {
|
|
52
|
+
const steps = consoleUiJob().steps;
|
|
53
|
+
if (!Array.isArray(steps)) {
|
|
54
|
+
throw new Error('steps is not an array');
|
|
55
|
+
}
|
|
56
|
+
const playwrightInstallStep = steps
|
|
57
|
+
.filter(isRecord)
|
|
58
|
+
.find((step) => step.name === playwrightInstallStepName);
|
|
59
|
+
expect(playwrightInstallStep).toBeDefined();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const SHELL_CACHE = 'console-shell-v1';
|
|
2
|
+
|
|
3
|
+
self.addEventListener('install', (event) => {
|
|
4
|
+
event.waitUntil(caches.open(SHELL_CACHE).then((cache) => cache.add('/')));
|
|
5
|
+
self.skipWaiting();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
self.addEventListener('activate', (event) => {
|
|
9
|
+
event.waitUntil(
|
|
10
|
+
caches
|
|
11
|
+
.keys()
|
|
12
|
+
.then((names) =>
|
|
13
|
+
Promise.all(
|
|
14
|
+
names
|
|
15
|
+
.filter((name) => name !== SHELL_CACHE)
|
|
16
|
+
.map((name) => caches.delete(name)),
|
|
17
|
+
),
|
|
18
|
+
),
|
|
19
|
+
);
|
|
20
|
+
self.clients.claim();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
self.addEventListener('fetch', (event) => {
|
|
24
|
+
if (event.request.method !== 'GET') {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const url = new URL(event.request.url);
|
|
28
|
+
if (url.origin !== self.location.origin) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (
|
|
32
|
+
url.pathname.startsWith('/projects/') ||
|
|
33
|
+
url.pathname.startsWith('/api/')
|
|
34
|
+
) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
event.respondWith(
|
|
38
|
+
fetch(event.request)
|
|
39
|
+
.then((response) => {
|
|
40
|
+
if (response.ok) {
|
|
41
|
+
caches
|
|
42
|
+
.open(SHELL_CACHE)
|
|
43
|
+
.then((cache) => cache.put(event.request, response.clone()))
|
|
44
|
+
.catch((e) => console.warn('Shell cache put failed:', e));
|
|
45
|
+
}
|
|
46
|
+
return response;
|
|
47
|
+
})
|
|
48
|
+
.catch(async () => {
|
|
49
|
+
const cached = await caches.match(event.request);
|
|
50
|
+
if (cached !== undefined) {
|
|
51
|
+
return cached;
|
|
52
|
+
}
|
|
53
|
+
const root = await caches.match('/');
|
|
54
|
+
if (root !== undefined) {
|
|
55
|
+
return root;
|
|
56
|
+
}
|
|
57
|
+
return Response.error();
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
@@ -9,6 +9,7 @@ const meta: Meta<typeof ConsoleTabList> = {
|
|
|
9
9
|
args: {
|
|
10
10
|
pjcode: 'acme',
|
|
11
11
|
generatedAt: '2026-06-19T08:42:11.000Z',
|
|
12
|
+
fromCache: false,
|
|
12
13
|
tabHref: (tab: ConsoleTabName) => `/projects/acme/${tab}`,
|
|
13
14
|
onSelectTab: () => {},
|
|
14
15
|
},
|
|
@@ -85,6 +86,14 @@ export const AfterAutoAdvanceToNextTab: Story = {
|
|
|
85
86
|
},
|
|
86
87
|
};
|
|
87
88
|
|
|
89
|
+
export const CachedSnapshot: Story = {
|
|
90
|
+
args: {
|
|
91
|
+
activeTab: 'prs',
|
|
92
|
+
counts,
|
|
93
|
+
fromCache: true,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
88
97
|
export const Interactive: Story = {
|
|
89
98
|
render: (args) => {
|
|
90
99
|
const [activeTab, setActiveTab] = useState<ConsoleTabName>('prs');
|
|
@@ -16,6 +16,7 @@ const counts: Record<ConsoleTabName, number> = {
|
|
|
16
16
|
const baseProps = {
|
|
17
17
|
pjcode: 'acme',
|
|
18
18
|
generatedAt: '2026-06-19T08:42:11.000Z',
|
|
19
|
+
fromCache: false,
|
|
19
20
|
tabHref: (tab: ConsoleTabName) => `/projects/acme/${tab}`,
|
|
20
21
|
onSelectTab: () => {},
|
|
21
22
|
};
|
|
@@ -116,4 +117,34 @@ describe('ConsoleTabList', () => {
|
|
|
116
117
|
fireEvent.click(getByText('Unread'));
|
|
117
118
|
expect(onSelectTab).toHaveBeenCalledWith('unread');
|
|
118
119
|
});
|
|
120
|
+
|
|
121
|
+
it('prefixes the snapshot info with "(cached)" and sets data-from-cache when data is from cache', () => {
|
|
122
|
+
const { getByText } = render(
|
|
123
|
+
<ConsoleTabList
|
|
124
|
+
{...baseProps}
|
|
125
|
+
activeTab="prs"
|
|
126
|
+
counts={counts}
|
|
127
|
+
fromCache={true}
|
|
128
|
+
/>,
|
|
129
|
+
);
|
|
130
|
+
const genInfo = getByText('(cached) snapshot: 2026-06-19T08:42:11.000Z');
|
|
131
|
+
expect(genInfo).toBeInTheDocument();
|
|
132
|
+
expect(genInfo).toHaveAttribute('data-from-cache', 'true');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('omits the cache prefix and data-from-cache attribute when data is from the network', () => {
|
|
136
|
+
const { getByText, queryByText } = render(
|
|
137
|
+
<ConsoleTabList
|
|
138
|
+
{...baseProps}
|
|
139
|
+
activeTab="prs"
|
|
140
|
+
counts={counts}
|
|
141
|
+
fromCache={false}
|
|
142
|
+
/>,
|
|
143
|
+
);
|
|
144
|
+
expect(getByText('snapshot: 2026-06-19T08:42:11.000Z')).toBeInTheDocument();
|
|
145
|
+
expect(
|
|
146
|
+
queryByText('(cached) snapshot: 2026-06-19T08:42:11.000Z'),
|
|
147
|
+
).toBeNull();
|
|
148
|
+
expect(document.querySelector('[data-from-cache]')).toBeNull();
|
|
149
|
+
});
|
|
119
150
|
});
|
|
@@ -5,6 +5,7 @@ export type ConsoleTabBarProps = {
|
|
|
5
5
|
counts: Record<ConsoleTabName, number>;
|
|
6
6
|
pjcode: string | null;
|
|
7
7
|
generatedAt: string | null;
|
|
8
|
+
fromCache: boolean;
|
|
8
9
|
tabHref: (tab: ConsoleTabName) => string;
|
|
9
10
|
onSelectTab: (tab: ConsoleTabName) => void;
|
|
10
11
|
};
|
|
@@ -14,6 +15,7 @@ export const ConsoleTabList = ({
|
|
|
14
15
|
counts,
|
|
15
16
|
pjcode,
|
|
16
17
|
generatedAt,
|
|
18
|
+
fromCache,
|
|
17
19
|
tabHref,
|
|
18
20
|
onSelectTab,
|
|
19
21
|
}: ConsoleTabBarProps) => {
|
|
@@ -59,7 +61,12 @@ export const ConsoleTabList = ({
|
|
|
59
61
|
})}
|
|
60
62
|
{pjcode !== null && <span className="console-tab-pjname">{pjcode}</span>}
|
|
61
63
|
{generatedAt !== null && (
|
|
62
|
-
<span
|
|
64
|
+
<span
|
|
65
|
+
className="console-tab-geninfo"
|
|
66
|
+
data-from-cache={fromCache ? 'true' : undefined}
|
|
67
|
+
>
|
|
68
|
+
{fromCache ? '(cached) ' : ''}snapshot: {generatedAt}
|
|
69
|
+
</span>
|
|
63
70
|
)}
|
|
64
71
|
</nav>
|
|
65
72
|
);
|
package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts
CHANGED
|
@@ -5,27 +5,72 @@ import {
|
|
|
5
5
|
useConsoleTabData,
|
|
6
6
|
} from './useConsoleTabData';
|
|
7
7
|
|
|
8
|
+
const makeTabPayload = (overrides: Record<string, unknown> = {}) => ({
|
|
9
|
+
pjcode: 'acme',
|
|
10
|
+
generatedAt: '2026-06-19T00:00:00.000Z',
|
|
11
|
+
statusOptions: [{ id: 's1', name: 'Unread', color: 'ORANGE' }],
|
|
12
|
+
storyColors: {},
|
|
13
|
+
items: [],
|
|
14
|
+
...overrides,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const installNetworkFetch = (
|
|
18
|
+
perTabItems: (url: string) => unknown[] = () => [],
|
|
19
|
+
): jest.Mock => {
|
|
20
|
+
const fetchMock = jest.fn(async (url: string) => ({
|
|
21
|
+
ok: true,
|
|
22
|
+
status: 200,
|
|
23
|
+
json: async () =>
|
|
24
|
+
makeTabPayload({
|
|
25
|
+
items: url.includes('/prs/') ? perTabItems(url) : [],
|
|
26
|
+
}),
|
|
27
|
+
}));
|
|
28
|
+
global.fetch = fetchMock as unknown as typeof fetch;
|
|
29
|
+
return fetchMock;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type MockCacheEntry = { json: jest.Mock };
|
|
33
|
+
type MockCache = {
|
|
34
|
+
match: jest.Mock<Promise<MockCacheEntry | undefined>>;
|
|
35
|
+
put: jest.Mock<Promise<void>>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const installMockCaches = (
|
|
39
|
+
matchResult: MockCacheEntry | undefined,
|
|
40
|
+
): MockCache => {
|
|
41
|
+
const mockCache: MockCache = {
|
|
42
|
+
match: jest.fn(async () => matchResult),
|
|
43
|
+
put: jest.fn(async () => undefined),
|
|
44
|
+
};
|
|
45
|
+
Object.defineProperty(global, 'caches', {
|
|
46
|
+
value: { open: jest.fn(async () => mockCache) },
|
|
47
|
+
writable: true,
|
|
48
|
+
configurable: true,
|
|
49
|
+
});
|
|
50
|
+
return mockCache;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const removeMockCaches = (): void => {
|
|
54
|
+
Reflect.deleteProperty(global as Record<string, unknown>, 'caches');
|
|
55
|
+
};
|
|
56
|
+
|
|
8
57
|
describe('useConsoleTabData', () => {
|
|
9
58
|
beforeEach(() => {
|
|
10
59
|
localStorage.clear();
|
|
11
60
|
window.history.replaceState({}, '', '/?k=token');
|
|
61
|
+
removeMockCaches();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
removeMockCaches();
|
|
12
66
|
});
|
|
13
67
|
|
|
14
68
|
it('fetches every tab once at startup and parses snapshots', async () => {
|
|
15
|
-
const fetchMock =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
generatedAt: '2026-06-19T00:00:00.000Z',
|
|
21
|
-
statusOptions: [{ id: 's1', name: 'Unread', color: 'ORANGE' }],
|
|
22
|
-
storyColors: {},
|
|
23
|
-
items: url.includes('/prs/')
|
|
24
|
-
? [{ number: 1, itemId: 'PVTI_1', projectItemId: 'PVTI_1' }]
|
|
25
|
-
: [],
|
|
26
|
-
}),
|
|
27
|
-
}));
|
|
28
|
-
global.fetch = fetchMock as unknown as typeof fetch;
|
|
69
|
+
const fetchMock = installNetworkFetch((url) =>
|
|
70
|
+
url.includes('/prs/')
|
|
71
|
+
? [{ number: 1, itemId: 'PVTI_1', projectItemId: 'PVTI_1' }]
|
|
72
|
+
: [],
|
|
73
|
+
);
|
|
29
74
|
|
|
30
75
|
const { result } = renderHook(() => useConsoleTabData('acme'));
|
|
31
76
|
await waitFor(() => {
|
|
@@ -41,6 +86,15 @@ describe('useConsoleTabData', () => {
|
|
|
41
86
|
);
|
|
42
87
|
});
|
|
43
88
|
|
|
89
|
+
it('sets fromCache false when data is loaded from the network', async () => {
|
|
90
|
+
installNetworkFetch();
|
|
91
|
+
const { result } = renderHook(() => useConsoleTabData('acme'));
|
|
92
|
+
await waitFor(() => {
|
|
93
|
+
expect(result.current.isLoading).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
expect(result.current.snapshots.prs?.fromCache).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
44
98
|
it('normalizes relatedOpenPullRequestUrls for a snapshot written before the field existed', async () => {
|
|
45
99
|
const fetchMock = jest.fn(async (url: string) => ({
|
|
46
100
|
ok: true,
|
|
@@ -116,7 +170,7 @@ describe('useConsoleTabData', () => {
|
|
|
116
170
|
jest.useRealTimers();
|
|
117
171
|
});
|
|
118
172
|
|
|
119
|
-
it('surfaces an error when a tab fetch fails', async () => {
|
|
173
|
+
it('surfaces an error when a tab fetch fails and no cache is available', async () => {
|
|
120
174
|
const fetchMock = jest.fn(async () => ({
|
|
121
175
|
ok: false,
|
|
122
176
|
status: 500,
|
|
@@ -129,6 +183,48 @@ describe('useConsoleTabData', () => {
|
|
|
129
183
|
});
|
|
130
184
|
});
|
|
131
185
|
|
|
186
|
+
it('serves board data from cache and sets fromCache true when every network fetch rejects', async () => {
|
|
187
|
+
const cachedPayload = makeTabPayload({
|
|
188
|
+
generatedAt: '2026-06-10T00:00:00.000Z',
|
|
189
|
+
items: [{ number: 1, itemId: 'PVTI_1', projectItemId: 'PVTI_1' }],
|
|
190
|
+
});
|
|
191
|
+
const cacheEntry = { json: jest.fn(async () => cachedPayload) };
|
|
192
|
+
installMockCaches(cacheEntry);
|
|
193
|
+
|
|
194
|
+
global.fetch = jest
|
|
195
|
+
.fn()
|
|
196
|
+
.mockRejectedValue(new Error('Network error')) as unknown as typeof fetch;
|
|
197
|
+
|
|
198
|
+
const { result } = renderHook(() => useConsoleTabData('acme'));
|
|
199
|
+
await waitFor(() => {
|
|
200
|
+
expect(result.current.isLoading).toBe(false);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(result.current.error).toBeNull();
|
|
204
|
+
expect(result.current.snapshots.prs?.fromCache).toBe(true);
|
|
205
|
+
expect(result.current.snapshots.prs?.generatedAt).toBe(
|
|
206
|
+
'2026-06-10T00:00:00.000Z',
|
|
207
|
+
);
|
|
208
|
+
expect(result.current.snapshots.prs?.items.length).toBe(1);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('surfaces an error when network rejects and no cache entry exists', async () => {
|
|
212
|
+
installMockCaches(undefined);
|
|
213
|
+
global.fetch = jest
|
|
214
|
+
.fn()
|
|
215
|
+
.mockRejectedValue(new Error('offline')) as unknown as typeof fetch;
|
|
216
|
+
|
|
217
|
+
const { result } = renderHook(() => useConsoleTabData('acme'));
|
|
218
|
+
await waitFor(() => {
|
|
219
|
+
expect(result.current.isLoading).toBe(false);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(result.current.error).toBe('offline');
|
|
223
|
+
expect(
|
|
224
|
+
Object.values(result.current.snapshots).every((s) => s === null),
|
|
225
|
+
).toBe(true);
|
|
226
|
+
});
|
|
227
|
+
|
|
132
228
|
it('reports an error and fetches nothing when no pjcode is in the URL', async () => {
|
|
133
229
|
const fetchMock = jest.fn();
|
|
134
230
|
global.fetch = fetchMock as unknown as typeof fetch;
|
|
@@ -16,6 +16,7 @@ export type ConsoleTabSnapshot = {
|
|
|
16
16
|
storyColors: ConsoleStoryColorSource;
|
|
17
17
|
stories: ConsoleStoryEntry[];
|
|
18
18
|
defaultNameWithOwner: string | null;
|
|
19
|
+
fromCache: boolean;
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
export type ConsoleTabDataState = {
|
|
@@ -61,7 +62,9 @@ const parseStoryEntries = (value: unknown): ConsoleStoryEntry[] => {
|
|
|
61
62
|
return value.filter(isRecord) as unknown as ConsoleStoryEntry[];
|
|
62
63
|
};
|
|
63
64
|
|
|
64
|
-
const
|
|
65
|
+
const parseSnapshotData = (
|
|
66
|
+
payload: unknown,
|
|
67
|
+
): Omit<ConsoleTabSnapshot, 'fromCache'> => ({
|
|
65
68
|
items: parseItems(payload),
|
|
66
69
|
generatedAt:
|
|
67
70
|
isRecord(payload) && typeof payload.generatedAt === 'string'
|
|
@@ -96,24 +99,106 @@ const buildListUrl = (pjcode: string, tab: ConsoleTabName): string =>
|
|
|
96
99
|
|
|
97
100
|
export const CONSOLE_TAB_REFRESH_INTERVAL_MS = 60000;
|
|
98
101
|
|
|
102
|
+
const CONSOLE_LIST_CACHE_NAME = 'console-list-v1';
|
|
103
|
+
|
|
104
|
+
const persistListSnapshot = (url: string, payload: unknown): void => {
|
|
105
|
+
try {
|
|
106
|
+
if (!('caches' in globalThis)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
globalThis.caches
|
|
110
|
+
.open(CONSOLE_LIST_CACHE_NAME)
|
|
111
|
+
.then((cache) =>
|
|
112
|
+
cache.put(
|
|
113
|
+
url,
|
|
114
|
+
new Response(JSON.stringify(payload), {
|
|
115
|
+
headers: { 'Content-Type': 'application/json' },
|
|
116
|
+
}),
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
.catch((e: unknown) => {
|
|
120
|
+
console.warn('Failed to persist tab snapshot to cache:', e);
|
|
121
|
+
});
|
|
122
|
+
} catch (e: unknown) {
|
|
123
|
+
console.warn('Failed to access cache storage:', e);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const loadListSnapshotFromCache = async (
|
|
128
|
+
url: string,
|
|
129
|
+
): Promise<ConsoleTabSnapshot | null> => {
|
|
130
|
+
if (!('caches' in globalThis)) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const cache = await globalThis.caches.open(CONSOLE_LIST_CACHE_NAME);
|
|
135
|
+
const cached = await cache.match(url);
|
|
136
|
+
if (cached === undefined) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const payload: unknown = await cached.json();
|
|
140
|
+
return { ...parseSnapshotData(payload), fromCache: true };
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const fetchSingleSnapshot = async (
|
|
147
|
+
pjcode: string,
|
|
148
|
+
tabName: ConsoleTabName,
|
|
149
|
+
): Promise<{ snapshot: ConsoleTabSnapshot | null; error: Error | null }> => {
|
|
150
|
+
const url = buildListUrl(pjcode, tabName);
|
|
151
|
+
try {
|
|
152
|
+
const response = await fetch(url);
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
throw new Error(`HTTP ${response.status}`);
|
|
155
|
+
}
|
|
156
|
+
const payload: unknown = await response.json();
|
|
157
|
+
persistListSnapshot(url, payload);
|
|
158
|
+
return {
|
|
159
|
+
snapshot: { ...parseSnapshotData(payload), fromCache: false },
|
|
160
|
+
error: null,
|
|
161
|
+
};
|
|
162
|
+
} catch (e: unknown) {
|
|
163
|
+
const cached = await loadListSnapshotFromCache(url);
|
|
164
|
+
if (cached !== null) {
|
|
165
|
+
return { snapshot: cached, error: null };
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
snapshot: null,
|
|
169
|
+
error: e instanceof Error ? e : new Error(String(e)),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
99
174
|
const fetchSnapshots = async (
|
|
100
175
|
pjcode: string,
|
|
101
|
-
): Promise<
|
|
102
|
-
|
|
176
|
+
): Promise<{
|
|
177
|
+
snapshots: Record<ConsoleTabName, ConsoleTabSnapshot | null>;
|
|
178
|
+
firstError: Error | null;
|
|
179
|
+
}> => {
|
|
180
|
+
const results = await Promise.all(
|
|
103
181
|
CONSOLE_TABS.map(async (tab) => {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
throw new Error(`HTTP ${response.status}`);
|
|
107
|
-
}
|
|
108
|
-
const payload: unknown = await response.json();
|
|
109
|
-
return [tab.name, parseSnapshot(payload)] as const;
|
|
182
|
+
const { snapshot, error } = await fetchSingleSnapshot(pjcode, tab.name);
|
|
183
|
+
return { tabName: tab.name, snapshot, error };
|
|
110
184
|
}),
|
|
111
185
|
);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
186
|
+
|
|
187
|
+
const snapshots = emptySnapshots();
|
|
188
|
+
let firstError: Error | null = null;
|
|
189
|
+
|
|
190
|
+
for (const { tabName, snapshot, error } of results) {
|
|
191
|
+
snapshots[tabName] = snapshot;
|
|
192
|
+
if (error !== null && firstError === null) {
|
|
193
|
+
firstError = error;
|
|
194
|
+
}
|
|
115
195
|
}
|
|
116
|
-
|
|
196
|
+
|
|
197
|
+
const allNull = Object.values(snapshots).every((s) => s === null);
|
|
198
|
+
return {
|
|
199
|
+
snapshots,
|
|
200
|
+
firstError: allNull ? firstError : null,
|
|
201
|
+
};
|
|
117
202
|
};
|
|
118
203
|
|
|
119
204
|
export const useConsoleTabData = (
|
|
@@ -140,12 +225,12 @@ export const useConsoleTabData = (
|
|
|
140
225
|
|
|
141
226
|
const load = (): void => {
|
|
142
227
|
fetchSnapshots(pjcode)
|
|
143
|
-
.then((next) => {
|
|
228
|
+
.then(({ snapshots: next, firstError }) => {
|
|
144
229
|
if (cancelled) {
|
|
145
230
|
return;
|
|
146
231
|
}
|
|
147
232
|
setSnapshots(next);
|
|
148
|
-
setError(null);
|
|
233
|
+
setError(firstError !== null ? firstError.message : null);
|
|
149
234
|
setIsLoading(false);
|
|
150
235
|
})
|
|
151
236
|
.catch((cause: unknown) => {
|