feat: portfolio of all my projects
/cartera (and GraphQL portfolio, REST /api/me/portfolio, MCP get_portfolio) shows every active project I belong to (not archived, not as a guest) at a glance: open, overdue, blocked and stuck tickets, the sprint under way and whether it gets there, planned releases with their forecast, and the open epics with their progress. It reuses the dashboard's computations. A release with no tickets now reads «Sin tickets» instead of «Todo hecho», here and on the dashboard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1852,4 +1852,56 @@ describe('task-stream (e2e)', () => {
|
||||
(r) => r.status === 200,
|
||||
);
|
||||
});
|
||||
|
||||
it('shows my active projects at a glance (the portfolio)', async () => {
|
||||
const key = 'F' + Date.now().toString(36).slice(-5).toUpperCase();
|
||||
await rest('token-ana', 'POST', '/projects', {
|
||||
key,
|
||||
name: `Cartera ${key}`,
|
||||
});
|
||||
await eventually(
|
||||
() => rest('token-ana', 'GET', `/projects/${key}`),
|
||||
(r) => r.status === 200,
|
||||
);
|
||||
const epic = (
|
||||
await rest('token-ana', 'POST', `/projects/${key}/issues`, {
|
||||
type: 'FEATURE',
|
||||
title: 'Pagos',
|
||||
dueDate: '2030-01-01',
|
||||
})
|
||||
).body['key'];
|
||||
await eventually(
|
||||
() => rest('token-ana', 'GET', `/issues/${epic}`),
|
||||
(r) => r.status === 200,
|
||||
);
|
||||
await rest('token-ana', 'POST', `/projects/${key}/issues`, {
|
||||
type: 'TASK',
|
||||
title: 'Parte',
|
||||
parent: epic,
|
||||
dueDate: '2020-01-01',
|
||||
});
|
||||
await rest('token-ana', 'POST', `/projects/${key}/releases`, {
|
||||
name: '1.0',
|
||||
targetDate: '2030-06-01',
|
||||
});
|
||||
const mine = await eventually(
|
||||
() => rest('token-ana', 'GET', '/me/portfolio'),
|
||||
(r) =>
|
||||
r.body?.find?.((p: { key: string }) => p.key === key)?.epics?.length ===
|
||||
1 &&
|
||||
r.body.find((p: { key: string }) => p.key === key).releases.length ===
|
||||
1,
|
||||
);
|
||||
const row = mine.body.find((p: { key: string }) => p.key === key);
|
||||
expect(row).toMatchObject({ total: 2, open: 2, overdue: 1, sprint: null });
|
||||
expect(row.releases[0]).toMatchObject({ name: '1.0', total: 0 });
|
||||
expect(row.epics[0].issue.key).toBe(epic);
|
||||
|
||||
// Archived projects leave it.
|
||||
await rest('token-ana', 'POST', `/projects/${key}/archive`);
|
||||
await eventually(
|
||||
() => gql('token-ana', `query { portfolio { key } }`),
|
||||
(d) => !d['portfolio'].some((p: { key: string }) => p.key === key),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user