perf(team): cap global task projections earlier
This commit is contained in:
parent
304d0a5ef1
commit
b13ee56359
2 changed files with 84 additions and 22 deletions
|
|
@ -1041,9 +1041,21 @@ export class TeamDataService {
|
|||
|
||||
const deletedTeams = new Set(teams.filter((t) => t.deletedAt).map((t) => t.teamName));
|
||||
|
||||
const teamNames = [
|
||||
...new Set(rawTasks.map((t) => t.teamName).filter((n) => teamInfoMap.has(n))),
|
||||
];
|
||||
const MAX_GLOBAL_TASKS_EXPORTED = 500;
|
||||
let tasksToExport = rawTasks.filter((task) => teamInfoMap.has(task.teamName));
|
||||
if (tasksToExport.length > MAX_GLOBAL_TASKS_EXPORTED) {
|
||||
// Prefer newest first before reading kanban and building the lightweight IPC projection.
|
||||
tasksToExport = tasksToExport
|
||||
.slice()
|
||||
.sort((a, b) => {
|
||||
const at = Date.parse(a.updatedAt ?? a.createdAt ?? '') || 0;
|
||||
const bt = Date.parse(b.updatedAt ?? b.createdAt ?? '') || 0;
|
||||
return bt - at;
|
||||
})
|
||||
.slice(0, MAX_GLOBAL_TASKS_EXPORTED);
|
||||
}
|
||||
|
||||
const teamNames = [...new Set(tasksToExport.map((task) => task.teamName))];
|
||||
const kanbanByTeam = new Map<string, KanbanState>();
|
||||
await Promise.all(
|
||||
teamNames.map(async (teamName) => {
|
||||
|
|
@ -1058,10 +1070,7 @@ export class TeamDataService {
|
|||
|
||||
const out: GlobalTask[] = [];
|
||||
let processed = 0;
|
||||
for (const task of rawTasks) {
|
||||
if (!teamInfoMap.has(task.teamName)) {
|
||||
continue;
|
||||
}
|
||||
for (const task of tasksToExport) {
|
||||
const info = teamInfoMap.get(task.teamName)!;
|
||||
const kanbanTaskState = kanbanByTeam.get(task.teamName)?.tasks[task.id];
|
||||
const reviewState = this.resolveTaskReviewState(task, kanbanTaskState);
|
||||
|
|
@ -1117,18 +1126,6 @@ export class TeamDataService {
|
|||
}
|
||||
}
|
||||
|
||||
// Hard cap: keep renderer responsive even with huge task sets.
|
||||
const MAX_GLOBAL_TASKS_EXPORTED = 500;
|
||||
if (out.length > MAX_GLOBAL_TASKS_EXPORTED) {
|
||||
// Prefer newest first if timestamps exist.
|
||||
out.sort((a, b) => {
|
||||
const at = Date.parse(a.updatedAt ?? a.createdAt ?? '') || 0;
|
||||
const bt = Date.parse(b.updatedAt ?? b.createdAt ?? '') || 0;
|
||||
return bt - at;
|
||||
});
|
||||
return out.slice(0, MAX_GLOBAL_TASKS_EXPORTED);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1524,7 +1524,7 @@ describe('TeamDataService', () => {
|
|||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
(teamName: string) =>
|
||||
(_teamName: string) =>
|
||||
({
|
||||
tasks: {
|
||||
createTask: createTaskMock,
|
||||
|
|
@ -1612,7 +1612,7 @@ describe('TeamDataService', () => {
|
|||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
(teamName: string) =>
|
||||
(_teamName: string) =>
|
||||
({
|
||||
tasks: {
|
||||
createTask: createTaskMock,
|
||||
|
|
@ -1724,7 +1724,7 @@ describe('TeamDataService', () => {
|
|||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
(teamName: string) =>
|
||||
(_teamName: string) =>
|
||||
({
|
||||
tasks: {
|
||||
createTask: createTaskMock,
|
||||
|
|
@ -2235,6 +2235,71 @@ describe('TeamDataService', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('caps global task projections before building lightweight comment payloads', async () => {
|
||||
const rawTasks = Array.from({ length: 501 }, (_, index) => ({
|
||||
id: `task-${index}`,
|
||||
teamName: index === 0 ? 'old-team' : 'my-team',
|
||||
subject: `Task ${index}`,
|
||||
status: 'pending' as const,
|
||||
owner: 'bob',
|
||||
createdAt: `2026-03-01T00:${String(index % 60).padStart(2, '0')}:00.000Z`,
|
||||
updatedAt: `2026-03-01T${String(Math.floor(index / 60)).padStart(2, '0')}:${String(
|
||||
index % 60
|
||||
).padStart(2, '0')}:00.000Z`,
|
||||
comments: [
|
||||
{
|
||||
id: `comment-${index}`,
|
||||
author: 'bob',
|
||||
text: `Comment ${index}`,
|
||||
createdAt: '2026-03-01T09:00:00.000Z',
|
||||
type: 'comment' as const,
|
||||
},
|
||||
],
|
||||
}));
|
||||
const getState = vi.fn(async (teamName: string) => ({
|
||||
teamName,
|
||||
reviewers: [],
|
||||
tasks: {},
|
||||
}));
|
||||
const service = new TeamDataService(
|
||||
{
|
||||
listTeams: vi.fn(async () => [
|
||||
{
|
||||
teamName: 'my-team',
|
||||
displayName: 'My team',
|
||||
projectPath: '/repo',
|
||||
},
|
||||
{
|
||||
teamName: 'old-team',
|
||||
displayName: 'Old team',
|
||||
projectPath: '/old-repo',
|
||||
},
|
||||
]),
|
||||
} as never,
|
||||
{
|
||||
getAllTasks: vi.fn(async () => rawTasks),
|
||||
} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{
|
||||
getState,
|
||||
} as never
|
||||
);
|
||||
|
||||
const tasks = await service.getAllTasks();
|
||||
|
||||
expect(tasks).toHaveLength(500);
|
||||
expect(tasks[0]?.id).toBe('task-500');
|
||||
expect(tasks.some((task) => task.id === 'task-0')).toBe(false);
|
||||
expect(tasks[0]?.comments?.[0]).toMatchObject({
|
||||
id: 'comment-500',
|
||||
text: 'Comment 500',
|
||||
});
|
||||
expect(getState).not.toHaveBeenCalledWith('old-team');
|
||||
});
|
||||
|
||||
it('lets kanban approved overlay win over stale review history in global task projections', async () => {
|
||||
const service = new TeamDataService(
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue