perf(renderer): cache team message filter data
This commit is contained in:
parent
5d129ce822
commit
5f5b3dc62c
2 changed files with 120 additions and 32 deletions
|
|
@ -19,6 +19,30 @@ export interface TeamMessagesFilter {
|
|||
showNoise: boolean;
|
||||
}
|
||||
|
||||
interface CachedMessageFilterData {
|
||||
readonly messageKind: InboxMessage['messageKind'];
|
||||
readonly source: InboxMessage['source'];
|
||||
readonly from: InboxMessage['from'];
|
||||
readonly to: InboxMessage['to'];
|
||||
readonly messageId: InboxMessage['messageId'];
|
||||
readonly text: InboxMessage['text'];
|
||||
readonly trimmedMessageId: string;
|
||||
readonly trimmedFrom: string;
|
||||
readonly trimmedTo: string;
|
||||
readonly normalizedFrom: string;
|
||||
readonly normalizedTo: string;
|
||||
readonly normalizedText: string;
|
||||
readonly isTaskCommentNotification: boolean;
|
||||
readonly isTaskStallRemediation: boolean;
|
||||
readonly isMemberWorkSyncNudge: boolean;
|
||||
readonly isReviewPickupEscalation: boolean;
|
||||
readonly isInternalControlEnvelope: boolean;
|
||||
readonly isNoiseMessage: boolean;
|
||||
readonly keepIdleWhenNoiseHidden: boolean;
|
||||
}
|
||||
|
||||
const messageFilterDataCache = new WeakMap<InboxMessage, CachedMessageFilterData>();
|
||||
|
||||
function normalizeMessageText(value: string | undefined): string {
|
||||
return (value ?? '')
|
||||
.trim()
|
||||
|
|
@ -41,6 +65,47 @@ function normalizeLeadNames(values: Iterable<string> | undefined): Set<string> {
|
|||
return normalized;
|
||||
}
|
||||
|
||||
function getMessageFilterData(message: InboxMessage): CachedMessageFilterData {
|
||||
const cached = messageFilterDataCache.get(message);
|
||||
if (
|
||||
cached &&
|
||||
cached.messageKind === message.messageKind &&
|
||||
cached.source === message.source &&
|
||||
cached.from === message.from &&
|
||||
cached.to === message.to &&
|
||||
cached.messageId === message.messageId &&
|
||||
cached.text === message.text
|
||||
) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const text = typeof message.text === 'string' ? message.text : '';
|
||||
const isNoiseMessage = isInboxNoiseMessage(text);
|
||||
const next: CachedMessageFilterData = {
|
||||
messageKind: message.messageKind,
|
||||
source: message.source,
|
||||
from: message.from,
|
||||
to: message.to,
|
||||
messageId: message.messageId,
|
||||
text: message.text,
|
||||
trimmedMessageId: typeof message.messageId === 'string' ? message.messageId.trim() : '',
|
||||
trimmedFrom: typeof message.from === 'string' ? message.from.trim() : '',
|
||||
trimmedTo: typeof message.to === 'string' ? message.to.trim() : '',
|
||||
normalizedFrom: normalizeParticipant(message.from),
|
||||
normalizedTo: normalizeParticipant(message.to),
|
||||
normalizedText: normalizeMessageText(message.text),
|
||||
isTaskCommentNotification: message.messageKind === 'task_comment_notification',
|
||||
isTaskStallRemediation: isTaskStallRemediationMessage(message),
|
||||
isMemberWorkSyncNudge: isMemberWorkSyncNudgeMessage(message),
|
||||
isReviewPickupEscalation: isReviewPickupEscalationMessage(message),
|
||||
isInternalControlEnvelope: isTeamInternalControlMessageEnvelope(message),
|
||||
isNoiseMessage,
|
||||
keepIdleWhenNoiseHidden: isNoiseMessage && shouldKeepIdleMessageInActivityWhenNoiseHidden(text),
|
||||
};
|
||||
messageFilterDataCache.set(message, next);
|
||||
return next;
|
||||
}
|
||||
|
||||
function isLeadAlias(value: string | undefined): boolean {
|
||||
const normalized = normalizeParticipant(value).replace(/[\s_]+/g, '-');
|
||||
return (
|
||||
|
|
@ -65,25 +130,27 @@ function isRelayDuplicateOfVisibleMessage(
|
|||
return false;
|
||||
}
|
||||
|
||||
if (isInboxNoiseMessage(message.text)) {
|
||||
const messageData = getMessageFilterData(message);
|
||||
if (messageData.isNoiseMessage) {
|
||||
return true;
|
||||
}
|
||||
const originalData = getMessageFilterData(original);
|
||||
|
||||
const isInternalLeadRelayDelivery =
|
||||
(message.source === 'runtime_delivery' || message.source === 'lead_process') &&
|
||||
original.source === 'user_sent' &&
|
||||
normalizeParticipant(original.from) === 'user' &&
|
||||
originalData.normalizedFrom === 'user' &&
|
||||
isLeadParticipant(original.to, leadNames) &&
|
||||
isLeadParticipant(message.from, leadNames) &&
|
||||
normalizeParticipant(message.to) !== 'user';
|
||||
messageData.normalizedTo !== 'user';
|
||||
|
||||
if (isInternalLeadRelayDelivery) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const sameDirection =
|
||||
normalizeParticipant(message.from) === normalizeParticipant(original.from) &&
|
||||
normalizeParticipant(message.to) === normalizeParticipant(original.to);
|
||||
messageData.normalizedFrom === originalData.normalizedFrom &&
|
||||
messageData.normalizedTo === originalData.normalizedTo;
|
||||
|
||||
if (!sameDirection) {
|
||||
return false;
|
||||
|
|
@ -93,7 +160,7 @@ function isRelayDuplicateOfVisibleMessage(
|
|||
return true;
|
||||
}
|
||||
|
||||
return normalizeMessageText(message.text) === normalizeMessageText(original.text);
|
||||
return messageData.normalizedText === originalData.normalizedText;
|
||||
}
|
||||
|
||||
function getRuntimeDeliveryRelayDuplicateKey(
|
||||
|
|
@ -103,9 +170,10 @@ function getRuntimeDeliveryRelayDuplicateKey(
|
|||
if (message.source !== 'runtime_delivery') {
|
||||
return null;
|
||||
}
|
||||
const from = normalizeParticipant(message.from);
|
||||
const to = normalizeParticipant(message.to);
|
||||
const text = normalizeMessageText(message.text);
|
||||
const data = getMessageFilterData(message);
|
||||
const from = data.normalizedFrom;
|
||||
const to = data.normalizedTo;
|
||||
const text = data.normalizedText;
|
||||
if (!from || !to || !text) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -135,14 +203,16 @@ export function filterTeamMessages(
|
|||
} = options;
|
||||
const leadNames = normalizeLeadNames(rawLeadNames);
|
||||
|
||||
let list = messages.filter(
|
||||
(m) =>
|
||||
m.messageKind !== 'task_comment_notification' &&
|
||||
(includeAutomationEvents || !isTaskStallRemediationMessage(m)) &&
|
||||
(includeMemberWorkSyncNudges || !isMemberWorkSyncNudgeMessage(m)) &&
|
||||
!isReviewPickupEscalationMessage(m) &&
|
||||
!isTeamInternalControlMessageEnvelope(m)
|
||||
);
|
||||
let list = messages.filter((m) => {
|
||||
const data = getMessageFilterData(m);
|
||||
return (
|
||||
!data.isTaskCommentNotification &&
|
||||
(includeAutomationEvents || !data.isTaskStallRemediation) &&
|
||||
(includeMemberWorkSyncNudges || !data.isMemberWorkSyncNudge) &&
|
||||
!data.isReviewPickupEscalation &&
|
||||
!data.isInternalControlEnvelope
|
||||
);
|
||||
});
|
||||
if (timeWindow) {
|
||||
list = list.filter((m) => {
|
||||
const ts = new Date(m.timestamp).getTime();
|
||||
|
|
@ -151,12 +221,9 @@ export function filterTeamMessages(
|
|||
}
|
||||
if (!filter.showNoise) {
|
||||
list = list.filter((m) => {
|
||||
const text = typeof m.text === 'string' ? m.text : '';
|
||||
if (!isInboxNoiseMessage(text)) return true;
|
||||
return (
|
||||
includePassiveIdlePeerSummariesWhenNoiseHidden &&
|
||||
shouldKeepIdleMessageInActivityWhenNoiseHidden(text)
|
||||
);
|
||||
const data = getMessageFilterData(m);
|
||||
if (!data.isNoiseMessage) return true;
|
||||
return includePassiveIdlePeerSummariesWhenNoiseHidden && data.keepIdleWhenNoiseHidden;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -164,14 +231,16 @@ export function filterTeamMessages(
|
|||
const hasTo = filter.to.size > 0;
|
||||
if (hasFrom && hasTo) {
|
||||
list = list.filter((m) => {
|
||||
const fromMatch = Boolean(m.from?.trim() && filter.from.has(m.from.trim()));
|
||||
const toMatch = Boolean(m.to?.trim() && filter.to.has(m.to.trim()));
|
||||
const data = getMessageFilterData(m);
|
||||
const fromMatch = Boolean(data.trimmedFrom && filter.from.has(data.trimmedFrom));
|
||||
const toMatch = Boolean(data.trimmedTo && filter.to.has(data.trimmedTo));
|
||||
return fromMatch && toMatch;
|
||||
});
|
||||
} else if (hasFrom || hasTo) {
|
||||
list = list.filter((m) => {
|
||||
if (hasFrom) return Boolean(m.from?.trim() && filter.from.has(m.from.trim()));
|
||||
if (hasTo) return Boolean(m.to?.trim() && filter.to.has(m.to.trim()));
|
||||
const data = getMessageFilterData(m);
|
||||
if (hasFrom) return Boolean(data.trimmedFrom && filter.from.has(data.trimmedFrom));
|
||||
if (hasTo) return Boolean(data.trimmedTo && filter.to.has(data.trimmedTo));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
|
@ -190,7 +259,7 @@ export function filterTeamMessages(
|
|||
const visibleMessagesById = new Map(
|
||||
list
|
||||
.map((m) => {
|
||||
const id = typeof m.messageId === 'string' ? m.messageId.trim() : '';
|
||||
const id = getMessageFilterData(m).trimmedMessageId;
|
||||
return id ? ([id, m] as const) : null;
|
||||
})
|
||||
.filter((entry): entry is readonly [string, InboxMessage] => entry !== null)
|
||||
|
|
@ -204,8 +273,8 @@ export function filterTeamMessages(
|
|||
if (!relayOfMessageId) {
|
||||
return true;
|
||||
}
|
||||
const ownMessageId = typeof m.messageId === 'string' ? m.messageId.trim() : '';
|
||||
if (relayOfMessageId === ownMessageId) {
|
||||
const data = getMessageFilterData(m);
|
||||
if (relayOfMessageId === data.trimmedMessageId) {
|
||||
return true;
|
||||
}
|
||||
const runtimeDuplicateKey = getRuntimeDeliveryRelayDuplicateKey(m, relayOfMessageId);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { InboxMessage } from '@shared/types';
|
||||
|
||||
|
|
@ -500,6 +499,26 @@ Messages:
|
|||
expect(result[0].messageId).toBe('msg-2');
|
||||
});
|
||||
|
||||
it('recomputes cached message classification when mutable message fields change', () => {
|
||||
const message = makeMessage({
|
||||
messageId: 'mutable-message',
|
||||
text: '{"type":"idle_notification","idleReason":"available"}',
|
||||
});
|
||||
const options = {
|
||||
timeWindow: null,
|
||||
filter: { from: new Set<string>(), to: new Set<string>(), showNoise: false },
|
||||
searchQuery: '',
|
||||
};
|
||||
|
||||
expect(filterTeamMessages([message], options)).toEqual([]);
|
||||
|
||||
message.text = 'Real visible message';
|
||||
|
||||
expect(filterTeamMessages([message], options).map((item) => item.messageId)).toEqual([
|
||||
'mutable-message',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can preserve passive peer-summary idle rows in the activity sink while keeping pure heartbeat hidden even after read', () => {
|
||||
const messages = [
|
||||
makeMessage({
|
||||
|
|
|
|||
Loading…
Reference in a new issue