Skip to content

Commit dba082e

Browse files
authored
fix: show offline users' custom status text and expiration (#41087)
1 parent a4a1071 commit dba082e

5 files changed

Lines changed: 51 additions & 6 deletions

File tree

apps/meteor/app/api/server/v1/users.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1546,7 +1546,7 @@ API.v1.get(
15461546

15471547
if (ids) {
15481548
return API.v1.success({
1549-
users: await Users.findNotOfflineByIds(Array.isArray(ids) ? ids : ids.split(','), options).toArray(),
1549+
users: await Users.findPresenceUsersByIds(Array.isArray(ids) ? ids : ids.split(','), options).toArray(),
15501550
full: false,
15511551
});
15521552
}

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,6 +1848,34 @@ describe('[Users]', () => {
18481848
})
18491849
.end(done);
18501850
});
1851+
1852+
it('should return an offline user that still carries a custom status (vacation text/expiration survives offline)', async () => {
1853+
const vacationUser = await createUser();
1854+
const vacationCredentials = await login(vacationUser.username, password);
1855+
const expiresAt = new Date(Date.now() + 60 * 60 * 1000).toISOString();
1856+
1857+
await request
1858+
.post(api('users.setStatus'))
1859+
.set(vacationCredentials)
1860+
.send({ status: 'offline', message: 'On vacation', expiresAt })
1861+
.expect(200);
1862+
1863+
const res = await request
1864+
.get(api('users.presence'))
1865+
.query({ ids: vacationUser._id })
1866+
.set(credentials)
1867+
.expect('Content-Type', 'application/json')
1868+
.expect(200);
1869+
1870+
expect(res.body).to.have.property('success', true);
1871+
const returned = (res.body.users as IUser[]).find((u) => u._id === vacationUser._id);
1872+
expect(returned, 'offline user with a custom status must be returned by users.presence').to.not.be.undefined;
1873+
expect(returned).to.have.property('status', 'offline');
1874+
expect(returned).to.have.property('statusText', 'On vacation');
1875+
expect(returned).to.have.property('statusExpiresAt');
1876+
1877+
await deleteUser(vacationUser);
1878+
});
18511879
});
18521880
});
18531881

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,21 @@ describe('processPresence', () => {
4848
expect(result.values).toMatchObject({ status: UserStatus.OFFLINE, statusConnection: UserStatus.OFFLINE });
4949
});
5050

51+
test('should not clear a custom status when going OFFLINE (vacation text survives disconnect)', () => {
52+
const result = processPresence(
53+
user({
54+
statusDefault: UserStatus.AWAY,
55+
statusSource: 'manual',
56+
statusText: 'On vacation',
57+
statusExpiresAt: new Date(Date.now() + ONE_HOUR),
58+
}),
59+
[],
60+
);
61+
expect(result.values).toEqual({ status: UserStatus.OFFLINE, statusConnection: UserStatus.OFFLINE });
62+
expect(result.values).not.toHaveProperty('statusText');
63+
expect(result.clear).toBeUndefined();
64+
});
65+
5166
test('should stay OFFLINE when user is invisible (OFFLINE statusDefault with sessions)', () => {
5267
const result = processPresence(user({ statusDefault: UserStatus.OFFLINE }), [session(UserStatus.ONLINE)]);
5368
expect(result.values).toMatchObject({ status: UserStatus.OFFLINE, statusConnection: UserStatus.ONLINE });

packages/model-typings/src/models/IUsersModel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ export interface IUsersModel extends IBaseModel<IUser> {
381381
findOneActiveById(userId: string, options?: FindOptions<IUser>): Promise<IUser | null>;
382382
findOneByIdOrUsername(userId: string, options?: FindOptions<IUser>): Promise<IUser | null>;
383383
findOneByRolesAndType<T extends Document = IUser>(roles: IRole['_id'][], type: string, options?: FindOptions<IUser>): Promise<T | null>;
384-
findNotOfflineByIds(userIds: string[], options?: FindOptions<IUser>): FindCursor<IUser>;
384+
findPresenceUsersByIds(userIds: string[], options?: FindOptions<IUser>): FindCursor<IUser>;
385385
findUsersNotOffline(options?: FindOptions<IUser>): FindCursor<IUser>;
386386
countUsersNotOffline(options?: FindOptions<IUser>): Promise<number>;
387387
findNotIdUpdatedFrom(userId: string, updatedFrom: Date, options?: FindOptions<IUser>): FindCursor<IUser>;

packages/models/src/models/Users.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2488,12 +2488,14 @@ export class UsersRaw extends BaseRaw<IUser, DefaultFields<IUser>> implements IU
24882488
return this.findOne<T>(query, options);
24892489
}
24902490

2491-
findNotOfflineByIds(users?: IUser['_id'][], options?: FindOptions<IUser>) {
2491+
findPresenceUsersByIds(users: IUser['_id'][], options?: FindOptions<IUser>) {
24922492
const query = {
24932493
_id: { $in: users },
2494-
status: {
2495-
$in: [UserStatus.ONLINE, UserStatus.AWAY, UserStatus.BUSY],
2496-
},
2494+
$or: [
2495+
{ status: { $in: [UserStatus.ONLINE, UserStatus.AWAY, UserStatus.BUSY] } },
2496+
{ statusText: { $exists: true, $ne: '' } },
2497+
{ statusExpiresAt: { $exists: true } },
2498+
],
24972499
};
24982500
return this.find(query, options);
24992501
}

0 commit comments

Comments
 (0)