perf(renderer): reuse global task notification indexes

This commit is contained in:
777genius 2026-05-31 06:45:42 +03:00
parent c1ebecf813
commit 8eada276ca

View file

@ -8,7 +8,12 @@ import {
} from '@shared/utils/teamTaskState'; } from '@shared/utils/teamTaskState';
import type { AppConfig } from '@renderer/types/data'; import type { AppConfig } from '@renderer/types/data';
import type { GlobalTask, TaskComment, TeamMessageNotificationData, TeamSummary } from '@shared/types'; import type {
GlobalTask,
TaskComment,
TeamMessageNotificationData,
TeamSummary,
} from '@shared/types';
const notifiedClarificationTaskKeys = new Set<string>(); const notifiedClarificationTaskKeys = new Set<string>();
const notifiedStatusChangeKeys = new Set<string>(); const notifiedStatusChangeKeys = new Set<string>();
@ -19,6 +24,13 @@ const notifiedBlockedTaskKeys = new Set<string>();
let isFirstFetchAllTasks = true; let isFirstFetchAllTasks = true;
interface TaskNotificationIndexes {
readonly firstOldTaskByKey: ReadonlyMap<string, GlobalTask>;
readonly lastOldTaskByKey: ReadonlyMap<string, GlobalTask>;
readonly oldTaskKeys: ReadonlySet<string>;
readonly oldTasksByTeam: ReadonlyMap<string, readonly GlobalTask[]>;
}
export interface ProcessGlobalTaskNotificationsParams { export interface ProcessGlobalTaskNotificationsParams {
oldTasks: GlobalTask[]; oldTasks: GlobalTask[];
newTasks: GlobalTask[]; newTasks: GlobalTask[];
@ -43,9 +55,7 @@ export function consumeFirstGlobalTasksFetchFlag(): boolean {
return wasFirst; return wasFirst;
} }
export function processGlobalTaskNotifications( export function processGlobalTaskNotifications(params: ProcessGlobalTaskNotificationsParams): void {
params: ProcessGlobalTaskNotificationsParams
): void {
const { oldTasks, newTasks, appConfig, teamByName, isInitialFetch } = params; const { oldTasks, newTasks, appConfig, teamByName, isInitialFetch } = params;
if (isInitialFetch) { if (isInitialFetch) {
@ -54,18 +64,20 @@ export function processGlobalTaskNotifications(
} }
const notifyOnClarifications = appConfig?.notifications?.notifyOnClarifications ?? true; const notifyOnClarifications = appConfig?.notifications?.notifyOnClarifications ?? true;
detectClarificationNotifications(oldTasks, newTasks, notifyOnClarifications); const oldTaskIndexes = buildTaskNotificationIndexes(oldTasks);
detectBlockedTaskNotifications(oldTasks, newTasks, notifyOnClarifications);
detectStatusChangeNotifications(oldTasks, newTasks, appConfig, teamByName); detectClarificationNotifications(oldTaskIndexes, newTasks, notifyOnClarifications);
detectBlockedTaskNotifications(oldTaskIndexes, newTasks, notifyOnClarifications);
detectStatusChangeNotifications(oldTaskIndexes, newTasks, appConfig, teamByName);
const notifyOnTaskComments = appConfig?.notifications?.notifyOnTaskComments ?? true; const notifyOnTaskComments = appConfig?.notifications?.notifyOnTaskComments ?? true;
detectTaskCommentNotifications(oldTasks, newTasks, notifyOnTaskComments); detectTaskCommentNotifications(oldTaskIndexes, newTasks, notifyOnTaskComments);
const notifyOnTaskCreated = appConfig?.notifications?.notifyOnTaskCreated ?? true; const notifyOnTaskCreated = appConfig?.notifications?.notifyOnTaskCreated ?? true;
detectTaskCreatedNotifications(oldTasks, newTasks, notifyOnTaskCreated); detectTaskCreatedNotifications(oldTaskIndexes, newTasks, notifyOnTaskCreated);
const notifyOnAllCompleted = appConfig?.notifications?.notifyOnAllTasksCompleted ?? true; const notifyOnAllCompleted = appConfig?.notifications?.notifyOnAllTasksCompleted ?? true;
detectAllTasksCompletedNotification(oldTasks, newTasks, notifyOnAllCompleted); detectAllTasksCompletedNotification(oldTaskIndexes, newTasks, notifyOnAllCompleted);
} }
function seedGlobalTaskNotificationState(tasks: readonly GlobalTask[]): void { function seedGlobalTaskNotificationState(tasks: readonly GlobalTask[]): void {
@ -74,7 +86,9 @@ function seedGlobalTaskNotificationState(tasks: readonly GlobalTask[]): void {
notifiedClarificationTaskKeys.add(`${task.teamName}:${task.id}`); notifiedClarificationTaskKeys.add(`${task.teamName}:${task.id}`);
} }
if ((task.blockedBy?.length ?? 0) > 0) { if ((task.blockedBy?.length ?? 0) > 0) {
notifiedBlockedTaskKeys.add(`${task.teamName}:${task.id}:${(task.blockedBy ?? []).join(',')}`); notifiedBlockedTaskKeys.add(
`${task.teamName}:${task.id}:${(task.blockedBy ?? []).join(',')}`
);
} }
notifiedStatusChangeKeys.add(`${task.teamName}:${task.id}:${task.status}`); notifiedStatusChangeKeys.add(`${task.teamName}:${task.id}:${task.status}`);
if (isTeamTaskNeedsFixActionable(task)) { if (isTeamTaskNeedsFixActionable(task)) {
@ -105,19 +119,53 @@ function seedGlobalTaskNotificationState(tasks: readonly GlobalTask[]): void {
} }
} }
function getTaskNotificationKey(task: Pick<GlobalTask, 'teamName' | 'id'>): string {
return `${task.teamName}:${task.id}`;
}
function buildTaskNotificationIndexes(tasks: readonly GlobalTask[]): TaskNotificationIndexes {
const firstOldTaskByKey = new Map<string, GlobalTask>();
const lastOldTaskByKey = new Map<string, GlobalTask>();
const oldTaskKeys = new Set<string>();
const oldTasksByTeam = new Map<string, GlobalTask[]>();
for (const task of tasks) {
const key = getTaskNotificationKey(task);
if (!firstOldTaskByKey.has(key)) {
firstOldTaskByKey.set(key, task);
}
lastOldTaskByKey.set(key, task);
oldTaskKeys.add(key);
const teamTasks = oldTasksByTeam.get(task.teamName);
if (teamTasks) {
teamTasks.push(task);
} else {
oldTasksByTeam.set(task.teamName, [task]);
}
}
return {
firstOldTaskByKey,
lastOldTaskByKey,
oldTaskKeys,
oldTasksByTeam,
};
}
function showTeamNotification(data: TeamMessageNotificationData): void { function showTeamNotification(data: TeamMessageNotificationData): void {
void api.teams?.showMessageNotification(data).catch(() => undefined); void api.teams?.showMessageNotification(data).catch(() => undefined);
} }
function detectClarificationNotifications( function detectClarificationNotifications(
oldTasks: GlobalTask[], oldTaskIndexes: TaskNotificationIndexes,
newTasks: GlobalTask[], newTasks: GlobalTask[],
notifyEnabled: boolean notifyEnabled: boolean
): void { ): void {
for (const task of newTasks) { for (const task of newTasks) {
const key = `${task.teamName}:${task.id}`; const key = getTaskNotificationKey(task);
if (task.needsClarification === 'user') { if (task.needsClarification === 'user') {
const oldTask = oldTasks.find((t) => t.teamName === task.teamName && t.id === task.id); const oldTask = oldTaskIndexes.firstOldTaskByKey.get(key);
if (oldTask?.needsClarification !== 'user' && !notifiedClarificationTaskKeys.has(key)) { if (oldTask?.needsClarification !== 'user' && !notifiedClarificationTaskKeys.has(key)) {
notifiedClarificationTaskKeys.add(key); notifiedClarificationTaskKeys.add(key);
showClarificationNotification(task, !notifyEnabled); showClarificationNotification(task, !notifyEnabled);
@ -155,7 +203,7 @@ function showClarificationNotification(task: GlobalTask, suppressToast: boolean)
} }
function detectStatusChangeNotifications( function detectStatusChangeNotifications(
oldTasks: GlobalTask[], oldTaskIndexes: TaskNotificationIndexes,
newTasks: GlobalTask[], newTasks: GlobalTask[],
config: AppConfig | null, config: AppConfig | null,
teamByName: Record<string, TeamSummary> teamByName: Record<string, TeamSummary>
@ -168,7 +216,7 @@ function detectStatusChangeNotifications(
const onlySolo = config?.notifications?.statusChangeOnlySolo ?? true; const onlySolo = config?.notifications?.statusChangeOnlySolo ?? true;
for (const task of newTasks) { for (const task of newTasks) {
const oldTask = oldTasks.find((t) => t.teamName === task.teamName && t.id === task.id); const oldTask = oldTaskIndexes.firstOldTaskByKey.get(getTaskNotificationKey(task));
if (!oldTask) continue; if (!oldTask) continue;
const taskKanbanColumn = getTeamTaskWorkflowColumn(task); const taskKanbanColumn = getTeamTaskWorkflowColumn(task);
@ -254,15 +302,12 @@ function showStatusChangeNotification(
} }
function detectTaskCommentNotifications( function detectTaskCommentNotifications(
oldTasks: GlobalTask[], oldTaskIndexes: TaskNotificationIndexes,
newTasks: GlobalTask[], newTasks: GlobalTask[],
notifyEnabled: boolean notifyEnabled: boolean
): void { ): void {
const oldTaskMap = new Map(oldTasks.map((t) => [`${t.teamName}:${t.id}`, t]));
for (const task of newTasks) { for (const task of newTasks) {
const mapKey = `${task.teamName}:${task.id}`; const oldTask = oldTaskIndexes.lastOldTaskByKey.get(getTaskNotificationKey(task));
const oldTask = oldTaskMap.get(mapKey);
const oldCommentCount = oldTask?.comments?.length ?? 0; const oldCommentCount = oldTask?.comments?.length ?? 0;
const newCommentCount = task.comments?.length ?? 0; const newCommentCount = task.comments?.length ?? 0;
@ -346,14 +391,12 @@ function showTaskReviewRequestedNotification(
} }
function detectBlockedTaskNotifications( function detectBlockedTaskNotifications(
oldTasks: GlobalTask[], oldTaskIndexes: TaskNotificationIndexes,
newTasks: GlobalTask[], newTasks: GlobalTask[],
notifyEnabled: boolean notifyEnabled: boolean
): void { ): void {
const oldTaskMap = new Map(oldTasks.map((task) => [`${task.teamName}:${task.id}`, task]));
for (const task of newTasks) { for (const task of newTasks) {
const oldTask = oldTaskMap.get(`${task.teamName}:${task.id}`); const oldTask = oldTaskIndexes.lastOldTaskByKey.get(getTaskNotificationKey(task));
const oldBlockedBy = new Set(oldTask?.blockedBy?.filter(Boolean) ?? []); const oldBlockedBy = new Set(oldTask?.blockedBy?.filter(Boolean) ?? []);
const newBlockedBy = Array.from(new Set(task.blockedBy?.filter(Boolean) ?? [])); const newBlockedBy = Array.from(new Set(task.blockedBy?.filter(Boolean) ?? []));
const taskKeyPrefix = `${task.teamName}:${task.id}:`; const taskKeyPrefix = `${task.teamName}:${task.id}:`;
@ -407,15 +450,13 @@ function showTaskBlockedNotification(
} }
function detectTaskCreatedNotifications( function detectTaskCreatedNotifications(
oldTasks: GlobalTask[], oldTaskIndexes: TaskNotificationIndexes,
newTasks: GlobalTask[], newTasks: GlobalTask[],
notifyEnabled: boolean notifyEnabled: boolean
): void { ): void {
const oldTaskKeys = new Set(oldTasks.map((t) => `${t.teamName}:${t.id}`));
for (const task of newTasks) { for (const task of newTasks) {
const key = `${task.teamName}:${task.id}`; const key = getTaskNotificationKey(task);
if (oldTaskKeys.has(key)) continue; if (oldTaskIndexes.oldTaskKeys.has(key)) continue;
if (notifiedCreatedTaskKeys.has(key)) continue; if (notifiedCreatedTaskKeys.has(key)) continue;
notifiedCreatedTaskKeys.add(key); notifiedCreatedTaskKeys.add(key);
@ -444,7 +485,7 @@ function showTaskCreatedNotification(task: GlobalTask, suppressToast: boolean):
} }
function detectAllTasksCompletedNotification( function detectAllTasksCompletedNotification(
oldTasks: GlobalTask[], oldTaskIndexes: TaskNotificationIndexes,
newTasks: GlobalTask[], newTasks: GlobalTask[],
notifyEnabled: boolean notifyEnabled: boolean
): void { ): void {
@ -464,7 +505,7 @@ function detectAllTasksCompletedNotification(
} }
if (notifiedAllCompletedTeams.has(teamName)) continue; if (notifiedAllCompletedTeams.has(teamName)) continue;
const oldTeamTasks = oldTasks.filter((t) => t.teamName === teamName); const oldTeamTasks = oldTaskIndexes.oldTasksByTeam.get(teamName) ?? [];
const wasAlreadyAllCompleted = const wasAlreadyAllCompleted =
oldTeamTasks.length > 0 && oldTeamTasks.every(isTeamTaskFinalForCompletionNotification); oldTeamTasks.length > 0 && oldTeamTasks.every(isTeamTaskFinalForCompletionNotification);
if (wasAlreadyAllCompleted) { if (wasAlreadyAllCompleted) {