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

Commit 10a7654

Browse files
author
Kerry
authored
Polls push rules: synchronise poll rules with message rules (#10263)
* basic sync setup * formatting * get loudest value for synced rules * more types * test synced rules in notifications settings * type fixes * noimplicitany fixes * remove debug * tidying
1 parent e5291c1 commit 10a7654

File tree

3 files changed

+403
-50
lines changed

3 files changed

+403
-50
lines changed

src/components/views/settings/Notifications.tsx

Lines changed: 114 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@ limitations under the License.
1515
*/
1616

1717
import React, { ReactNode } from "react";
18-
import { IAnnotatedPushRule, IPusher, PushRuleAction, PushRuleKind, RuleId } from "matrix-js-sdk/src/@types/PushRules";
18+
import {
19+
IAnnotatedPushRule,
20+
IPusher,
21+
PushRuleAction,
22+
IPushRule,
23+
PushRuleKind,
24+
RuleId,
25+
} from "matrix-js-sdk/src/@types/PushRules";
1926
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
2027
import { logger } from "matrix-js-sdk/src/logger";
2128
import { LocalNotificationSettings } from "matrix-js-sdk/src/@types/local_notifications";
29+
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
2230

2331
import Spinner from "../elements/Spinner";
2432
import { MatrixClientPeg } from "../../../MatrixClientPeg";
@@ -92,6 +100,9 @@ interface IVectorPushRule {
92100
rule?: IAnnotatedPushRule;
93101
description: TranslatedString | string;
94102
vectorState: VectorState;
103+
// loudest vectorState of a rule and its synced rules
104+
// undefined when rule has no synced rules
105+
syncedVectorState?: VectorState;
95106
}
96107

97108
interface IProps {}
@@ -115,9 +126,68 @@ interface IState {
115126

116127
clearingNotifications: boolean;
117128
}
129+
const findInDefaultRules = (
130+
ruleId: RuleId | string,
131+
defaultRules: {
132+
[k in RuleClass]: IAnnotatedPushRule[];
133+
},
134+
): IAnnotatedPushRule | undefined => {
135+
for (const category in defaultRules) {
136+
const rule: IAnnotatedPushRule | undefined = defaultRules[category as RuleClass].find(
137+
(rule) => rule.rule_id === ruleId,
138+
);
139+
if (rule) {
140+
return rule;
141+
}
142+
}
143+
};
144+
145+
// Vector notification states ordered by loudness in ascending order
146+
const OrderedVectorStates = [VectorState.Off, VectorState.On, VectorState.Loud];
147+
148+
/**
149+
* Find the 'loudest' vector state assigned to a rule
150+
* and it's synced rules
151+
* If rules have fallen out of sync,
152+
* the loudest rule can determine the display value
153+
* @param defaultRules
154+
* @param rule - parent rule
155+
* @param definition - definition of parent rule
156+
* @returns VectorState - the maximum/loudest state for the parent and synced rules
157+
*/
158+
const maximumVectorState = (
159+
defaultRules: {
160+
[k in RuleClass]: IAnnotatedPushRule[];
161+
},
162+
rule: IAnnotatedPushRule,
163+
definition: VectorPushRuleDefinition,
164+
): VectorState | undefined => {
165+
if (!definition.syncedRuleIds?.length) {
166+
return undefined;
167+
}
168+
const vectorState = definition.syncedRuleIds.reduce<VectorState>((maxVectorState, ruleId) => {
169+
// already set to maximum
170+
if (maxVectorState === VectorState.Loud) {
171+
return maxVectorState;
172+
}
173+
const syncedRule = findInDefaultRules(ruleId, defaultRules);
174+
if (syncedRule) {
175+
const syncedRuleVectorState = definition.ruleToVectorState(syncedRule);
176+
// if syncedRule is 'louder' than current maximum
177+
// set maximum to louder vectorState
178+
if (OrderedVectorStates.indexOf(syncedRuleVectorState) > OrderedVectorStates.indexOf(maxVectorState)) {
179+
return syncedRuleVectorState;
180+
}
181+
}
182+
return maxVectorState;
183+
}, definition.ruleToVectorState(rule));
184+
185+
return vectorState;
186+
};
118187

119188
export default class Notifications extends React.PureComponent<IProps, IState> {
120189
private settingWatchers: string[];
190+
private pushProcessor: PushProcessor;
121191

122192
public constructor(props: IProps) {
123193
super(props);
@@ -145,6 +215,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
145215
this.setState({ audioNotifications: value as boolean }),
146216
),
147217
];
218+
219+
this.pushProcessor = new PushProcessor(MatrixClientPeg.get());
148220
}
149221

150222
private get isInhibited(): boolean {
@@ -281,6 +353,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
281353
ruleId: rule.rule_id,
282354
rule,
283355
vectorState,
356+
syncedVectorState: maximumVectorState(defaultRules, rule, definition),
284357
description: _t(definition.description),
285358
});
286359
}
@@ -388,6 +461,43 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
388461
await SettingsStore.setValue("audioNotificationsEnabled", null, SettingLevel.DEVICE, checked);
389462
};
390463

464+
private setPushRuleActions = async (
465+
ruleId: IPushRule["rule_id"],
466+
kind: PushRuleKind,
467+
actions?: PushRuleAction[],
468+
): Promise<void> => {
469+
const cli = MatrixClientPeg.get();
470+
if (!actions) {
471+
await cli.setPushRuleEnabled("global", kind, ruleId, false);
472+
} else {
473+
await cli.setPushRuleActions("global", kind, ruleId, actions);
474+
await cli.setPushRuleEnabled("global", kind, ruleId, true);
475+
}
476+
};
477+
478+
/**
479+
* Updated syncedRuleIds from rule definition
480+
* If a rule does not exist it is ignored
481+
* Synced rules are updated sequentially
482+
* and stop at first error
483+
*/
484+
private updateSyncedRules = async (
485+
syncedRuleIds: VectorPushRuleDefinition["syncedRuleIds"],
486+
actions?: PushRuleAction[],
487+
): Promise<void> => {
488+
// get synced rules that exist for user
489+
const syncedRules: ReturnType<PushProcessor["getPushRuleAndKindById"]>[] = syncedRuleIds
490+
?.map((ruleId) => this.pushProcessor.getPushRuleAndKindById(ruleId))
491+
.filter(Boolean);
492+
493+
if (!syncedRules?.length) {
494+
return;
495+
}
496+
for (const { kind, rule: syncedRule } of syncedRules) {
497+
await this.setPushRuleActions(syncedRule.rule_id, kind, actions);
498+
}
499+
};
500+
391501
private onRadioChecked = async (rule: IVectorPushRule, checkedState: VectorState): Promise<void> => {
392502
this.setState({ phase: Phase.Persisting });
393503

@@ -428,12 +538,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
428538
} else {
429539
const definition: VectorPushRuleDefinition = VectorPushRulesDefinitions[rule.ruleId];
430540
const actions = definition.vectorStateToActions[checkedState];
431-
if (!actions) {
432-
await cli.setPushRuleEnabled("global", rule.rule.kind, rule.rule.rule_id, false);
433-
} else {
434-
await cli.setPushRuleActions("global", rule.rule.kind, rule.rule.rule_id, actions);
435-
await cli.setPushRuleEnabled("global", rule.rule.kind, rule.rule.rule_id, true);
436-
}
541+
await this.setPushRuleActions(rule.rule.rule_id, rule.rule.kind, actions);
542+
await this.updateSyncedRules(definition.syncedRuleIds, actions);
437543
}
438544

439545
await this.refreshFromServer();
@@ -684,7 +790,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
684790
<StyledRadioButton
685791
key={r.ruleId + s}
686792
name={r.ruleId}
687-
checked={r.vectorState === s}
793+
checked={(r.syncedVectorState ?? r.vectorState) === s}
688794
onChange={this.onRadioChecked.bind(this, r, s)}
689795
disabled={this.state.phase === Phase.Persisting}
690796
aria-label={VectorStateToLabel[s]}

src/notifications/VectorPushRulesDefinitions.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { IAnnotatedPushRule, PushRuleAction } from "matrix-js-sdk/src/@types/PushRules";
17+
import { IAnnotatedPushRule, PushRuleAction, RuleId } from "matrix-js-sdk/src/@types/PushRules";
1818
import { logger } from "matrix-js-sdk/src/logger";
1919

2020
import { _td } from "../languageHandler";
@@ -29,15 +29,22 @@ type StateToActionsMap = {
2929
interface IVectorPushRuleDefinition {
3030
description: string;
3131
vectorStateToActions: StateToActionsMap;
32+
/**
33+
* Rules that should be updated to be kept in sync
34+
* when this rule changes
35+
*/
36+
syncedRuleIds?: (RuleId | string)[];
3237
}
3338

3439
class VectorPushRuleDefinition {
3540
public readonly description: string;
3641
public readonly vectorStateToActions: StateToActionsMap;
42+
public readonly syncedRuleIds?: (RuleId | string)[];
3743

3844
public constructor(opts: IVectorPushRuleDefinition) {
3945
this.description = opts.description;
4046
this.vectorStateToActions = opts.vectorStateToActions;
47+
this.syncedRuleIds = opts.syncedRuleIds;
4148
}
4249

4350
// Translate the rule actions and its enabled value into vector state
@@ -125,6 +132,12 @@ export const VectorPushRulesDefinitions: Record<string, VectorPushRuleDefinition
125132
[VectorState.Loud]: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
126133
[VectorState.Off]: StandardActions.ACTION_DONT_NOTIFY,
127134
},
135+
syncedRuleIds: [
136+
RuleId.PollStartOneToOne,
137+
RuleId.PollStartOneToOneUnstable,
138+
RuleId.PollEndOneToOne,
139+
RuleId.PollEndOneToOneUnstable,
140+
],
128141
}),
129142

130143
// Encrypted messages just sent to the user in a 1:1 room
@@ -147,6 +160,7 @@ export const VectorPushRulesDefinitions: Record<string, VectorPushRuleDefinition
147160
[VectorState.Loud]: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
148161
[VectorState.Off]: StandardActions.ACTION_DONT_NOTIFY,
149162
},
163+
syncedRuleIds: [RuleId.PollStart, RuleId.PollStartUnstable, RuleId.PollEnd, RuleId.PollEndUnstable],
150164
}),
151165

152166
// Encrypted messages just sent to a group chat room

0 commit comments

Comments
 (0)