refactor: streamline message filtering in TeamDetailView
- Replaced manual message filtering logic with a dedicated utility function, `filterTeamMessages`, to enhance readability and maintainability. - Removed the temporary lead member message filtering, simplifying the message handling process. - Updated dependencies in the filtering logic to accommodate new parameters for time window and search query.
This commit is contained in:
parent
1f4c550ed3
commit
b09c4e4fd0
4 changed files with 129 additions and 45 deletions
|
|
@ -27,9 +27,9 @@ import { formatProjectPath } from '@renderer/utils/pathDisplay';
|
|||
import { buildTaskCountsByOwner, normalizePath } from '@renderer/utils/pathNormalize';
|
||||
import { nameColorSet } from '@renderer/utils/projectColor';
|
||||
import { resolveProjectIdByPath } from '@renderer/utils/projectLookup';
|
||||
import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering';
|
||||
import { toMessageKey } from '@renderer/utils/teamMessageKey';
|
||||
import { stripAgentBlocks } from '@shared/constants/agentBlocks';
|
||||
import { isInboxNoiseMessage } from '@shared/utils/inboxNoise';
|
||||
import { deriveTaskDisplayId, formatTaskDisplayLabel } from '@shared/utils/taskIdentity';
|
||||
import {
|
||||
AlertTriangle,
|
||||
|
|
@ -600,51 +600,14 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
|
|||
[data?.members]
|
||||
);
|
||||
|
||||
const leadMemberName = useMemo(
|
||||
() => activeMembers.find((m) => m.agentType === 'team-lead')?.name,
|
||||
[activeMembers]
|
||||
);
|
||||
|
||||
const filteredMessages = useMemo(() => {
|
||||
if (!data) return [];
|
||||
let list = data.messages;
|
||||
// Temporarily hide lead→user messages from the UI
|
||||
// (notifications and other processing still receive them via data.messages)
|
||||
if (leadMemberName) {
|
||||
list = list.filter((m) => !(m.to?.trim() === 'user' && m.from?.trim() === leadMemberName));
|
||||
}
|
||||
if (timeWindow) {
|
||||
list = list.filter((m) => {
|
||||
const ts = new Date(m.timestamp).getTime();
|
||||
return ts >= timeWindow.start && ts < timeWindow.end;
|
||||
});
|
||||
}
|
||||
if (!messagesFilter.showNoise) {
|
||||
list = list.filter((m) => !isInboxNoiseMessage(typeof m.text === 'string' ? m.text : ''));
|
||||
}
|
||||
const hasFrom = messagesFilter.from.size > 0;
|
||||
const hasTo = messagesFilter.to.size > 0;
|
||||
if (hasFrom || hasTo) {
|
||||
list = list.filter((m) => {
|
||||
const fromMatch = hasFrom && m.from?.trim() && messagesFilter.from.has(m.from.trim());
|
||||
const toMatch = hasTo && m.to?.trim() && messagesFilter.to.has(m.to.trim());
|
||||
// When both filters active → OR (show messages matching either direction)
|
||||
// When only one active → just that filter
|
||||
return fromMatch || toMatch;
|
||||
});
|
||||
}
|
||||
const q = messagesSearchQuery.trim().toLowerCase();
|
||||
if (q) {
|
||||
list = list.filter((m) => {
|
||||
const text = (m.text ?? '').toLowerCase();
|
||||
const summary = (m.summary ?? '').toLowerCase();
|
||||
const from = (m.from ?? '').toLowerCase();
|
||||
const to = (m.to ?? '').toLowerCase();
|
||||
return text.includes(q) || summary.includes(q) || from.includes(q) || to.includes(q);
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}, [data, timeWindow, messagesFilter, messagesSearchQuery, leadMemberName]);
|
||||
return filterTeamMessages(data.messages, {
|
||||
timeWindow,
|
||||
filter: messagesFilter,
|
||||
searchQuery: messagesSearchQuery,
|
||||
});
|
||||
}, [data, timeWindow, messagesFilter, messagesSearchQuery]);
|
||||
|
||||
const { readSet, markRead, markAllRead } = useTeamMessagesRead(teamName ?? '');
|
||||
const { expandedSet, toggle: toggleExpandOverride } = useTeamMessagesExpanded(teamName ?? '');
|
||||
|
|
|
|||
|
|
@ -442,7 +442,14 @@ export const MessageComposer = ({
|
|||
setTeamSelectorOpen(false);
|
||||
}}
|
||||
>
|
||||
<ArrowRightLeft size={11} className="shrink-0 text-purple-400" />
|
||||
{target.color ? (
|
||||
<span
|
||||
className="inline-block size-2 shrink-0 rounded-full"
|
||||
style={{ backgroundColor: target.color }}
|
||||
/>
|
||||
) : (
|
||||
<ArrowRightLeft size={11} className="shrink-0 text-purple-400" />
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-[var(--color-text)]">
|
||||
{target.displayName}
|
||||
|
|
|
|||
54
src/renderer/utils/teamMessageFiltering.ts
Normal file
54
src/renderer/utils/teamMessageFiltering.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { isInboxNoiseMessage } from '@shared/utils/inboxNoise';
|
||||
|
||||
import type { InboxMessage } from '@shared/types';
|
||||
|
||||
export interface TeamMessagesFilter {
|
||||
from: Set<string>;
|
||||
to: Set<string>;
|
||||
showNoise: boolean;
|
||||
}
|
||||
|
||||
export function filterTeamMessages(
|
||||
messages: InboxMessage[],
|
||||
options: {
|
||||
timeWindow?: { start: number; end: number } | null;
|
||||
filter: TeamMessagesFilter;
|
||||
searchQuery: string;
|
||||
}
|
||||
): InboxMessage[] {
|
||||
const { timeWindow, filter, searchQuery } = options;
|
||||
|
||||
let list = messages;
|
||||
if (timeWindow) {
|
||||
list = list.filter((m) => {
|
||||
const ts = new Date(m.timestamp).getTime();
|
||||
return ts >= timeWindow.start && ts < timeWindow.end;
|
||||
});
|
||||
}
|
||||
if (!filter.showNoise) {
|
||||
list = list.filter((m) => !isInboxNoiseMessage(typeof m.text === 'string' ? m.text : ''));
|
||||
}
|
||||
|
||||
const hasFrom = filter.from.size > 0;
|
||||
const hasTo = filter.to.size > 0;
|
||||
if (hasFrom || hasTo) {
|
||||
list = list.filter((m) => {
|
||||
const fromMatch = hasFrom && m.from?.trim() && filter.from.has(m.from.trim());
|
||||
const toMatch = hasTo && m.to?.trim() && filter.to.has(m.to.trim());
|
||||
return fromMatch || toMatch;
|
||||
});
|
||||
}
|
||||
|
||||
const q = searchQuery.trim().toLowerCase();
|
||||
if (q) {
|
||||
list = list.filter((m) => {
|
||||
const text = (m.text ?? '').toLowerCase();
|
||||
const summary = (m.summary ?? '').toLowerCase();
|
||||
const from = (m.from ?? '').toLowerCase();
|
||||
const to = (m.to ?? '').toLowerCase();
|
||||
return text.includes(q) || summary.includes(q) || from.includes(q) || to.includes(q);
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
60
test/renderer/utils/teamMessageFiltering.test.ts
Normal file
60
test/renderer/utils/teamMessageFiltering.test.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering';
|
||||
|
||||
import type { InboxMessage } from '@shared/types';
|
||||
|
||||
function makeMessage(overrides: Partial<InboxMessage> = {}): InboxMessage {
|
||||
return {
|
||||
from: 'team-lead',
|
||||
text: 'Hello',
|
||||
timestamp: '2026-03-09T12:00:00.000Z',
|
||||
read: true,
|
||||
messageId: 'msg-1',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('filterTeamMessages', () => {
|
||||
it('keeps lead-to-user messages visible', () => {
|
||||
const messages = [
|
||||
makeMessage({
|
||||
from: 'lead',
|
||||
to: 'user',
|
||||
text: 'Accepted cross-team request. Delegating now.',
|
||||
source: 'lead_process',
|
||||
}),
|
||||
];
|
||||
|
||||
const result = filterTeamMessages(messages, {
|
||||
timeWindow: null,
|
||||
filter: { from: new Set(), to: new Set(), showNoise: true },
|
||||
searchQuery: '',
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].to).toBe('user');
|
||||
expect(result[0].source).toBe('lead_process');
|
||||
});
|
||||
|
||||
it('still filters noise messages when showNoise is false', () => {
|
||||
const messages = [
|
||||
makeMessage({
|
||||
text: '{"type":"idle_notification","idleReason":"available"}',
|
||||
}),
|
||||
makeMessage({
|
||||
messageId: 'msg-2',
|
||||
text: 'Real visible message',
|
||||
}),
|
||||
];
|
||||
|
||||
const result = filterTeamMessages(messages, {
|
||||
timeWindow: null,
|
||||
filter: { from: new Set(), to: new Set(), showNoise: false },
|
||||
searchQuery: '',
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].messageId).toBe('msg-2');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue