Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Notifications: inline error message on notifications saving error #10288

Merged
merged 35 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6f31ac0
basic sync setup
Feb 28, 2023
2f99655
formatting
Feb 28, 2023
a993092
Merge branch 'develop' into psg-955/polls-push-sync
Feb 28, 2023
cfac7f7
get loudest value for synced rules
Mar 1, 2023
ca49e31
more types
Mar 1, 2023
19dbc95
test synced rules in notifications settings
Mar 2, 2023
be25984
Merge branch 'develop' into psg-955/polls-push-sync
Mar 2, 2023
e69c65a
type fixes
Mar 2, 2023
8c58724
noimplicitany fixes
Mar 2, 2023
6c8994f
remove debug
Mar 2, 2023
4188cb0
tidying
Mar 2, 2023
7d5f800
extract updatePushRuleActions fn to utils
Mar 3, 2023
5e752d0
extract update synced rules
Mar 3, 2023
8d39557
just synchronise in one place?
Mar 3, 2023
4602ed4
monitor account data changes AND trigger changes sync in notification…
Mar 5, 2023
0af76dd
Merge branch 'develop' into psg-1135/polls-push-sync-on-start
Mar 5, 2023
1a8b003
lint
Mar 5, 2023
0e2eb9e
setup LoggedInView test with enough mocks
Mar 5, 2023
55bb323
test rule syncing in LoggedInView
Mar 6, 2023
2443dd4
strict fixes
Mar 6, 2023
c994972
more comments
Mar 8, 2023
5afce3f
one more comment
Mar 8, 2023
7d553dc
add error variant for caption component
Mar 6, 2023
30a3595
tests for new error message
Mar 9, 2023
2eec3bb
tweak styles
Mar 9, 2023
e1247cd
Merge branch 'develop' into psg-1126/notifications-change-error-message
Mar 9, 2023
2ec92bc
Merge branch 'develop' into psg-1126/notifications-change-error-message
Mar 9, 2023
cc750fc
noImplicitAny
Mar 9, 2023
caa05c5
revert out of date prettier changes to unrelated files
Mar 9, 2023
c634f4e
limit inline message to radios only, tests
Mar 9, 2023
443eb98
Merge branch 'develop' into psg-1126/notifications-change-error-message
Mar 9, 2023
287aa2a
strict fix
Mar 9, 2023
1d7da11
Merge branch 'psg-1126/notifications-change-error-message' of https:/…
Mar 9, 2023
7164315
Merge branch 'develop' into psg-1126/notifications-change-error-message
Mar 13, 2023
2b16349
Merge branch 'develop' into psg-1126/notifications-change-error-message
Mar 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions res/css/components/views/typography/_Caption.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ limitations under the License.
.mx_Caption {
font-size: $font-12px;
color: $secondary-content;

&.mx_Caption_error {
color: $alert;
}
}
8 changes: 8 additions & 0 deletions res/css/views/settings/_Notifications.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ limitations under the License.
font-size: $font-12px;
font-weight: $font-semi-bold;
}
.mx_UserNotifSettings_gridRowError {
// occupy full row
grid-column: 1/-1;
justify-self: start;
padding-right: 30%;
// collapse half of the grid-gap
margin-top: -$spacing-4;
}

.mx_UserNotifSettings {
color: $primary-content; /* override from default settings page styles */
Expand Down
65 changes: 46 additions & 19 deletions src/components/views/settings/Notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
updateExistingPushRulesWithActions,
updatePushRuleActions,
} from "../../../utils/pushRules/updatePushRuleActions";
import { Caption } from "../typography/Caption";

// TODO: this "view" component still has far too much application logic in it,
// which should be factored out to other files.
Expand All @@ -55,7 +56,10 @@ enum Phase {
Loading = "loading",
Ready = "ready",
Persisting = "persisting", // technically a meta-state for Ready, but whatever
// unrecoverable error - eg can't load push rules
Error = "error",
// error saving individual rule
SavingError = "savingError",
}

enum RuleClass {
Expand Down Expand Up @@ -121,6 +125,8 @@ interface IState {
audioNotifications: boolean;

clearingNotifications: boolean;

ruleIdsWithError: Record<RuleId | string, boolean>;
}
const findInDefaultRules = (
ruleId: RuleId | string,
Expand Down Expand Up @@ -194,6 +200,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
desktopShowBody: SettingsStore.getValue("notificationBodyEnabled"),
audioNotifications: SettingsStore.getValue("audioNotificationsEnabled"),
clearingNotifications: false,
ruleIdsWithError: {},
};

this.settingWatchers = [
Expand Down Expand Up @@ -243,13 +250,9 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
).reduce((p, c) => Object.assign(c, p), {});

this.setState<
keyof Omit<
keyof Pick<
IState,
| "deviceNotificationsEnabled"
| "desktopNotifications"
| "desktopShowBody"
| "audioNotifications"
| "clearingNotifications"
"phase" | "vectorKeywordRuleInfo" | "vectorPushRules" | "pushers" | "threepids" | "masterPushRule"
>
>({
...newState,
Expand Down Expand Up @@ -393,8 +396,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
private onMasterRuleChanged = async (checked: boolean): Promise<void> => {
this.setState({ phase: Phase.Persisting });

const masterRule = this.state.masterPushRule!;
try {
const masterRule = this.state.masterPushRule!;
await MatrixClientPeg.get().setPushRuleEnabled("global", masterRule.kind, masterRule.rule_id, !checked);
await this.refreshFromServer();
} catch (e) {
Expand All @@ -404,6 +407,13 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
}
};

private setSavingError = (ruleId: RuleId | string): void => {
this.setState(({ ruleIdsWithError }) => ({
phase: Phase.SavingError,
ruleIdsWithError: { ...ruleIdsWithError, [ruleId]: true },
}));
};

private updateDeviceNotifications = async (checked: boolean): Promise<void> => {
await SettingsStore.setValue("deviceNotificationsEnabled", null, SettingLevel.DEVICE, checked);
};
Expand Down Expand Up @@ -455,11 +465,18 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
};

private onRadioChecked = async (rule: IVectorPushRule, checkedState: VectorState): Promise<void> => {
this.setState({ phase: Phase.Persisting });
this.setState(({ ruleIdsWithError }) => ({
phase: Phase.Persisting,
ruleIdsWithError: { ...ruleIdsWithError, [rule.ruleId]: false },
}));

try {
const cli = MatrixClientPeg.get();
if (rule.ruleId === KEYWORD_RULE_ID) {
// should not encounter this
if (!this.state.vectorKeywordRuleInfo) {
throw new Error("Notification data is incomplete.");
}
// Update all the keywords
for (const rule of this.state.vectorKeywordRuleInfo.rules) {
let enabled: boolean | undefined;
Expand Down Expand Up @@ -505,9 +522,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {

await this.refreshFromServer();
} catch (e) {
this.setState({ phase: Phase.Error });
this.setSavingError(rule.ruleId);
logger.error("Error updating push rule:", e);
this.showSaveError();
}
};

Expand Down Expand Up @@ -618,14 +634,16 @@ export default class Notifications extends React.PureComponent<IProps, IState> {

private renderTopSection(): JSX.Element {
const masterSwitch = (
<LabelledToggleSwitch
data-testid="notif-master-switch"
value={!this.isInhibited}
label={_t("Enable notifications for this account")}
caption={_t("Turn off to disable notifications on all your devices and sessions")}
onChange={this.onMasterRuleChanged}
disabled={this.state.phase === Phase.Persisting}
/>
<>
<LabelledToggleSwitch
data-testid="notif-master-switch"
value={!this.isInhibited}
label={_t("Enable notifications for this account")}
caption={_t("Turn off to disable notifications on all your devices and sessions")}
onChange={this.onMasterRuleChanged}
disabled={this.state.phase === Phase.Persisting}
/>
</>
);

// If all the rules are inhibited, don't show anything.
Expand All @@ -639,7 +657,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
<LabelledToggleSwitch
data-testid="notif-email-switch"
key={e.address}
value={this.state.pushers.some((p) => p.kind === "email" && p.pushkey === e.address)}
value={!!this.state.pushers?.some((p) => p.kind === "email" && p.pushkey === e.address)}
label={_t("Enable email notifications for %(email)s", { email: e.address })}
onChange={this.onEmailNotificationsChanged.bind(this, e.address)}
disabled={this.state.phase === Phase.Persisting}
Expand Down Expand Up @@ -768,6 +786,15 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
{makeRadio(r, VectorState.Off)}
{makeRadio(r, VectorState.On)}
{makeRadio(r, VectorState.Loud)}
{this.state.ruleIdsWithError[r.ruleId] && (
<div className="mx_UserNotifSettings_gridRowError">
<Caption isError>
{_t(
"An error occurred when updating your notification preferences. Please try to toggle your option again.",
)}
</Caption>
</div>
)}
</fieldset>
));

Expand Down
11 changes: 9 additions & 2 deletions src/components/views/typography/Caption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import classNames from "classnames";
import React, { HTMLAttributes } from "react";

interface Props extends Omit<HTMLAttributes<HTMLSpanElement>, "className"> {
children: React.ReactNode;
isError?: boolean;
}

export const Caption: React.FC<Props> = ({ children, ...rest }) => {
export const Caption: React.FC<Props> = ({ children, isError, ...rest }) => {
return (
<span className="mx_Caption" {...rest}>
<span
className={classNames("mx_Caption", {
mx_Caption_error: isError,
})}
{...rest}
>
{children}
</span>
);
Expand Down
1 change: 1 addition & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,7 @@
"On": "On",
"Off": "Off",
"Noisy": "Noisy",
"An error occurred when updating your notification preferences. Please try to toggle your option again.": "An error occurred when updating your notification preferences. Please try to toggle your option again.",
"Global": "Global",
"Mentions & keywords": "Mentions & keywords",
"Notification targets": "Notification targets",
Expand Down
Loading