Skip to content

Commit b29fb86

Browse files
authored
fix: Calendar status event status switch (#39491)
1 parent e206889 commit b29fb86

4 files changed

Lines changed: 89 additions & 27 deletions

File tree

.changeset/red-windows-breathe.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@rocket.chat/meteor": patch
3+
---
4+
5+
Fixes calendar events modifying the wrong status property when attempting to sync `busy` status.

apps/meteor/server/services/calendar/service.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -285,15 +285,15 @@ export class CalendarService extends ServiceClassInternal implements ICalendarSe
285285
return;
286286
}
287287

288-
const user = await Users.findOneById(event.uid, { projection: { status: 1 } });
289-
if (!user || user.status === UserStatus.OFFLINE) {
288+
const user = await Users.findOneById(event.uid, { projection: { statusDefault: 1 } });
289+
if (!user || user.statusDefault === UserStatus.OFFLINE) {
290290
return;
291291
}
292292

293293
const overlappingEvents = await CalendarEvent.findOverlappingEvents(event._id, event.uid, event.startTime, event.endTime)
294294
.sort({ startTime: -1 })
295295
.toArray();
296-
const previousStatus = overlappingEvents.at(0)?.previousStatus ?? user.status;
296+
const previousStatus = overlappingEvents.at(0)?.previousStatus ?? user.statusDefault;
297297

298298
if (previousStatus) {
299299
await CalendarEvent.updateEvent(event._id, { previousStatus });
@@ -313,16 +313,16 @@ export class CalendarService extends ServiceClassInternal implements ICalendarSe
313313
return;
314314
}
315315

316-
const user = await Users.findOneById(event.uid, { projection: { status: 1 } });
316+
const user = await Users.findOneById(event.uid, { projection: { statusDefault: 1 } });
317317
if (!user) {
318318
return;
319319
}
320320

321321
// Only restore status if:
322-
// 1. The current status is BUSY (meaning it was set by our system, not manually changed by user)
322+
// 1. The current statusDefault is BUSY (meaning it was set by our system, not manually changed by user)
323323
// 2. We have a previousStatus stored from before the event started
324324

325-
if (user.status === UserStatus.BUSY && event.previousStatus && event.previousStatus !== user.status) {
325+
if (user.statusDefault === UserStatus.BUSY && event.previousStatus && event.previousStatus !== user.statusDefault) {
326326
await applyStatusChange({
327327
eventId: event._id,
328328
uid: event.uid,
@@ -334,7 +334,7 @@ export class CalendarService extends ServiceClassInternal implements ICalendarSe
334334
logger.debug({
335335
msg: 'Not restoring status for user',
336336
userId: event.uid,
337-
currentStatus: user.status,
337+
currentStatusDefault: user.statusDefault,
338338
previousStatus: event.previousStatus,
339339
});
340340
}

apps/meteor/server/services/calendar/statusEvents/applyStatusChange.ts

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,51 @@ export async function applyStatusChange({
2020
status?: UserStatus;
2121
shouldScheduleRemoval?: boolean;
2222
}): Promise<void> {
23+
const user = await Users.findOneById(uid, { projection: { roles: 1, username: 1, name: 1, statusDefault: 1 } });
24+
25+
if (!user || user.statusDefault === UserStatus.OFFLINE) {
26+
logger.debug({
27+
msg: 'Cannot apply status change for event, user is offline or does not exist',
28+
eventId,
29+
uid,
30+
});
31+
32+
return;
33+
}
34+
35+
const newStatus = status ?? UserStatus.BUSY;
36+
const previousStatus = user.statusDefault;
37+
2338
logger.debug({
2439
msg: 'Applying status change for event',
2540
eventId,
26-
uid,
41+
user: { _id: uid, statusDefault: user?.statusDefault },
2742
startTime,
2843
endTime,
29-
status: status ?? UserStatus.BUSY,
44+
newStatus,
45+
previousStatus,
3046
});
3147

32-
const user = await Users.findOneById(uid, { projection: { roles: 1, username: 1, name: 1, status: 1 } });
33-
if (!user || user.status === UserStatus.OFFLINE) {
34-
return;
48+
let statusChanged = false;
49+
50+
if (newStatus === UserStatus.BUSY) {
51+
await Users.updateStatusAndStatusDefault(uid, newStatus, newStatus);
52+
statusChanged = true;
53+
} else if (user.statusDefault === UserStatus.BUSY) {
54+
await Users.updateStatusAndStatusDefault(uid, newStatus, newStatus);
55+
statusChanged = true;
3556
}
3657

37-
const newStatus = status ?? UserStatus.BUSY;
38-
const previousStatus = user.status;
39-
40-
await Users.updateStatusAndStatusDefault(uid, newStatus, newStatus);
41-
42-
await api.broadcast('presence.status', {
43-
user: {
44-
status: newStatus,
45-
_id: uid,
46-
roles: user.roles,
47-
username: user.username,
48-
name: user.name,
49-
},
50-
previousStatus,
51-
});
58+
if (statusChanged) {
59+
await api.broadcast('presence.status', {
60+
user: {
61+
status: newStatus,
62+
_id: uid,
63+
roles: user.roles,
64+
username: user.username,
65+
name: user.name,
66+
},
67+
previousStatus,
68+
});
69+
}
5270
}

apps/meteor/tests/end-to-end/api/calendar.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import { expect } from 'chai';
44
import { after, before, describe, it } from 'mocha';
55
import type { Response } from 'supertest';
66

7+
import { sleep } from '../../../lib/utils/sleep';
78
import { getCredentials, api, request, credentials } from '../../data/api-data';
89
import { password } from '../../data/user';
910
import { createUser, deleteUser, login } from '../../data/users.helper';
11+
import { IS_EE } from '../../e2e/config/constants';
1012

1113
describe('[Calendar Events]', () => {
1214
let user2: IUser;
@@ -663,4 +665,41 @@ describe('[Calendar Events]', () => {
663665
.expect(400);
664666
});
665667
});
668+
669+
(IS_EE ? describe : describe.skip)('[Calendar Events Status Sync]', () => {
670+
before(async () => {
671+
await request.post('/api/v1/users.setStatus').set(userCredentials).send({ status: 'away' }).expect(200);
672+
});
673+
674+
it('should set user status to busy during event and restore manual status after event ends', async () => {
675+
const now = new Date();
676+
const startTime = new Date(now.getTime() + 1000);
677+
// Event cannot be less than 5 secs in duration, otherwise `processStatusChangesAtTime` would trigger both start/end status changes at the same time, due to the 5s offset
678+
const endTime = new Date(startTime.getTime() + 6000);
679+
680+
const eventPayload = {
681+
startTime: startTime.toISOString(),
682+
endTime: endTime.toISOString(),
683+
subject: 'Subject',
684+
description: 'Description',
685+
reminderMinutesBeforeStart: 1,
686+
busy: true,
687+
};
688+
689+
const createResponse = await request.post('/api/v1/calendar-events.create').set(userCredentials).send(eventPayload).expect(200);
690+
const eventId = createResponse.body.id;
691+
692+
await sleep(3000);
693+
694+
const statusResponseDuring = await request.get('/api/v1/users.getStatus').set(userCredentials).expect(200);
695+
expect(statusResponseDuring.body.status).to.equal('busy');
696+
697+
await sleep(5000);
698+
699+
const statusResponseAfter = await request.get('/api/v1/users.getStatus').set(userCredentials).expect(200);
700+
expect(statusResponseAfter.body.status).to.equal('away');
701+
702+
await request.post('/api/v1/calendar-events.delete').set(userCredentials).send({ eventId }).expect(200);
703+
});
704+
});
666705
});

0 commit comments

Comments
 (0)