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 deletedTeams = new Set(teams.filter((t) => t.deletedAt).map((t) => t.teamName));
|
||||||
|
|
||||||
const teamNames = [
|
const MAX_GLOBAL_TASKS_EXPORTED = 500;
|
||||||
...new Set(rawTasks.map((t) => t.teamName).filter((n) => teamInfoMap.has(n))),
|
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>();
|
const kanbanByTeam = new Map<string, KanbanState>();
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
teamNames.map(async (teamName) => {
|
teamNames.map(async (teamName) => {
|
||||||
|
|
@ -1058,10 +1070,7 @@ export class TeamDataService {
|
||||||
|
|
||||||
const out: GlobalTask[] = [];
|
const out: GlobalTask[] = [];
|
||||||
let processed = 0;
|
let processed = 0;
|
||||||
for (const task of rawTasks) {
|
for (const task of tasksToExport) {
|
||||||
if (!teamInfoMap.has(task.teamName)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const info = teamInfoMap.get(task.teamName)!;
|
const info = teamInfoMap.get(task.teamName)!;
|
||||||
const kanbanTaskState = kanbanByTeam.get(task.teamName)?.tasks[task.id];
|
const kanbanTaskState = kanbanByTeam.get(task.teamName)?.tasks[task.id];
|
||||||
const reviewState = this.resolveTaskReviewState(task, kanbanTaskState);
|
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;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1524,7 +1524,7 @@ describe('TeamDataService', () => {
|
||||||
{} as never,
|
{} as never,
|
||||||
{} as never,
|
{} as never,
|
||||||
{} as never,
|
{} as never,
|
||||||
(teamName: string) =>
|
(_teamName: string) =>
|
||||||
({
|
({
|
||||||
tasks: {
|
tasks: {
|
||||||
createTask: createTaskMock,
|
createTask: createTaskMock,
|
||||||
|
|
@ -1612,7 +1612,7 @@ describe('TeamDataService', () => {
|
||||||
{} as never,
|
{} as never,
|
||||||
{} as never,
|
{} as never,
|
||||||
{} as never,
|
{} as never,
|
||||||
(teamName: string) =>
|
(_teamName: string) =>
|
||||||
({
|
({
|
||||||
tasks: {
|
tasks: {
|
||||||
createTask: createTaskMock,
|
createTask: createTaskMock,
|
||||||
|
|
@ -1724,7 +1724,7 @@ describe('TeamDataService', () => {
|
||||||
{} as never,
|
{} as never,
|
||||||
{} as never,
|
{} as never,
|
||||||
{} as never,
|
{} as never,
|
||||||
(teamName: string) =>
|
(_teamName: string) =>
|
||||||
({
|
({
|
||||||
tasks: {
|
tasks: {
|
||||||
createTask: createTaskMock,
|
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 () => {
|
it('lets kanban approved overlay win over stale review history in global task projections', async () => {
|
||||||
const service = new TeamDataService(
|
const service = new TeamDataService(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue