fix(team): preserve task labels and change presence
This commit is contained in:
parent
086a8b3e29
commit
5513531053
4 changed files with 106 additions and 15 deletions
|
|
@ -94,6 +94,7 @@ export const SidebarTaskItem = memo(function SidebarTaskItem({
|
|||
getDisplaySubject,
|
||||
}: SidebarTaskItemProps): React.JSX.Element {
|
||||
const { t } = useAppTranslation('team');
|
||||
const { t: tCommon } = useAppTranslation('common');
|
||||
const openGlobalTaskDetail = useStore((s) => s.openGlobalTaskDetail);
|
||||
const teamMembers = useStore(useShallow((s) => s.teamByName[task.teamName]?.members));
|
||||
const unreadCount = useUnreadCommentCount(task.teamName, task.id, task.comments);
|
||||
|
|
@ -137,10 +138,10 @@ export const SidebarTaskItem = memo(function SidebarTaskItem({
|
|||
);
|
||||
const updatedLabel = formatUpdatedLabel(
|
||||
task,
|
||||
t('tasks.date.updatedPrefix'),
|
||||
t('tasks.date.updatedYesterday')
|
||||
tCommon('tasks.date.updatedPrefix'),
|
||||
tCommon('tasks.date.updatedYesterday')
|
||||
);
|
||||
const dateLabel = updatedLabel ?? formatTaskDate(task.createdAt, t('tasks.date.yesterday'));
|
||||
const dateLabel = updatedLabel ?? formatTaskDate(task.createdAt, tCommon('tasks.date.yesterday'));
|
||||
|
||||
const ownerColorSet = useMemo(() => {
|
||||
if (!teamMembers || !task.owner) return null;
|
||||
|
|
@ -246,7 +247,7 @@ export const SidebarTaskItem = memo(function SidebarTaskItem({
|
|||
<span
|
||||
className={`ml-1.5 inline-block rounded-full px-1.5 py-0.5 align-middle text-[10px] font-medium leading-none ${REVIEW_STATE_DISPLAY.needsFix.bg} ${REVIEW_STATE_DISPLAY.needsFix.text}`}
|
||||
>
|
||||
{t('tasks.reviewState.needsFix')}
|
||||
{tCommon('tasks.reviewState.needsFix')}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -522,10 +522,8 @@ export function __getTeamScopedTransientStateForTests(teamName: string): {
|
|||
getResolvedMemberSelectorCacheSnapshotForTeam(teamName);
|
||||
|
||||
return {
|
||||
hasResolvedMembersSelector:
|
||||
resolvedMemberSelectorCacheSnapshot.hasResolvedMembersSelector,
|
||||
resolvedMemberSelectorCount:
|
||||
resolvedMemberSelectorCacheSnapshot.resolvedMemberSelectorCount,
|
||||
hasResolvedMembersSelector: resolvedMemberSelectorCacheSnapshot.hasResolvedMembersSelector,
|
||||
resolvedMemberSelectorCount: resolvedMemberSelectorCacheSnapshot.resolvedMemberSelectorCount,
|
||||
hasMergedMessagesSelector: messageSelectorCache.hasMergedMessagesSelector,
|
||||
memberMessagesSelectorCount: messageSelectorCache.memberMessagesSelectorCount,
|
||||
hasPendingFreshTeamDataRefresh: pendingFreshTeamDataRefreshes.has(teamName),
|
||||
|
|
@ -621,12 +619,7 @@ function maybeLogMemberSpawnUiEqualSuppressed(
|
|||
teamName: string,
|
||||
runId: string | null | undefined
|
||||
): void {
|
||||
if (
|
||||
!shouldLogMemberSpawnUiEqualSuppressed(
|
||||
teamName,
|
||||
MEMBER_SPAWN_UI_EQUAL_WARN_THROTTLE_MS
|
||||
)
|
||||
) {
|
||||
if (!shouldLogMemberSpawnUiEqualSuppressed(teamName, MEMBER_SPAWN_UI_EQUAL_WARN_THROTTLE_MS)) {
|
||||
return;
|
||||
}
|
||||
logger.debug(
|
||||
|
|
@ -2050,6 +2043,13 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
let changed = false;
|
||||
const nextTasks = teamData.tasks.map((task) => {
|
||||
const nextPresence = presenceByTaskId[task.id] ?? 'unknown';
|
||||
if (
|
||||
nextPresence === 'unknown' &&
|
||||
task.changePresence &&
|
||||
task.changePresence !== 'unknown'
|
||||
) {
|
||||
return task;
|
||||
}
|
||||
if (task.changePresence === nextPresence) {
|
||||
return task;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { act } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { GlobalTask } from '../../../../src/shared/types';
|
||||
|
|
@ -29,6 +30,28 @@ vi.mock('../../../../src/renderer/hooks/useTheme', () => ({
|
|||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@features/localization/renderer', () => ({
|
||||
useAppTranslation: (namespace: string) => {
|
||||
const catalogs: Record<string, Record<string, string>> = {
|
||||
common: {
|
||||
'tasks.date.updatedPrefix': 'upd',
|
||||
'tasks.date.updatedYesterday': 'upd yesterday',
|
||||
'tasks.date.yesterday': 'Yesterday',
|
||||
'tasks.reviewState.needsFix': 'Needs Fixes',
|
||||
},
|
||||
team: {
|
||||
'tasks.teamPrefix': 'Team:',
|
||||
'tasks.unassigned': 'Unassigned',
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
resolvedLanguage: 'en',
|
||||
t: (key: string) => catalogs[namespace]?.[key] ?? key,
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../../../src/renderer/components/ui/tooltip', () => ({
|
||||
Tooltip: ({ children }: React.PropsWithChildren) =>
|
||||
React.createElement(React.Fragment, null, children),
|
||||
|
|
@ -203,4 +226,38 @@ describe('SidebarTaskItem unread styling', () => {
|
|||
await Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders translated updated and review labels instead of i18n keys', async () => {
|
||||
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
|
||||
|
||||
const host = document.createElement('div');
|
||||
document.body.appendChild(host);
|
||||
const root = createRoot(host);
|
||||
|
||||
const updatedAt = new Date();
|
||||
const createdAt = new Date(updatedAt.getTime() - 5 * 60_000);
|
||||
|
||||
await act(async () => {
|
||||
root.render(
|
||||
React.createElement(SidebarTaskItem, {
|
||||
task: makeTask({
|
||||
createdAt: createdAt.toISOString(),
|
||||
reviewState: 'needsFix',
|
||||
updatedAt: updatedAt.toISOString(),
|
||||
}),
|
||||
})
|
||||
);
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(host.textContent).toContain('upd');
|
||||
expect(host.textContent).toContain('Needs Fixes');
|
||||
expect(host.textContent).not.toContain('tasks.date.updatedPrefix');
|
||||
expect(host.textContent).not.toContain('tasks.reviewState.needsFix');
|
||||
|
||||
await act(async () => {
|
||||
root.unmount();
|
||||
await Promise.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import {
|
|||
__resetTeamSliceModuleStateForTests,
|
||||
createTeamSlice,
|
||||
getActiveTeamPendingReplyWaits,
|
||||
hasActiveTeamPendingReplyWait,
|
||||
getCurrentProvisioningProgressForTeam,
|
||||
hasActiveTeamPendingReplyWait,
|
||||
loadPersistedMessagesPanelMode,
|
||||
savePersistedMessagesPanelMode,
|
||||
selectMemberMessagesForTeamMember,
|
||||
|
|
@ -24,6 +24,7 @@ import {
|
|||
const hoisted = vi.hoisted(() => ({
|
||||
list: vi.fn(),
|
||||
getData: vi.fn(),
|
||||
getTaskChangePresence: vi.fn(),
|
||||
getMessagesPage: vi.fn(),
|
||||
getMemberActivityMeta: vi.fn(),
|
||||
createTeam: vi.fn(),
|
||||
|
|
@ -61,6 +62,7 @@ vi.mock('@renderer/api', () => ({
|
|||
teams: {
|
||||
list: hoisted.list,
|
||||
getData: hoisted.getData,
|
||||
getTaskChangePresence: hoisted.getTaskChangePresence,
|
||||
getMessagesPage: hoisted.getMessagesPage,
|
||||
getMemberActivityMeta: hoisted.getMemberActivityMeta,
|
||||
createTeam: hoisted.createTeam,
|
||||
|
|
@ -302,6 +304,7 @@ describe('teamSlice actions', () => {
|
|||
__resetTeamRefreshFanoutDiagnosticsForTests();
|
||||
hoisted.list.mockResolvedValue([]);
|
||||
hoisted.getData.mockResolvedValue(createTeamSnapshot());
|
||||
hoisted.getTaskChangePresence.mockResolvedValue({});
|
||||
hoisted.getMessagesPage.mockResolvedValue({
|
||||
messages: [],
|
||||
nextCursor: null,
|
||||
|
|
@ -5147,6 +5150,36 @@ describe('teamSlice actions', () => {
|
|||
|
||||
expect(store.getState().selectedTeamData?.tasks[0]?.changePresence).toBe('has_changes');
|
||||
});
|
||||
|
||||
it('does not clear known task changePresence when presence refresh returns unknown', async () => {
|
||||
const store = createSliceStore();
|
||||
store.setState({
|
||||
selectedTeamName: 'my-team',
|
||||
selectedTeamData: createTeamSnapshot({
|
||||
tasks: [
|
||||
{
|
||||
id: 'task-1',
|
||||
subject: 'Known changes',
|
||||
status: 'in_progress',
|
||||
owner: 'alice',
|
||||
createdAt: '2026-03-01T10:00:00.000Z',
|
||||
updatedAt: '2026-03-01T10:00:00.000Z',
|
||||
workIntervals: [{ startedAt: '2026-03-01T10:05:00.000Z' }],
|
||||
historyEvents: [],
|
||||
comments: [],
|
||||
attachments: [],
|
||||
changePresence: 'has_changes',
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
hoisted.getTaskChangePresence.mockResolvedValue({ 'task-1': 'unknown' });
|
||||
|
||||
await store.getState().refreshTeamChangePresence('my-team');
|
||||
|
||||
expect(store.getState().selectedTeamData?.tasks[0]?.changePresence).toBe('has_changes');
|
||||
});
|
||||
});
|
||||
|
||||
describe('provisioning run scoping', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue