github-issue-tower-defence-management 2.1.0 → 2.1.2
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 +14 -0
- package/bin/adapter/entry-points/console/consoleOperationApi.js +6 -4
- package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/{index-lT5OKx7e.js → index-BVyeR7xJ.js} +2 -2
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/adapter/entry-points/console/webServer.js +0 -4
- package/bin/adapter/entry-points/console/webServer.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +1 -0
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +9 -0
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/console/consoleOperationApi.test.ts +60 -32
- package/src/adapter/entry-points/console/consoleOperationApi.ts +6 -7
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +12 -17
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.stories.tsx +1 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.test.tsx +85 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.tsx +73 -1
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +53 -16
- package/src/adapter/entry-points/console/ui-dist/assets/{index-lT5OKx7e.js → index-BVyeR7xJ.js} +2 -2
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/adapter/entry-points/console/webServer.ts +0 -8
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +1 -0
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +45 -0
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +19 -0
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts +1 -2
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
- package/types/adapter/entry-points/console/webServer.d.ts +0 -2
- package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts +1 -0
- package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
|
@@ -38,6 +38,7 @@ const defaultProps = {
|
|
|
38
38
|
onAddStory: () => Promise.resolve(),
|
|
39
39
|
onSelectColor: () => undefined,
|
|
40
40
|
onToggleGray: () => undefined,
|
|
41
|
+
onReorderStory: () => Promise.resolve(),
|
|
41
42
|
optimisticColors: {} as Record<string, ConsoleColor>,
|
|
42
43
|
colorChangeInFlight: null as string | null,
|
|
43
44
|
colorErrors: {} as Record<string, string>,
|
|
@@ -466,4 +467,88 @@ describe('ConsoleStoryList', () => {
|
|
|
466
467
|
expect(getByText('No active stories')).toBeInTheDocument();
|
|
467
468
|
});
|
|
468
469
|
});
|
|
470
|
+
|
|
471
|
+
describe('reorder buttons', () => {
|
|
472
|
+
it('renders Move up and Move down buttons for each visible story', () => {
|
|
473
|
+
const { getAllByRole } = render(<ConsoleStoryList {...defaultProps} />);
|
|
474
|
+
const upButtons = getAllByRole('button', { name: 'Move up' });
|
|
475
|
+
const downButtons = getAllByRole('button', { name: 'Move down' });
|
|
476
|
+
expect(upButtons).toHaveLength(storyEntries.length);
|
|
477
|
+
expect(downButtons).toHaveLength(storyEntries.length);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('disables the Move up button for the first story', () => {
|
|
481
|
+
const { getAllByRole } = render(<ConsoleStoryList {...defaultProps} />);
|
|
482
|
+
const upButtons = getAllByRole('button', { name: 'Move up' });
|
|
483
|
+
expect(upButtons[0]).toBeDisabled();
|
|
484
|
+
expect(upButtons[1]).not.toBeDisabled();
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('disables the Move down button for the last story', () => {
|
|
488
|
+
const { getAllByRole } = render(<ConsoleStoryList {...defaultProps} />);
|
|
489
|
+
const downButtons = getAllByRole('button', { name: 'Move down' });
|
|
490
|
+
expect(downButtons[0]).not.toBeDisabled();
|
|
491
|
+
expect(downButtons[1]).toBeDisabled();
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('calls onReorderStory with storyOptionId and up when Move up is clicked', async () => {
|
|
495
|
+
const onReorderStory = jest.fn().mockResolvedValue(undefined);
|
|
496
|
+
const { getAllByRole } = render(
|
|
497
|
+
<ConsoleStoryList {...defaultProps} onReorderStory={onReorderStory} />,
|
|
498
|
+
);
|
|
499
|
+
const upButtons = getAllByRole('button', { name: 'Move up' });
|
|
500
|
+
await act(async () => {
|
|
501
|
+
fireEvent.click(upButtons[1]);
|
|
502
|
+
});
|
|
503
|
+
expect(onReorderStory).toHaveBeenCalledWith('564803ee', 'up');
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('calls onReorderStory with storyOptionId and down when Move down is clicked', async () => {
|
|
507
|
+
const onReorderStory = jest.fn().mockResolvedValue(undefined);
|
|
508
|
+
const { getAllByRole } = render(
|
|
509
|
+
<ConsoleStoryList {...defaultProps} onReorderStory={onReorderStory} />,
|
|
510
|
+
);
|
|
511
|
+
const downButtons = getAllByRole('button', { name: 'Move down' });
|
|
512
|
+
await act(async () => {
|
|
513
|
+
fireEvent.click(downButtons[0]);
|
|
514
|
+
});
|
|
515
|
+
expect(onReorderStory).toHaveBeenCalledWith('1491051e', 'down');
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it('disables Move up and Move down buttons while reorder is in progress', async () => {
|
|
519
|
+
let resolveReorder: () => void;
|
|
520
|
+
const reorderPromise = new Promise<void>((resolve) => {
|
|
521
|
+
resolveReorder = resolve;
|
|
522
|
+
});
|
|
523
|
+
const onReorderStory = jest.fn().mockReturnValue(reorderPromise);
|
|
524
|
+
const { getAllByRole } = render(
|
|
525
|
+
<ConsoleStoryList {...defaultProps} onReorderStory={onReorderStory} />,
|
|
526
|
+
);
|
|
527
|
+
const downButtons = getAllByRole('button', { name: 'Move down' });
|
|
528
|
+
fireEvent.click(downButtons[0]);
|
|
529
|
+
await waitFor(() => {
|
|
530
|
+
const upButtons = getAllByRole('button', { name: 'Move up' });
|
|
531
|
+
expect(upButtons[0]).toBeDisabled();
|
|
532
|
+
expect(downButtons[0]).toBeDisabled();
|
|
533
|
+
});
|
|
534
|
+
await act(async () => {
|
|
535
|
+
resolveReorder!();
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('shows an error message when onReorderStory throws', async () => {
|
|
540
|
+
const onReorderStory = jest
|
|
541
|
+
.fn()
|
|
542
|
+
.mockRejectedValue(new Error('Reorder failed'));
|
|
543
|
+
const { getAllByRole, findByRole } = render(
|
|
544
|
+
<ConsoleStoryList {...defaultProps} onReorderStory={onReorderStory} />,
|
|
545
|
+
);
|
|
546
|
+
const downButtons = getAllByRole('button', { name: 'Move down' });
|
|
547
|
+
await act(async () => {
|
|
548
|
+
fireEvent.click(downButtons[0]);
|
|
549
|
+
});
|
|
550
|
+
const alert = await findByRole('alert');
|
|
551
|
+
expect(alert).toHaveTextContent('Reorder failed');
|
|
552
|
+
});
|
|
553
|
+
});
|
|
469
554
|
});
|
|
@@ -3,6 +3,11 @@ import { CONSOLE_COLOR_PALETTE, colorFromEnum } from '../../logic/colors';
|
|
|
3
3
|
import type { ConsoleColor, ConsoleStoryEntry } from '../../logic/types';
|
|
4
4
|
import { ConsoleCopyStoryNameButton } from './ConsoleCopyStoryNameButton';
|
|
5
5
|
|
|
6
|
+
type RowReorderState = {
|
|
7
|
+
inProgress: boolean;
|
|
8
|
+
error: string | null;
|
|
9
|
+
};
|
|
10
|
+
|
|
6
11
|
const ALL_COLORS = Object.keys(CONSOLE_COLOR_PALETTE) as ConsoleColor[];
|
|
7
12
|
|
|
8
13
|
type InlineInputFormProps = {
|
|
@@ -137,6 +142,10 @@ export type ConsoleStoryListProps = {
|
|
|
137
142
|
onAddStory: (storyName: string) => Promise<void>;
|
|
138
143
|
onSelectColor: (storyOptionId: string, newColor: ConsoleColor) => void;
|
|
139
144
|
onToggleGray: () => void;
|
|
145
|
+
onReorderStory: (
|
|
146
|
+
storyOptionId: string,
|
|
147
|
+
direction: 'up' | 'down',
|
|
148
|
+
) => Promise<void>;
|
|
140
149
|
optimisticColors: Record<string, ConsoleColor>;
|
|
141
150
|
colorChangeInFlight: string | null;
|
|
142
151
|
colorErrors: Record<string, string>;
|
|
@@ -151,6 +160,7 @@ export const ConsoleStoryList = ({
|
|
|
151
160
|
onAddStory,
|
|
152
161
|
onSelectColor,
|
|
153
162
|
onToggleGray,
|
|
163
|
+
onReorderStory,
|
|
154
164
|
optimisticColors,
|
|
155
165
|
colorChangeInFlight,
|
|
156
166
|
colorErrors,
|
|
@@ -160,6 +170,37 @@ export const ConsoleStoryList = ({
|
|
|
160
170
|
const [colorPickerOptionId, setColorPickerOptionId] = useState<string | null>(
|
|
161
171
|
null,
|
|
162
172
|
);
|
|
173
|
+
const [rowReorderStates, setRowReorderStates] = useState<
|
|
174
|
+
Record<string, RowReorderState>
|
|
175
|
+
>({});
|
|
176
|
+
|
|
177
|
+
const getRowReorderState = (id: string): RowReorderState =>
|
|
178
|
+
rowReorderStates[id] ?? { inProgress: false, error: null };
|
|
179
|
+
|
|
180
|
+
const handleReorder = async (
|
|
181
|
+
storyOptionId: string,
|
|
182
|
+
direction: 'up' | 'down',
|
|
183
|
+
): Promise<void> => {
|
|
184
|
+
setRowReorderStates((prev) => ({
|
|
185
|
+
...prev,
|
|
186
|
+
[storyOptionId]: { inProgress: true, error: null },
|
|
187
|
+
}));
|
|
188
|
+
try {
|
|
189
|
+
await onReorderStory(storyOptionId, direction);
|
|
190
|
+
setRowReorderStates((prev) => ({
|
|
191
|
+
...prev,
|
|
192
|
+
[storyOptionId]: { inProgress: false, error: null },
|
|
193
|
+
}));
|
|
194
|
+
} catch (err) {
|
|
195
|
+
setRowReorderStates((prev) => ({
|
|
196
|
+
...prev,
|
|
197
|
+
[storyOptionId]: {
|
|
198
|
+
inProgress: false,
|
|
199
|
+
error: err instanceof Error ? err.message : String(err),
|
|
200
|
+
},
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
};
|
|
163
204
|
|
|
164
205
|
if (error !== null) {
|
|
165
206
|
return (
|
|
@@ -212,7 +253,7 @@ export const ConsoleStoryList = ({
|
|
|
212
253
|
<p className="console-list-empty">No active stories</p>
|
|
213
254
|
) : (
|
|
214
255
|
<ul className="console-story-list">
|
|
215
|
-
{visibleStories.map((entry) => {
|
|
256
|
+
{visibleStories.map((entry, index) => {
|
|
216
257
|
const displayColor: ConsoleColor =
|
|
217
258
|
optimisticColors[entry.storyOptionId] ?? entry.color;
|
|
218
259
|
const palette = colorFromEnum(displayColor);
|
|
@@ -220,6 +261,10 @@ export const ConsoleStoryList = ({
|
|
|
220
261
|
const isPickerOpen = colorPickerOptionId === entry.storyOptionId;
|
|
221
262
|
const isInFlight = colorChangeInFlight === entry.storyOptionId;
|
|
222
263
|
const colorError = colorErrors[entry.storyOptionId] ?? null;
|
|
264
|
+
const { inProgress: reorderInProgress, error: reorderError } =
|
|
265
|
+
getRowReorderState(entry.storyOptionId);
|
|
266
|
+
const isFirst = index === 0;
|
|
267
|
+
const isLast = index === visibleStories.length - 1;
|
|
223
268
|
return (
|
|
224
269
|
<li key={entry.storyOptionId} className="console-story-list-row">
|
|
225
270
|
<div className="console-story-list-row-main">
|
|
@@ -268,7 +313,34 @@ export const ConsoleStoryList = ({
|
|
|
268
313
|
>
|
|
269
314
|
Change color
|
|
270
315
|
</button>
|
|
316
|
+
<button
|
|
317
|
+
type="button"
|
|
318
|
+
className="console-op-button"
|
|
319
|
+
aria-label="Move up"
|
|
320
|
+
disabled={isFirst || reorderInProgress}
|
|
321
|
+
onClick={() =>
|
|
322
|
+
void handleReorder(entry.storyOptionId, 'up')
|
|
323
|
+
}
|
|
324
|
+
>
|
|
325
|
+
↑
|
|
326
|
+
</button>
|
|
327
|
+
<button
|
|
328
|
+
type="button"
|
|
329
|
+
className="console-op-button"
|
|
330
|
+
aria-label="Move down"
|
|
331
|
+
disabled={isLast || reorderInProgress}
|
|
332
|
+
onClick={() =>
|
|
333
|
+
void handleReorder(entry.storyOptionId, 'down')
|
|
334
|
+
}
|
|
335
|
+
>
|
|
336
|
+
↓
|
|
337
|
+
</button>
|
|
271
338
|
</div>
|
|
339
|
+
{reorderError !== null && (
|
|
340
|
+
<p role="alert" className="console-list-error">
|
|
341
|
+
{reorderError}
|
|
342
|
+
</p>
|
|
343
|
+
)}
|
|
272
344
|
{isPickerOpen && (
|
|
273
345
|
<ColorPalette
|
|
274
346
|
onSelectColor={(newColor) =>
|
|
@@ -52,6 +52,7 @@ import type {
|
|
|
52
52
|
ConsoleColor,
|
|
53
53
|
ConsoleListItem,
|
|
54
54
|
ConsoleOverlayStatus,
|
|
55
|
+
ConsoleStoryEntry,
|
|
55
56
|
ConsoleTabName,
|
|
56
57
|
} from '../logic/types';
|
|
57
58
|
import { CONSOLE_TABS } from '../logic/types';
|
|
@@ -185,6 +186,11 @@ export const ConsolePage = () => {
|
|
|
185
186
|
stories: typeof storyOptions;
|
|
186
187
|
} | null>(null);
|
|
187
188
|
|
|
189
|
+
const [localStoryEntriesOverride, setLocalStoryEntriesOverride] = useState<{
|
|
190
|
+
generatedAt: string | undefined;
|
|
191
|
+
stories: ConsoleStoryEntry[];
|
|
192
|
+
} | null>(null);
|
|
193
|
+
|
|
188
194
|
const triageStoryOptions =
|
|
189
195
|
localStoryOptionsOverride !== null &&
|
|
190
196
|
localStoryOptionsOverride.generatedAt === activeSnapshot?.generatedAt
|
|
@@ -347,7 +353,12 @@ export const ConsolePage = () => {
|
|
|
347
353
|
}, []);
|
|
348
354
|
|
|
349
355
|
const storiesSnapshot = snapshots.stories;
|
|
350
|
-
const
|
|
356
|
+
const rawStoryEntries = storiesSnapshot?.stories ?? [];
|
|
357
|
+
const storyEntries =
|
|
358
|
+
localStoryEntriesOverride !== null &&
|
|
359
|
+
localStoryEntriesOverride.generatedAt === storiesSnapshot?.generatedAt
|
|
360
|
+
? localStoryEntriesOverride.stories
|
|
361
|
+
: rawStoryEntries;
|
|
351
362
|
const defaultNameWithOwner = storiesSnapshot?.defaultNameWithOwner ?? null;
|
|
352
363
|
|
|
353
364
|
const handleCreateIssue = useCallback(
|
|
@@ -374,24 +385,49 @@ export const ConsolePage = () => {
|
|
|
374
385
|
throw new Error('No project specified in the URL path.');
|
|
375
386
|
}
|
|
376
387
|
await postConsoleReorderStory({ pjcode, storyOptionId, direction });
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
388
|
+
const triageIndex = triageStoryOptions.findIndex(
|
|
389
|
+
(o) => o.id === storyOptionId,
|
|
390
|
+
);
|
|
391
|
+
if (triageIndex !== -1) {
|
|
392
|
+
const triageSwapIndex = triageIndex + (direction === 'up' ? -1 : 1);
|
|
393
|
+
if (
|
|
394
|
+
triageSwapIndex >= 0 &&
|
|
395
|
+
triageSwapIndex < triageStoryOptions.length
|
|
396
|
+
) {
|
|
397
|
+
const nextTriage = [...triageStoryOptions];
|
|
398
|
+
const triageTemp = nextTriage[triageIndex];
|
|
399
|
+
nextTriage[triageIndex] = nextTriage[triageSwapIndex];
|
|
400
|
+
nextTriage[triageSwapIndex] = triageTemp;
|
|
401
|
+
setLocalStoryOptionsOverride({
|
|
402
|
+
generatedAt: activeSnapshot?.generatedAt,
|
|
403
|
+
stories: nextTriage,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
380
406
|
}
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
407
|
+
const entryIndex = storyEntries.findIndex(
|
|
408
|
+
(e) => e.storyOptionId === storyOptionId,
|
|
409
|
+
);
|
|
410
|
+
if (entryIndex !== -1) {
|
|
411
|
+
const entrySwapIndex = entryIndex + (direction === 'up' ? -1 : 1);
|
|
412
|
+
if (entrySwapIndex >= 0 && entrySwapIndex < storyEntries.length) {
|
|
413
|
+
const nextEntries = [...storyEntries];
|
|
414
|
+
const entryTemp = nextEntries[entryIndex];
|
|
415
|
+
nextEntries[entryIndex] = nextEntries[entrySwapIndex];
|
|
416
|
+
nextEntries[entrySwapIndex] = entryTemp;
|
|
417
|
+
setLocalStoryEntriesOverride({
|
|
418
|
+
generatedAt: storiesSnapshot?.generatedAt,
|
|
419
|
+
stories: nextEntries,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
384
422
|
}
|
|
385
|
-
const next = [...triageStoryOptions];
|
|
386
|
-
const temp = next[index];
|
|
387
|
-
next[index] = next[swapIndex];
|
|
388
|
-
next[swapIndex] = temp;
|
|
389
|
-
setLocalStoryOptionsOverride({
|
|
390
|
-
generatedAt: activeSnapshot?.generatedAt,
|
|
391
|
-
stories: next,
|
|
392
|
-
});
|
|
393
423
|
},
|
|
394
|
-
[
|
|
424
|
+
[
|
|
425
|
+
pjcode,
|
|
426
|
+
triageStoryOptions,
|
|
427
|
+
storyEntries,
|
|
428
|
+
activeSnapshot?.generatedAt,
|
|
429
|
+
storiesSnapshot?.generatedAt,
|
|
430
|
+
],
|
|
395
431
|
);
|
|
396
432
|
|
|
397
433
|
const handleStoryAdd = useCallback(
|
|
@@ -523,6 +559,7 @@ export const ConsolePage = () => {
|
|
|
523
559
|
onAddStory={handleStoryAdd}
|
|
524
560
|
onSelectColor={handleSelectColor}
|
|
525
561
|
onToggleGray={handleToggleGray}
|
|
562
|
+
onReorderStory={handleReorderStory}
|
|
526
563
|
optimisticColors={storyOptimisticColors}
|
|
527
564
|
colorChangeInFlight={storyColorChangeInFlight}
|
|
528
565
|
colorErrors={storyColorErrors}
|