feat: bulk edit tickets
Select several tickets in the backlog and change their status, assignee, sprint, priority or labels at once. Each ticket goes through the normal rules and may refuse on its own; the result says which did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import { type } from '@ngrx/signals';
|
||||
import { eventGroup } from '@ngrx/signals/events';
|
||||
import { IssuePage } from '@task-stream/web/tracker/domain';
|
||||
import {
|
||||
BulkChange,
|
||||
BulkResult,
|
||||
IssuePage,
|
||||
} from '@task-stream/web/tracker/domain';
|
||||
|
||||
export const backlogPageEvents = eventGroup({
|
||||
source: 'Backlog Page',
|
||||
@@ -8,6 +12,7 @@ export const backlogPageEvents = eventGroup({
|
||||
opened: type<{ projectId: string }>(),
|
||||
/** Put the ticket at `index` of the backlog as shown now. */
|
||||
moveRequested: type<{ issueId: string; index: number }>(),
|
||||
bulkRequested: type<{ issueIds: string[]; change: BulkChange }>(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -18,5 +23,6 @@ export const backlogApiEvents = eventGroup({
|
||||
loadFailed: type<string>(),
|
||||
moved: type<void>(),
|
||||
moveFailed: type<string>(),
|
||||
bulkDone: type<BulkResult>(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { inject, Injectable, Provider } from '@angular/core';
|
||||
import { injectDispatch } from '@ngrx/signals/events';
|
||||
import { BulkChange } from '@task-stream/web/tracker/domain';
|
||||
import { backlogPageEvents } from '../events/backlog.events';
|
||||
import { BacklogStore } from '../stores/backlog.store';
|
||||
|
||||
@@ -20,6 +21,11 @@ export class BacklogFacade {
|
||||
this.dispatch.opened({ projectId });
|
||||
}
|
||||
|
||||
/** The same change on several tickets (each may refuse on its own). */
|
||||
bulkUpdate(issueIds: string[], change: BulkChange): void {
|
||||
if (issueIds.length) this.dispatch.bulkRequested({ issueIds, change });
|
||||
}
|
||||
|
||||
/** Puts the ticket at `index` of the list as shown now. */
|
||||
move(issueId: string, index: number): void {
|
||||
this.dispatch.moveRequested({ issueId, index });
|
||||
|
||||
@@ -79,7 +79,7 @@ export const BacklogStore = signalStore(
|
||||
switchMap(({ payload }) =>
|
||||
merge(
|
||||
realtime.issueChanges(payload.projectId).pipe(debounceTime(400)),
|
||||
events.on(backlogApiEvents.moveFailed),
|
||||
events.on(backlogApiEvents.moveFailed, backlogApiEvents.bulkDone),
|
||||
).pipe(
|
||||
startWith(null),
|
||||
switchMap(() => fetch(payload.projectId)),
|
||||
@@ -111,6 +111,28 @@ export const BacklogStore = signalStore(
|
||||
);
|
||||
}),
|
||||
),
|
||||
bulk$: events.on(backlogPageEvents.bulkRequested).pipe(
|
||||
concatMap(({ payload }) =>
|
||||
issues.bulkUpdate(payload.issueIds, payload.change).pipe(
|
||||
mapResponse({
|
||||
next: (result) => backlogApiEvents.bulkDone(result),
|
||||
error: (error) =>
|
||||
backlogApiEvents.moveFailed(describeError(error)),
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
bulkFeedback$: events.on(backlogApiEvents.bulkDone).pipe(
|
||||
tap(({ payload }) => {
|
||||
const refused = payload.results.filter((r) => !r.ok);
|
||||
if (!refused.length)
|
||||
notifications.success(`${payload.updated} tickets actualizados`);
|
||||
else
|
||||
notifications.error(
|
||||
`${payload.updated} actualizados; ${refused.length} no: ${refused[0].error}`,
|
||||
);
|
||||
}),
|
||||
),
|
||||
feedback$: events
|
||||
.on(backlogApiEvents.moveFailed)
|
||||
.pipe(tap(({ payload }) => notifications.error(payload))),
|
||||
|
||||
Reference in New Issue
Block a user