feat: presence on tickets
A ticket's page beats every 10 s (touchPresence / PUT
/issues/{issue}/presence, with whether I am editing) and gets back who
else is there, editing ones first. Entries live in Redis, one hash per
ticket, and fade after 30 s without a beat; leaving the page takes me out
at once. Only for people who can read the ticket; never in the event
store. The ticket shows «Bob está editando · Ana también lo está viendo»
above its title; editing means a ticket or comment form is open or a
comment is being written.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1700,4 +1700,59 @@ describe('task-stream (e2e)', () => {
|
||||
.summary,
|
||||
).toMatch(/tu equipo Frontend/);
|
||||
});
|
||||
|
||||
it('tells who else is on a ticket, and who is editing it', async () => {
|
||||
const key = 'P' + Date.now().toString(36).slice(-5).toUpperCase();
|
||||
await rest('token-ana', 'POST', '/projects', { key, name: key });
|
||||
await eventually(
|
||||
() => rest('token-ana', 'GET', `/projects/${key}`),
|
||||
(r) => r.status === 200,
|
||||
);
|
||||
await rest('token-ana', 'POST', `/projects/${key}/members`, {
|
||||
userId: '102',
|
||||
});
|
||||
const created = await rest('token-ana', 'POST', `/projects/${key}/issues`, {
|
||||
type: 'TASK',
|
||||
title: 'Aquí estamos',
|
||||
});
|
||||
const issue = await eventually(
|
||||
() => rest('token-bob', 'GET', `/issues/${created.body['key']}`),
|
||||
(r) => r.status === 200,
|
||||
);
|
||||
const id = issue.body['id'];
|
||||
|
||||
expect(
|
||||
(
|
||||
await rest('token-ana', 'PUT', `/issues/${id}/presence`, {
|
||||
editing: false,
|
||||
})
|
||||
).body,
|
||||
).toEqual([]);
|
||||
const { touchPresence } = await gql(
|
||||
'token-bob',
|
||||
`mutation($i: ID!) { touchPresence(issueId: $i, editing: true) { user { id name } editing } }`,
|
||||
{ i: id },
|
||||
);
|
||||
expect(touchPresence).toEqual([
|
||||
{ user: { id: '101', name: 'Ana (lead)' }, editing: false },
|
||||
]);
|
||||
const seen = await rest('token-ana', 'PUT', `/issues/${id}/presence`, {});
|
||||
expect(seen.body).toEqual([
|
||||
expect.objectContaining({
|
||||
user: { id: '102', name: 'Bob (frontend)' },
|
||||
editing: true,
|
||||
}),
|
||||
]);
|
||||
// Outsiders cannot even ask (a private ticket does not exist for them).
|
||||
expect(
|
||||
(await rest('token-eve', 'PUT', `/issues/${id}/presence`, {})).status,
|
||||
).toBe(404);
|
||||
|
||||
expect(
|
||||
(await rest('token-bob', 'DELETE', `/issues/${id}/presence`)).status,
|
||||
).toBe(204);
|
||||
expect(
|
||||
(await rest('token-ana', 'PUT', `/issues/${id}/presence`, {})).body,
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user