Skip to content

Commit 14f506e

Browse files
committed
refactor: change presence precedence model to internal > manual > external
1 parent 319fbf9 commit 14f506e

2 files changed

Lines changed: 56 additions & 86 deletions

File tree

ee/packages/presence/src/lib/presenceEngine.spec.ts

Lines changed: 55 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@ describe('presenceEngine', () => {
2222
describe('setActive', () => {
2323
describe('when a higher-priority source arrives', () => {
2424
it('should save current as previous and apply new', () => {
25-
const result = setActive(user({ statusDefault: UserStatus.BUSY, statusText: 'Deep work', statusSource: 'manual' }), {
25+
const result = setActive(user({ statusDefault: UserStatus.BUSY, statusText: 'Standup', statusSource: 'external' }), {
2626
statusDefault: UserStatus.BUSY,
27-
statusText: 'Standup',
28-
statusSource: 'external',
29-
statusEmoji: '📅',
27+
statusText: 'Deep work',
28+
statusSource: 'manual',
29+
statusEmoji: '🔒',
3030
});
3131

32-
expect(result?.values.statusSource).toBe('external');
33-
expect(result?.values.statusText).toBe('Standup');
34-
expect(result?.values.previousState).toMatchObject({ statusSource: 'manual', statusText: 'Deep work' });
32+
expect(result?.values.statusSource).toBe('manual');
33+
expect(result?.values.statusText).toBe('Deep work');
34+
expect(result?.values.previousState).toMatchObject({ statusSource: 'external', statusText: 'Standup' });
3535
});
3636

3737
it('should not save previous when there is no active claim', () => {
@@ -79,8 +79,8 @@ describe('presenceEngine', () => {
7979

8080
describe('when a lower-priority source arrives', () => {
8181
it('should save as previous when slot is empty (no visible change)', () => {
82-
const newState = { statusDefault: UserStatus.BUSY, statusText: 'Deep work', statusSource: 'manual' as const };
83-
const result = setActive(user({ statusSource: 'external', statusText: 'Standup', statusDefault: UserStatus.BUSY }), newState);
82+
const newState = { statusDefault: UserStatus.BUSY, statusText: 'Standup', statusSource: 'external' as const };
83+
const result = setActive(user({ statusSource: 'manual', statusText: 'Deep work', statusDefault: UserStatus.BUSY }), newState);
8484

8585
expect(result?.values).toEqual({ previousState: newState });
8686
});
@@ -111,12 +111,12 @@ describe('presenceEngine', () => {
111111
statusDefault: UserStatus.BUSY,
112112
previousState: {
113113
statusDefault: UserStatus.BUSY,
114-
statusText: 'Standup',
115-
statusSource: 'external',
114+
statusText: 'Deep work',
115+
statusSource: 'manual',
116116
statusExpiresAt: new Date(Date.now() + ONE_HOUR),
117117
},
118118
}),
119-
{ statusDefault: UserStatus.BUSY, statusText: 'Deep work', statusSource: 'manual' },
119+
{ statusDefault: UserStatus.BUSY, statusText: 'Standup', statusSource: 'external' },
120120
);
121121

122122
expect(result).toBeNull();
@@ -217,13 +217,13 @@ describe('presenceEngine', () => {
217217
it('when previous is valid, should restore it', () => {
218218
const result = endActive(
219219
user({
220-
statusSource: 'external',
220+
statusSource: 'manual',
221221
statusDefault: UserStatus.BUSY,
222-
previousState: { statusDefault: UserStatus.BUSY, statusText: 'Deep work', statusSource: 'manual' },
222+
previousState: { statusDefault: UserStatus.BUSY, statusText: 'Standup', statusSource: 'external' },
223223
}),
224224
);
225225

226-
expect(result.values).toMatchObject({ statusSource: 'manual', statusText: 'Deep work', status: UserStatus.BUSY });
226+
expect(result.values).toMatchObject({ statusSource: 'external', statusText: 'Standup', status: UserStatus.BUSY });
227227
expect(result.clear).toContain('previousState');
228228
expect(result.clear).toContain('statusEmoji');
229229
});
@@ -285,44 +285,44 @@ describe('presenceEngine', () => {
285285
});
286286

287287
describe('precedence flows', () => {
288-
it('when an external claim arrives over manual, should save manual as previous and restore it on end', () => {
289-
const setResult = setActive(user({ statusSource: 'manual', statusDefault: UserStatus.BUSY, statusText: 'Deep work' }), {
288+
it('when a manual claim arrives over external, should save external as previous and restore it on end', () => {
289+
const setResult = setActive(user({ statusSource: 'external', statusDefault: UserStatus.BUSY, statusText: 'Standup' }), {
290290
statusDefault: UserStatus.BUSY,
291-
statusText: 'Standup',
292-
statusSource: 'external',
291+
statusText: 'Deep work',
292+
statusSource: 'manual',
293293
});
294-
expect(setResult?.values.statusSource).toBe('external');
295-
expect(setResult?.values.previousState).toMatchObject({ statusSource: 'manual', statusText: 'Deep work' });
294+
expect(setResult?.values.statusSource).toBe('manual');
295+
expect(setResult?.values.previousState).toMatchObject({ statusSource: 'external', statusText: 'Standup' });
296296

297297
const endResult = endActive(
298298
user({
299-
statusSource: 'external',
299+
statusSource: 'manual',
300300
statusDefault: UserStatus.BUSY,
301-
previousState: { statusDefault: UserStatus.BUSY, statusText: 'Deep work', statusSource: 'manual' },
301+
previousState: { statusDefault: UserStatus.BUSY, statusText: 'Standup', statusSource: 'external' },
302302
}),
303303
);
304-
expect(endResult.values).toMatchObject({ statusSource: 'manual', statusText: 'Deep work' });
304+
expect(endResult.values).toMatchObject({ statusSource: 'external', statusText: 'Standup' });
305305
expect(endResult.clear).toContain('previousState');
306306
});
307307

308-
it('when a manual claim arrives during active external, should queue it as previous and restore on end', () => {
309-
const setResult = setActive(user({ statusSource: 'external', statusDefault: UserStatus.BUSY, statusText: 'Standup' }), {
310-
statusDefault: UserStatus.AWAY,
311-
statusText: 'Lunch',
312-
statusSource: 'manual',
308+
it('when an external claim arrives during active manual, should queue it as previous and restore on end', () => {
309+
const setResult = setActive(user({ statusSource: 'manual', statusDefault: UserStatus.BUSY, statusText: 'Deep work' }), {
310+
statusDefault: UserStatus.BUSY,
311+
statusText: 'Standup',
312+
statusSource: 'external',
313313
});
314314
expect(setResult?.values).toEqual({
315-
previousState: { statusDefault: UserStatus.AWAY, statusText: 'Lunch', statusSource: 'manual' },
315+
previousState: { statusDefault: UserStatus.BUSY, statusText: 'Standup', statusSource: 'external' },
316316
});
317317

318318
const endResult = endActive(
319319
user({
320-
statusSource: 'external',
320+
statusSource: 'manual',
321321
statusDefault: UserStatus.BUSY,
322-
previousState: { statusDefault: UserStatus.AWAY, statusText: 'Lunch', statusSource: 'manual' },
322+
previousState: { statusDefault: UserStatus.BUSY, statusText: 'Standup', statusSource: 'external' },
323323
}),
324324
);
325-
expect(endResult.values).toMatchObject({ statusSource: 'manual', statusText: 'Lunch', statusDefault: UserStatus.AWAY });
325+
expect(endResult.values).toMatchObject({ statusSource: 'external', statusText: 'Standup', statusDefault: UserStatus.BUSY });
326326
});
327327

328328
it('when an external claim arrives during active internal, should queue it, restore on end, then reset to system', () => {
@@ -376,30 +376,6 @@ describe('presenceEngine', () => {
376376
expect(endResult.clear).not.toContain('statusEmoji');
377377
});
378378

379-
it('when a new manual claim arrives during active internal, should replace existing manual in previous and restore it on end', () => {
380-
const setResult = setActive(
381-
user({
382-
statusSource: 'internal',
383-
statusDefault: UserStatus.BUSY,
384-
statusText: 'On a call',
385-
previousState: { statusDefault: UserStatus.BUSY, statusText: 'WFH', statusSource: 'manual' },
386-
}),
387-
{ statusDefault: UserStatus.AWAY, statusText: 'Lunch', statusSource: 'manual' },
388-
);
389-
expect(setResult?.values).toEqual({
390-
previousState: { statusDefault: UserStatus.AWAY, statusText: 'Lunch', statusSource: 'manual' },
391-
});
392-
393-
const endResult = endActive(
394-
user({
395-
statusSource: 'internal',
396-
statusDefault: UserStatus.BUSY,
397-
previousState: { statusDefault: UserStatus.AWAY, statusText: 'Lunch', statusSource: 'manual' },
398-
}),
399-
);
400-
expect(endResult.values).toMatchObject({ statusSource: 'manual', statusText: 'Lunch' });
401-
});
402-
403379
it('when previous has expired while internal is active, should ignore previous and reset to system on end', () => {
404380
const result = endActive(
405381
user({
@@ -418,46 +394,40 @@ describe('presenceEngine', () => {
418394
expect(result.clear).toContain('previousState');
419395
});
420396

421-
it('when internal arrives over external that has manual in previous, should replace manual with external (1-slot limit)', () => {
397+
it('when a same-priority manual arrives over active manual, should overwrite active and keep previous untouched', () => {
422398
const result = setActive(
423399
user({
424-
statusSource: 'external',
400+
statusSource: 'manual',
425401
statusDefault: UserStatus.BUSY,
426-
statusText: 'Standup',
427-
statusExpiresAt: new Date(Date.now() + ONE_HOUR),
428-
previousState: { statusDefault: UserStatus.BUSY, statusText: 'WFH', statusSource: 'manual' },
402+
statusText: 'WFH',
403+
previousState: { statusDefault: UserStatus.BUSY, statusText: 'Standup', statusSource: 'external' },
429404
}),
430-
{ statusDefault: UserStatus.BUSY, statusText: 'On a call', statusSource: 'internal', statusEmoji: '📞' },
405+
{ statusDefault: UserStatus.AWAY, statusText: 'Lunch', statusSource: 'manual' },
431406
);
432407

433-
expect(result?.values.statusSource).toBe('internal');
434-
expect(result?.values.previousState).toMatchObject({ statusSource: 'external', statusText: 'Standup' });
408+
expect(result?.values.statusSource).toBe('manual');
409+
expect(result?.values.statusText).toBe('Lunch');
410+
expect(result?.values.previousState).toBeUndefined();
435411
});
436412

437-
it('when a same-priority external arrives over active external, should overwrite active and keep previous untouched', () => {
438-
const result = setActive(
413+
it('when an internal claim arrives over manual, should save manual as previous and restore it on end', () => {
414+
const setResult = setActive(user({ statusSource: 'manual', statusDefault: UserStatus.BUSY, statusText: 'Deep work' }), {
415+
statusDefault: UserStatus.BUSY,
416+
statusText: 'On a call',
417+
statusSource: 'internal',
418+
});
419+
expect(setResult?.values.statusSource).toBe('internal');
420+
expect(setResult?.values.previousState).toMatchObject({ statusSource: 'manual', statusText: 'Deep work' });
421+
422+
const endResult = endActive(
439423
user({
440-
statusSource: 'external',
424+
statusSource: 'internal',
441425
statusDefault: UserStatus.BUSY,
442-
statusText: 'Standup',
443-
previousState: { statusDefault: UserStatus.BUSY, statusText: 'WFH', statusSource: 'manual' },
426+
previousState: { statusDefault: UserStatus.BUSY, statusText: 'Deep work', statusSource: 'manual' },
444427
}),
445-
{ statusDefault: UserStatus.BUSY, statusText: '1:1', statusSource: 'external' },
446-
);
447-
448-
expect(result?.values.statusSource).toBe('external');
449-
expect(result?.values.statusText).toBe('1:1');
450-
expect(result?.values.previousState).toBeUndefined();
451-
});
452-
453-
it('when user is auto-away with manual active, should still accept a higher-priority external claim', () => {
454-
const result = setActive(
455-
user({ statusSource: 'manual', statusDefault: UserStatus.BUSY, statusText: 'Deep work', statusConnection: UserStatus.AWAY }),
456-
{ statusDefault: UserStatus.BUSY, statusText: 'Standup', statusSource: 'external' },
457428
);
458-
459-
expect(result?.values.statusSource).toBe('external');
460-
expect(result?.values.previousState).toMatchObject({ statusSource: 'manual', statusText: 'Deep work' });
429+
expect(endResult.values).toMatchObject({ statusSource: 'manual', statusText: 'Deep work' });
430+
expect(endResult.clear).toContain('previousState');
461431
});
462432
});
463433
});

ee/packages/presence/src/lib/presenceEngine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type IUser, UserStatus } from '@rocket.chat/core-typings';
22

3-
const PRIORITY = { internal: 1, external: 2, manual: 3 };
3+
const PRIORITY = { internal: 1, manual: 2, external: 3 };
44

55
const NO_PRIORITY = 4;
66

0 commit comments

Comments
 (0)