diff --git a/web/frontend/src/__tests__/completed-tasks-banner.test.ts b/web/frontend/src/__tests__/completed-tasks-banner.test.ts index a71c160..7b35975 100644 --- a/web/frontend/src/__tests__/completed-tasks-banner.test.ts +++ b/web/frontend/src/__tests__/completed-tasks-banner.test.ts @@ -19,8 +19,7 @@ vi.mock('vue-router', () => ({ vi.mock('../api', () => ({ api: { notifications: vi.fn(), - projects: vi.fn(), - project: vi.fn(), + listTasks: vi.fn(), reviseTask: vi.fn(), }, })) @@ -38,40 +37,6 @@ const localStorageMock = (() => { })() Object.defineProperty(globalThis, 'localStorage', { value: localStorageMock, configurable: true }) -function makeProject(id = 'proj-1', name = 'MyProject', doneCount = 1) { - return { - id, - name, - path: '/projects/test', - status: 'active', - priority: 1, - tech_stack: ['python'], - execution_mode: null, - autocommit_enabled: null, - auto_test_enabled: null, - worktrees_enabled: null, - obsidian_vault_path: null, - deploy_command: null, - test_command: null, - deploy_host: null, - deploy_path: null, - deploy_runtime: null, - deploy_restart_cmd: null, - created_at: '2024-01-01', - total_tasks: doneCount, - done_tasks: doneCount, - active_tasks: 0, - blocked_tasks: 0, - review_tasks: 0, - project_type: null, - ssh_host: null, - ssh_user: null, - ssh_key_path: null, - ssh_proxy_jump: null, - description: null, - } -} - function makeCompletedTask(id = 'TSK-1', title = 'Test task') { return { id, @@ -93,21 +58,13 @@ function makeCompletedTask(id = 'TSK-1', title = 'Test task') { } } -function makeProjectDetail( - project: ReturnType, - tasks: ReturnType[], -) { - return { ...project, tasks, modules: [], decisions: [] } -} - beforeEach(() => { localStorageMock.clear() vi.clearAllMocks() mockPush.mockClear() vi.useFakeTimers() vi.mocked(api.notifications).mockResolvedValue([]) - vi.mocked(api.projects).mockResolvedValue([]) - vi.mocked(api.project).mockResolvedValue(makeProjectDetail(makeProject(), [])) + vi.mocked(api.listTasks).mockResolvedValue([]) vi.mocked(api.reviseTask).mockResolvedValue({ status: 'ok', comment: '' }) }) @@ -116,13 +73,8 @@ afterEach(() => { vi.restoreAllMocks() }) -async function mountWithCompleted( - tasks = [makeCompletedTask()], - projectName = 'MyProject', -) { - const project = makeProject('proj-1', projectName) - vi.mocked(api.projects).mockResolvedValue([project]) - vi.mocked(api.project).mockResolvedValue(makeProjectDetail(project, tasks)) +async function mountWithCompleted(tasks = [makeCompletedTask()]) { + vi.mocked(api.listTasks).mockResolvedValue(tasks) const wrapper = mount(EscalationBanner) await flushPromises() return wrapper @@ -158,7 +110,7 @@ describe('KIN-125 AC1: завершённые задачи отображают }) it('Бейдж не отображается когда нет завершённых задач', async () => { - vi.mocked(api.projects).mockResolvedValue([makeProject('proj-1', 'P', 0)]) + vi.mocked(api.listTasks).mockResolvedValue([]) const wrapper = mount(EscalationBanner) await flushPromises() const badge = wrapper.findAll('button').find(b => b.text().includes('Completed')) diff --git a/web/frontend/src/api.ts b/web/frontend/src/api.ts index b446172..97c6fe5 100644 --- a/web/frontend/src/api.ts +++ b/web/frontend/src/api.ts @@ -410,6 +410,11 @@ export const api = { deleteAttachment: (taskId: string, id: number) => del(`/tasks/${taskId}/attachments/${id}`), attachmentUrl: (id: number) => `${BASE}/attachments/${id}/file`, + listTasks: (status?: string, limit = 20, sort = 'updated_at') => { + const q = new URLSearchParams({ limit: String(limit), sort }) + if (status) q.set('status', status) + return get(`/tasks?${q.toString()}`) + }, getPipelineLogs: (pipelineId: string, sinceId: number) => get(`/pipelines/${pipelineId}/logs?since_id=${sinceId}`), projectLinks: (projectId: string) => diff --git a/web/frontend/src/components/EscalationBanner.vue b/web/frontend/src/components/EscalationBanner.vue index 04a78cb..043f0ba 100644 --- a/web/frontend/src/components/EscalationBanner.vue +++ b/web/frontend/src/components/EscalationBanner.vue @@ -148,24 +148,8 @@ const visibleCompleted = computed(() => async function loadCompletedTasks() { try { - const projects = await api.projects() - const withCompleted = projects.filter(p => p.done_tasks > 0) - if (withCompleted.length === 0) { - completedTasks.value = [] - return - } - const details = await Promise.all(withCompleted.map(p => api.project(p.id))) - const results: CompletedTaskItem[] = [] - for (let i = 0; i < details.length; i++) { - const projectName = withCompleted[i].name - for (const task of details[i].tasks) { - if (task.status === 'completed') { - results.push({ ...task, project_name: projectName }) - } - } - } - results.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()) - completedTasks.value = results.slice(0, 20) + const tasks = await api.listTasks('completed', 20, 'updated_at') + completedTasks.value = tasks.map(t => ({ ...t, project_name: t.project_id })) } catch { // silent }