feat: an ordered backlog
A Backlog tab lists the tickets still to start in the order the team will take them; drag them, or use the arrows, to reorder. Moving a ticket only rewrites that ticket's rank, read from the event store, and board columns follow the same order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
FindReleasesRepository,
|
||||
FindTimelineRepository,
|
||||
IssueFilter,
|
||||
IssueSort,
|
||||
Page,
|
||||
} from '@task-stream/api/tracker/application';
|
||||
import {
|
||||
@@ -135,7 +136,11 @@ export class MongoFindIssuesRepository extends FindIssuesRepository {
|
||||
const [docs, total] = await Promise.all([
|
||||
this.issues
|
||||
.find(query)
|
||||
.sort({ updatedAt: -1 })
|
||||
.sort(
|
||||
filter.sort === IssueSort.RANK
|
||||
? { rank: 1, number: 1 }
|
||||
: { updatedAt: -1 },
|
||||
)
|
||||
.skip(filter.offset)
|
||||
.limit(filter.limit)
|
||||
.lean<IssueDoc[]>(),
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { implicitRank } from '@task-stream/api/tracker/domain';
|
||||
import { Model } from 'mongoose';
|
||||
import { IssueDoc, ReadModels } from './read-model.schemas';
|
||||
|
||||
/**
|
||||
* Tickets projected before the backlog could be ordered lack `rank`; give
|
||||
* them their creation order once at startup, the rank they have anyway.
|
||||
*/
|
||||
@Injectable()
|
||||
export class RankBackfill implements OnApplicationBootstrap {
|
||||
private readonly logger = new Logger(RankBackfill.name);
|
||||
|
||||
constructor(
|
||||
@InjectModel(ReadModels.Issue) private readonly issues: Model<IssueDoc>,
|
||||
) {}
|
||||
|
||||
async onApplicationBootstrap(): Promise<void> {
|
||||
const missing = await this.issues
|
||||
.find({ rank: { $exists: false } }, { number: 1 })
|
||||
.lean<Pick<IssueDoc, '_id' | 'number'>[]>();
|
||||
if (missing.length === 0) return;
|
||||
const result = await this.issues.bulkWrite(
|
||||
missing.map((doc) => ({
|
||||
updateOne: {
|
||||
filter: { _id: doc._id, rank: { $exists: false } },
|
||||
update: { $set: { rank: implicitRank(doc.number) } },
|
||||
},
|
||||
})),
|
||||
);
|
||||
this.logger.log(
|
||||
`Backlog rank filled in on ${result.modifiedCount} tickets`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
IssueTimelineEntry,
|
||||
ProjectReadModel,
|
||||
ReleaseReadModel,
|
||||
implicitRank,
|
||||
} from '@task-stream/api/tracker/domain';
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
@@ -102,6 +103,7 @@ IssueSchema.index({ projectId: 1, statusCategory: 1 });
|
||||
IssueSchema.index({ 'assignee.id': 1 });
|
||||
IssueSchema.index({ 'watchers.id': 1 });
|
||||
IssueSchema.index({ 'parent.id': 1 });
|
||||
IssueSchema.index({ projectId: 1, rank: 1 });
|
||||
IssueSchema.index({ 'release.id': 1 });
|
||||
IssueSchema.index({ title: 'text', description: 'text', key: 'text' });
|
||||
|
||||
@@ -186,6 +188,7 @@ export function issueFromDoc(doc: IssueDoc): IssueReadModel {
|
||||
attachments: rest.attachments ?? [],
|
||||
watchers: rest.watchers ?? [],
|
||||
parent: rest.parent?.id ? rest.parent : null,
|
||||
rank: rest.rank ?? implicitRank(rest.number),
|
||||
checklist: rest.checklist ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user