github-issue-tower-defence-management 1.197.0 → 1.197.1
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 +7 -0
- package/bin/adapter/entry-points/console/consoleDataDelivery.js +1 -20
- package/bin/adapter/entry-points/console/consoleDataDelivery.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/{index-CYwLv9pf.js → index-lT5OKx7e.js} +2 -2
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/console/consoleDataDelivery.test.ts +12 -6
- package/src/adapter/entry-points/console/consoleDataDelivery.ts +1 -31
- package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.test.ts +30 -18
- package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.ts +12 -3
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +3 -1
- package/src/adapter/entry-points/console/ui-dist/assets/{index-CYwLv9pf.js → index-lT5OKx7e.js} +2 -2
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/adapter/entry-points/console/webServer.test.ts +10 -4
- package/src/adapter/entry-points/handlers/consoleListsWriter.test.ts +2 -2
- package/types/adapter/entry-points/console/consoleDataDelivery.d.ts.map +1 -1
|
@@ -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-lT5OKx7e.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-CLHvrafO.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
|
@@ -111,12 +111,12 @@ describe('buildConsoleDataResponse', () => {
|
|
|
111
111
|
expect(response.statusCode).toBe(404);
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
-
it('serves a list file
|
|
114
|
+
it('serves a list file without filtering items that have done records', () => {
|
|
115
115
|
writeJson('acme/prs/list.json', {
|
|
116
116
|
pjcode: 'acme',
|
|
117
117
|
items: [
|
|
118
118
|
{ projectItemId: 'PVTI_1', title: 'keep' },
|
|
119
|
-
{ projectItemId: 'PVTI_2', title: '
|
|
119
|
+
{ projectItemId: 'PVTI_2', title: 'also keep despite done record' },
|
|
120
120
|
],
|
|
121
121
|
});
|
|
122
122
|
recordDoneProjectItemId(baseDir, 'acme', 'prs', 'PVTI_2');
|
|
@@ -129,16 +129,19 @@ describe('buildConsoleDataResponse', () => {
|
|
|
129
129
|
const parsed: unknown = JSON.parse(response.body);
|
|
130
130
|
expect(parsed).toEqual({
|
|
131
131
|
pjcode: 'acme',
|
|
132
|
-
items: [
|
|
132
|
+
items: [
|
|
133
|
+
{ projectItemId: 'PVTI_1', title: 'keep' },
|
|
134
|
+
{ projectItemId: 'PVTI_2', title: 'also keep despite done record' },
|
|
135
|
+
],
|
|
133
136
|
});
|
|
134
137
|
});
|
|
135
138
|
|
|
136
|
-
it('
|
|
139
|
+
it('serves the workflow-blocker list without filtering items that have done records', () => {
|
|
137
140
|
writeJson('acme/workflow-blocker/list.json', {
|
|
138
141
|
pjcode: 'acme',
|
|
139
142
|
items: [
|
|
140
143
|
{ projectItemId: 'PVTI_1', title: 'keep' },
|
|
141
|
-
{ projectItemId: 'PVTI_2', title: '
|
|
144
|
+
{ projectItemId: 'PVTI_2', title: 'also keep despite done record' },
|
|
142
145
|
],
|
|
143
146
|
});
|
|
144
147
|
recordDoneProjectItemId(baseDir, 'acme', 'workflow-blocker', 'PVTI_2');
|
|
@@ -151,7 +154,10 @@ describe('buildConsoleDataResponse', () => {
|
|
|
151
154
|
const parsed: unknown = JSON.parse(response.body);
|
|
152
155
|
expect(parsed).toEqual({
|
|
153
156
|
pjcode: 'acme',
|
|
154
|
-
items: [
|
|
157
|
+
items: [
|
|
158
|
+
{ projectItemId: 'PVTI_1', title: 'keep' },
|
|
159
|
+
{ projectItemId: 'PVTI_2', title: 'also keep despite done record' },
|
|
160
|
+
],
|
|
155
161
|
});
|
|
156
162
|
});
|
|
157
163
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import { readDoneProjectItemIds } from './consoleDoneStore';
|
|
4
3
|
import { CONSOLE_LIST_TAB_NAMES } from './consoleTabNames';
|
|
5
4
|
|
|
6
5
|
export { CONSOLE_LIST_TAB_NAMES };
|
|
@@ -55,9 +54,6 @@ export const parseConsoleDataRoute = (
|
|
|
55
54
|
return null;
|
|
56
55
|
};
|
|
57
56
|
|
|
58
|
-
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
59
|
-
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
60
|
-
|
|
61
57
|
type ReadJsonResult = { found: false } | { found: true; data: unknown };
|
|
62
58
|
|
|
63
59
|
const readJsonFile = (filePath: string): ReadJsonResult => {
|
|
@@ -75,26 +71,6 @@ const readJsonFile = (filePath: string): ReadJsonResult => {
|
|
|
75
71
|
return { found: true, data };
|
|
76
72
|
};
|
|
77
73
|
|
|
78
|
-
const isExcludedItem = (item: unknown, doneSet: Set<string>): boolean => {
|
|
79
|
-
if (!isRecord(item)) {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
const projectItemId = item.projectItemId;
|
|
83
|
-
return typeof projectItemId === 'string' && doneSet.has(projectItemId);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const applyDoneExclusion = (
|
|
87
|
-
listData: unknown,
|
|
88
|
-
doneProjectItemIds: string[],
|
|
89
|
-
): unknown => {
|
|
90
|
-
if (!isRecord(listData) || !Array.isArray(listData.items)) {
|
|
91
|
-
return listData;
|
|
92
|
-
}
|
|
93
|
-
const doneSet = new Set(doneProjectItemIds);
|
|
94
|
-
const items = listData.items.filter((item) => !isExcludedItem(item, doneSet));
|
|
95
|
-
return { ...listData, items };
|
|
96
|
-
};
|
|
97
|
-
|
|
98
74
|
export type ConsoleDataResponse = {
|
|
99
75
|
statusCode: number;
|
|
100
76
|
contentType: string;
|
|
@@ -116,13 +92,7 @@ export const buildConsoleDataResponse = (
|
|
|
116
92
|
if (!listResult.found) {
|
|
117
93
|
return notFoundJson();
|
|
118
94
|
}
|
|
119
|
-
|
|
120
|
-
consoleDataOutputDir,
|
|
121
|
-
route.pjcode,
|
|
122
|
-
route.tab,
|
|
123
|
-
);
|
|
124
|
-
const filtered = applyDoneExclusion(listResult.data, doneProjectItemIds);
|
|
125
|
-
return okJson(filtered);
|
|
95
|
+
return okJson(listResult.data);
|
|
126
96
|
}
|
|
127
97
|
if (route.kind === 'detail') {
|
|
128
98
|
const filePath = path.join(
|
|
@@ -81,33 +81,33 @@ describe('counts driven to zero do not revive on tab switch', () => {
|
|
|
81
81
|
const overlay: ConsoleOverlay = {
|
|
82
82
|
PVTI_1: { ts: 500, mode: 'prs', done: true },
|
|
83
83
|
};
|
|
84
|
-
expect(countPendingItems([item(1)], overlay)).toBe(0);
|
|
84
|
+
expect(countPendingItems([item(1)], overlay, 'prs')).toBe(0);
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
-
it('
|
|
87
|
+
it('does not subtract a done item from a tab other than the one it was processed in', () => {
|
|
88
88
|
const overlay: ConsoleOverlay = {
|
|
89
89
|
PVTI_1: { ts: 100, mode: 'triage', done: true },
|
|
90
90
|
};
|
|
91
|
-
expect(countPendingItems([item(1)], overlay)).toBe(
|
|
91
|
+
expect(countPendingItems([item(1)], overlay, 'prs')).toBe(1);
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
it('does not revive a done item even when an entry was processed before the snapshot it still appears in', () => {
|
|
95
95
|
const overlay: ConsoleOverlay = {
|
|
96
96
|
PVTI_1: { ts: 100, mode: 'prs', done: true },
|
|
97
97
|
};
|
|
98
|
-
expect(countPendingItems([item(1)], overlay)).toBe(0);
|
|
98
|
+
expect(countPendingItems([item(1)], overlay, 'prs')).toBe(0);
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
it('
|
|
101
|
+
it('shows a done item in a tab other than the one it was processed in', () => {
|
|
102
102
|
const overlay: ConsoleOverlay = {
|
|
103
103
|
PVTI_1: { ts: 100, mode: 'prs', done: true },
|
|
104
104
|
};
|
|
105
|
-
expect(countPendingItems([item(1)], overlay)).toBe(
|
|
106
|
-
expect(filterPendingItems([item(1)], overlay)).toEqual([]);
|
|
105
|
+
expect(countPendingItems([item(1)], overlay, 'triage')).toBe(1);
|
|
106
|
+
expect(filterPendingItems([item(1)], overlay, 'triage')).toEqual([item(1)]);
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
it('counts an item that has no done entry', () => {
|
|
110
|
-
expect(countPendingItems([item(1)], {})).toBe(1);
|
|
110
|
+
expect(countPendingItems([item(1)], {}, 'prs')).toBe(1);
|
|
111
111
|
});
|
|
112
112
|
});
|
|
113
113
|
|
|
@@ -116,7 +116,7 @@ describe('filterPendingItems', () => {
|
|
|
116
116
|
const overlay: ConsoleOverlay = {
|
|
117
117
|
PVTI_1: { ts: 500, mode: 'prs', done: true },
|
|
118
118
|
};
|
|
119
|
-
const result = filterPendingItems([item(1), item(2)], overlay);
|
|
119
|
+
const result = filterPendingItems([item(1), item(2)], overlay, 'prs');
|
|
120
120
|
expect(result.map((entry) => entry.number)).toEqual([2]);
|
|
121
121
|
});
|
|
122
122
|
|
|
@@ -125,8 +125,8 @@ describe('filterPendingItems', () => {
|
|
|
125
125
|
PVTI_1: { ts: 500, mode: 'prs', done: true },
|
|
126
126
|
};
|
|
127
127
|
const items = [item(1), item(2)];
|
|
128
|
-
expect(countPendingItems(items, overlay)).toBe(
|
|
129
|
-
filterPendingItems(items, overlay).length,
|
|
128
|
+
expect(countPendingItems(items, overlay, 'prs')).toBe(
|
|
129
|
+
filterPendingItems(items, overlay, 'prs').length,
|
|
130
130
|
);
|
|
131
131
|
});
|
|
132
132
|
});
|
|
@@ -136,14 +136,20 @@ describe('workflow-blocker items are filtered by the done overlay like every oth
|
|
|
136
136
|
const overlay: ConsoleOverlay = {
|
|
137
137
|
PVTI_1: { ts: 100, mode: 'workflow-blocker', done: true },
|
|
138
138
|
};
|
|
139
|
-
expect(
|
|
139
|
+
expect(
|
|
140
|
+
countPendingItems([item(1), item(2)], overlay, 'workflow-blocker'),
|
|
141
|
+
).toBe(1);
|
|
140
142
|
});
|
|
141
143
|
|
|
142
144
|
it('removes a processed workflow-blocker item from the list', () => {
|
|
143
145
|
const overlay: ConsoleOverlay = {
|
|
144
146
|
PVTI_1: { ts: 100, mode: 'workflow-blocker', done: true },
|
|
145
147
|
};
|
|
146
|
-
const result = filterPendingItems(
|
|
148
|
+
const result = filterPendingItems(
|
|
149
|
+
[item(1), item(2)],
|
|
150
|
+
overlay,
|
|
151
|
+
'workflow-blocker',
|
|
152
|
+
);
|
|
147
153
|
expect(result.map((entry) => entry.number)).toEqual([2]);
|
|
148
154
|
});
|
|
149
155
|
|
|
@@ -152,8 +158,12 @@ describe('workflow-blocker items are filtered by the done overlay like every oth
|
|
|
152
158
|
PVTI_1: { ts: 100, mode: 'workflow-blocker', done: true },
|
|
153
159
|
PVTI_2: { ts: 100, mode: 'workflow-blocker', done: true },
|
|
154
160
|
};
|
|
155
|
-
expect(
|
|
156
|
-
|
|
161
|
+
expect(
|
|
162
|
+
countPendingItems([item(1), item(2)], overlay, 'workflow-blocker'),
|
|
163
|
+
).toBe(0);
|
|
164
|
+
expect(
|
|
165
|
+
filterPendingItems([item(1), item(2)], overlay, 'workflow-blocker'),
|
|
166
|
+
).toEqual([]);
|
|
157
167
|
});
|
|
158
168
|
});
|
|
159
169
|
|
|
@@ -171,8 +181,10 @@ describe('overlayEntriesActedSinceSnapshot', () => {
|
|
|
171
181
|
'2026-08-04T00:26:06Z',
|
|
172
182
|
);
|
|
173
183
|
expect(acted).toEqual({});
|
|
174
|
-
expect(countPendingItems([item(1)], acted)).toBe(1);
|
|
175
|
-
expect(filterPendingItems([item(1)], acted)).toEqual([
|
|
184
|
+
expect(countPendingItems([item(1)], acted, 'todo-by-human')).toBe(1);
|
|
185
|
+
expect(filterPendingItems([item(1)], acted, 'todo-by-human')).toEqual([
|
|
186
|
+
item(1),
|
|
187
|
+
]);
|
|
176
188
|
});
|
|
177
189
|
|
|
178
190
|
it('keeps an entry written after the snapshot was generated so the item stays hidden', () => {
|
|
@@ -187,7 +199,7 @@ describe('overlayEntriesActedSinceSnapshot', () => {
|
|
|
187
199
|
overlay,
|
|
188
200
|
'2026-08-04T00:26:06Z',
|
|
189
201
|
);
|
|
190
|
-
expect(countPendingItems([item(1)], acted)).toBe(0);
|
|
202
|
+
expect(countPendingItems([item(1)], acted, 'todo-by-human')).toBe(0);
|
|
191
203
|
});
|
|
192
204
|
|
|
193
205
|
it('keeps an entry written at the exact snapshot generation time', () => {
|
|
@@ -16,19 +16,28 @@ export const isOverlayEntryActed = (
|
|
|
16
16
|
entry: ConsoleOverlayEntry | undefined,
|
|
17
17
|
): boolean => entry !== undefined && entry.done === true;
|
|
18
18
|
|
|
19
|
+
const isActedInTab = (
|
|
20
|
+
entry: ConsoleOverlayEntry | undefined,
|
|
21
|
+
currentTab: ConsoleTabName,
|
|
22
|
+
): boolean =>
|
|
23
|
+
entry !== undefined && entry.done === true && entry.mode === currentTab;
|
|
24
|
+
|
|
19
25
|
export const countPendingItems = (
|
|
20
26
|
items: ConsoleListItem[],
|
|
21
27
|
overlay: ConsoleOverlay,
|
|
28
|
+
currentTab: ConsoleTabName,
|
|
22
29
|
): number =>
|
|
23
|
-
items.filter(
|
|
24
|
-
|
|
30
|
+
items.filter(
|
|
31
|
+
(item) => !isActedInTab(overlay[overlayKeyForItem(item)], currentTab),
|
|
32
|
+
).length;
|
|
25
33
|
|
|
26
34
|
export const filterPendingItems = (
|
|
27
35
|
items: ConsoleListItem[],
|
|
28
36
|
overlay: ConsoleOverlay,
|
|
37
|
+
currentTab: ConsoleTabName,
|
|
29
38
|
): ConsoleListItem[] =>
|
|
30
39
|
items.filter(
|
|
31
|
-
(item) => !
|
|
40
|
+
(item) => !isActedInTab(overlay[overlayKeyForItem(item)], currentTab),
|
|
32
41
|
);
|
|
33
42
|
|
|
34
43
|
export const overlayEntriesActedSinceSnapshot = (
|
|
@@ -107,6 +107,7 @@ export const ConsolePage = () => {
|
|
|
107
107
|
overlayState.overlay,
|
|
108
108
|
snapshot.generatedAt,
|
|
109
109
|
),
|
|
110
|
+
tab.name,
|
|
110
111
|
);
|
|
111
112
|
}
|
|
112
113
|
}
|
|
@@ -151,8 +152,9 @@ export const ConsolePage = () => {
|
|
|
151
152
|
overlayState.overlay,
|
|
152
153
|
activeSnapshot.generatedAt,
|
|
153
154
|
),
|
|
155
|
+
activeTab,
|
|
154
156
|
);
|
|
155
|
-
}, [activeSnapshot, overlayState.overlay]);
|
|
157
|
+
}, [activeSnapshot, overlayState.overlay, activeTab]);
|
|
156
158
|
|
|
157
159
|
const orderedPendingKeys = useMemo(
|
|
158
160
|
() => pendingItems.map((item) => overlayKeyForItem(item)),
|