perf(team): cap global task projections earlier

This commit is contained in:
777genius 2026-05-30 18:49:14 +03:00
parent 304d0a5ef1
commit b13ee56359
2 changed files with 84 additions and 22 deletions

View file

@ -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;
}

View file

@ -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(
{