Skip to content

Commit 6838969

Browse files
authored
Stabilise user profile timezones (#30815)
* Fix imports * lint * update test * log * Update comment
1 parent 2698ad4 commit 6838969

File tree

5 files changed

+71
-38
lines changed

5 files changed

+71
-38
lines changed

src/components/structures/LoggedInView.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
type SyncStateData,
1818
SyncState,
1919
EventType,
20+
ProfileKeyTimezone,
21+
ProfileKeyMSC4175Timezone,
2022
} from "matrix-js-sdk/src/matrix";
2123
import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
2224
import classNames from "classnames";
@@ -197,10 +199,12 @@ class LoggedInView extends React.Component<IProps, IState> {
197199
}
198200

199201
private onTimezoneUpdate = async (): Promise<void> => {
202+
// TODO: In a future app release, remove support for legacy key.
200203
if (!SettingsStore.getValue("userTimezonePublish")) {
201204
// Ensure it's deleted
202205
try {
203-
await this._matrixClient.deleteExtendedProfileProperty("us.cloke.msc4175.tz");
206+
await this._matrixClient.deleteExtendedProfileProperty(ProfileKeyMSC4175Timezone);
207+
await this._matrixClient.deleteExtendedProfileProperty(ProfileKeyTimezone);
204208
} catch (ex) {
205209
console.warn("Failed to delete timezone from user profile", ex);
206210
}
@@ -215,7 +219,8 @@ class LoggedInView extends React.Component<IProps, IState> {
215219
return;
216220
}
217221
try {
218-
await this._matrixClient.setExtendedProfileProperty("us.cloke.msc4175.tz", currentTimezone);
222+
await this._matrixClient.setExtendedProfileProperty(ProfileKeyTimezone, currentTimezone);
223+
await this._matrixClient.setExtendedProfileProperty(ProfileKeyMSC4175Timezone, currentTimezone);
219224
} catch (ex) {
220225
console.warn("Failed to update user profile with current timezone", ex);
221226
}

src/hooks/useUserTimezone.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
55
Please see LICENSE files in the repository root for full details.
66
*/
77
import { useEffect, useState } from "react";
8-
import { type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
8+
import {
9+
type MatrixClient,
10+
MatrixError,
11+
ProfileKeyMSC4175Timezone,
12+
ProfileKeyTimezone,
13+
} from "matrix-js-sdk/src/matrix";
14+
import { logger } from "matrix-js-sdk/src/logger";
915

1016
import { getTwelveHourOptions } from "../DateUtils.ts";
1117
import { useSettingValue } from "./useSettings.ts";
1218

19+
const log = logger.getChild("useUserTimezone");
20+
1321
/**
1422
* Fetch a user's delclared timezone through their profile, and return
1523
* a friendly string of the current time for that user. This will keep
@@ -52,11 +60,13 @@ export const useUserTimezone = (cli: MatrixClient, userId: string): { timezone:
5260
return;
5361
}
5462
(async () => {
55-
console.log("Trying to fetch TZ");
63+
log.debug("Trying to fetch TZ for", userId);
5664
try {
57-
const tz = await cli.getExtendedProfileProperty(userId, "us.cloke.msc4175.tz");
65+
const userProfile = await cli.getExtendedProfile(userId);
66+
// In a future spec release, remove support for legacy key.
67+
const tz = userProfile[ProfileKeyTimezone] ?? userProfile[ProfileKeyMSC4175Timezone];
5868
if (typeof tz !== "string") {
59-
// Err, definitely not a tz.
69+
// Definitely not a tz.
6070
throw Error("Timezone value was not a string");
6171
}
6272
// This will validate the timezone for us.
@@ -85,7 +95,7 @@ export const useUserTimezone = (cli: MatrixClient, userId: string): { timezone:
8595
// No timezone set, ignore.
8696
return;
8797
}
88-
console.error("Could not render current timezone for user", ex);
98+
log.warn(`Could not render current timezone for ${userId}`, ex);
8999
}
90100
})();
91101
}, [supported, userId, cli, showTwelveHour]);

test/unit-tests/components/structures/LoggedInView-test.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
MatrixEvent,
1616
ClientEvent,
1717
PushRuleKind,
18+
ProfileKeyTimezone,
19+
ProfileKeyMSC4175Timezone,
1820
} from "matrix-js-sdk/src/matrix";
1921
import { MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler";
2022
import { logger } from "matrix-js-sdk/src/logger";
@@ -470,30 +472,36 @@ describe("<LoggedInView />", () => {
470472
it("does not update the timezone when userTimezonePublish is off", async () => {
471473
getComponent();
472474
await SettingsStore.setValue("userTimezonePublish", null, SettingLevel.DEVICE, false);
473-
expect(mockClient.deleteExtendedProfileProperty).toHaveBeenCalledWith("us.cloke.msc4175.tz");
475+
expect(mockClient.deleteExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyTimezone);
476+
expect(mockClient.deleteExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyMSC4175Timezone);
474477
expect(mockClient.setExtendedProfileProperty).not.toHaveBeenCalled();
475478
});
476479
it("should set the user timezone when userTimezonePublish is enabled", async () => {
477480
getComponent();
478481
await SettingsStore.setValue("userTimezonePublish", null, SettingLevel.DEVICE, true);
479-
expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith("us.cloke.msc4175.tz", userTimezone);
482+
expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyTimezone, userTimezone);
483+
expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyMSC4175Timezone, userTimezone);
480484
});
481485

482486
it("should set the user timezone when the timezone is changed", async () => {
483487
const newTimezone = "Europe/Paris";
484488
getComponent();
485489
await SettingsStore.setValue("userTimezonePublish", null, SettingLevel.DEVICE, true);
486-
expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith("us.cloke.msc4175.tz", userTimezone);
490+
expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyTimezone, userTimezone);
491+
expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyMSC4175Timezone, userTimezone);
487492
await SettingsStore.setValue("userTimezone", null, SettingLevel.DEVICE, newTimezone);
488-
expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith("us.cloke.msc4175.tz", newTimezone);
493+
expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyTimezone, newTimezone);
494+
expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyMSC4175Timezone, newTimezone);
489495
});
490496

491497
it("should clear the timezone when the publish feature is turned off", async () => {
492498
getComponent();
493499
await SettingsStore.setValue("userTimezonePublish", null, SettingLevel.DEVICE, true);
494-
expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith("us.cloke.msc4175.tz", userTimezone);
500+
expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyTimezone, userTimezone);
501+
expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyMSC4175Timezone, userTimezone);
495502
await SettingsStore.setValue("userTimezonePublish", null, SettingLevel.DEVICE, false);
496-
expect(mockClient.deleteExtendedProfileProperty).toHaveBeenCalledWith("us.cloke.msc4175.tz");
503+
expect(mockClient.deleteExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyTimezone);
504+
expect(mockClient.deleteExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyMSC4175Timezone);
497505
});
498506
});
499507

test/unit-tests/components/views/right_panel/UserInfo-test.tsx

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ import React from "react";
1010
import { render, screen, act, waitForElementToBeRemoved } from "jest-matrix-react";
1111
import userEvent from "@testing-library/user-event";
1212
import { type Mocked, mocked } from "jest-mock";
13-
import { type Room, User, type MatrixClient, RoomMember, Device } from "matrix-js-sdk/src/matrix";
13+
import {
14+
type Room,
15+
User,
16+
type MatrixClient,
17+
RoomMember,
18+
Device,
19+
ProfileKeyTimezone,
20+
ProfileKeyMSC4175Timezone,
21+
} from "matrix-js-sdk/src/matrix";
1422
import { EventEmitter } from "events";
1523
import {
1624
UserVerificationStatus,
@@ -120,7 +128,7 @@ beforeEach(() => {
120128
isSynapseAdministrator: jest.fn().mockResolvedValue(false),
121129
doesServerSupportUnstableFeature: jest.fn().mockReturnValue(false),
122130
doesServerSupportExtendedProfiles: jest.fn().mockResolvedValue(false),
123-
getExtendedProfileProperty: jest.fn().mockRejectedValue(new Error("Not supported")),
131+
getExtendedProfile: jest.fn().mockRejectedValue(new Error("Not supported")),
124132
mxcUrlToHttp: jest.fn().mockReturnValue("mock-mxcUrlToHttp"),
125133
removeListener: jest.fn(),
126134
currentState: {
@@ -199,29 +207,31 @@ describe("<UserInfo />", () => {
199207
expect(screen.getByRole("heading", { name: defaultUserId })).toBeInTheDocument();
200208
});
201209

202-
it("renders user timezone if set", async () => {
203-
// For timezone, force a consistent locale.
204-
jest.spyOn(global.Date.prototype, "toLocaleString").mockImplementation(function (
205-
this: Date,
206-
_locale,
207-
opts,
208-
) {
209-
return origDate.call(this, "en-US", {
210-
...opts,
211-
hourCycle: "h12",
210+
describe.each([[ProfileKeyTimezone], [ProfileKeyMSC4175Timezone]])("timezone rendering (%s)", (profileKey) => {
211+
it("renders user timezone if set", async () => {
212+
// For timezone, force a consistent locale.
213+
jest.spyOn(global.Date.prototype, "toLocaleString").mockImplementation(function (
214+
this: Date,
215+
_locale,
216+
opts,
217+
) {
218+
return origDate.call(this, "en-US", {
219+
...opts,
220+
hourCycle: "h12",
221+
});
212222
});
223+
mockClient.doesServerSupportExtendedProfiles.mockResolvedValue(true);
224+
mockClient.getExtendedProfile.mockResolvedValue({ [profileKey]: "Europe/London" });
225+
renderComponent();
226+
await expect(screen.findByText(/\d\d:\d\d (AM|PM)/)).resolves.toBeInTheDocument();
213227
});
214-
mockClient.doesServerSupportExtendedProfiles.mockResolvedValue(true);
215-
mockClient.getExtendedProfileProperty.mockResolvedValue("Europe/London");
216-
renderComponent();
217-
await expect(screen.findByText(/\d\d:\d\d (AM|PM)/)).resolves.toBeInTheDocument();
218-
});
219228

220-
it("does not renders user timezone if timezone is invalid", async () => {
221-
mockClient.doesServerSupportExtendedProfiles.mockResolvedValue(true);
222-
mockClient.getExtendedProfileProperty.mockResolvedValue("invalid-tz");
223-
renderComponent();
224-
expect(screen.queryByText(/\d\d:\d\d (AM|PM)/)).not.toBeInTheDocument();
229+
it("does not renders user timezone if timezone is invalid", async () => {
230+
mockClient.doesServerSupportExtendedProfiles.mockResolvedValue(true);
231+
mockClient.getExtendedProfile.mockResolvedValue({ [profileKey]: "invalid-tz" });
232+
renderComponent();
233+
expect(screen.queryByText(/\d\d:\d\d (AM|PM)/)).not.toBeInTheDocument();
234+
});
225235
});
226236

227237
it("renders encryption info panel without pending verification", () => {

test/unit-tests/components/views/right_panel/__snapshots__/UserInfo-test.tsx.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
44
<div>
@@ -19,7 +19,7 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
1919
</p>
2020
</div>
2121
<button
22-
aria-labelledby="«r6i»"
22+
aria-labelledby="«r7c»"
2323
class="_icon-button_1pz9o_8"
2424
data-kind="secondary"
2525
data-testid="base-card-close-button"
@@ -306,7 +306,7 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
306306
</p>
307307
</div>
308308
<button
309-
aria-labelledby="«r6s»"
309+
aria-labelledby="«r7m»"
310310
class="_icon-button_1pz9o_8"
311311
data-kind="secondary"
312312
data-testid="base-card-close-button"

0 commit comments

Comments
 (0)