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
This commit is contained in:
parent
e5291c195d
commit
10a765472b
3 changed files with 403 additions and 50 deletions
|
@ -15,10 +15,18 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
import { IAnnotatedPushRule, IPusher, PushRuleAction, PushRuleKind, RuleId } from "matrix-js-sdk/src/@types/PushRules";
|
||||
import {
|
||||
IAnnotatedPushRule,
|
||||
IPusher,
|
||||
PushRuleAction,
|
||||
IPushRule,
|
||||
PushRuleKind,
|
||||
RuleId,
|
||||
} from "matrix-js-sdk/src/@types/PushRules";
|
||||
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { LocalNotificationSettings } from "matrix-js-sdk/src/@types/local_notifications";
|
||||
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
||||
|
||||
import Spinner from "../elements/Spinner";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
|
@ -92,6 +100,9 @@ interface IVectorPushRule {
|
|||
rule?: IAnnotatedPushRule;
|
||||
description: TranslatedString | string;
|
||||
vectorState: VectorState;
|
||||
// loudest vectorState of a rule and its synced rules
|
||||
// undefined when rule has no synced rules
|
||||
syncedVectorState?: VectorState;
|
||||
}
|
||||
|
||||
interface IProps {}
|
||||
|
@ -115,9 +126,68 @@ interface IState {
|
|||
|
||||
clearingNotifications: boolean;
|
||||
}
|
||||
const findInDefaultRules = (
|
||||
ruleId: RuleId | string,
|
||||
defaultRules: {
|
||||
[k in RuleClass]: IAnnotatedPushRule[];
|
||||
},
|
||||
): IAnnotatedPushRule | undefined => {
|
||||
for (const category in defaultRules) {
|
||||
const rule: IAnnotatedPushRule | undefined = defaultRules[category as RuleClass].find(
|
||||
(rule) => rule.rule_id === ruleId,
|
||||
);
|
||||
if (rule) {
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Vector notification states ordered by loudness in ascending order
|
||||
const OrderedVectorStates = [VectorState.Off, VectorState.On, VectorState.Loud];
|
||||
|
||||
/**
|
||||
* Find the 'loudest' vector state assigned to a rule
|
||||
* and it's synced rules
|
||||
* If rules have fallen out of sync,
|
||||
* the loudest rule can determine the display value
|
||||
* @param defaultRules
|
||||
* @param rule - parent rule
|
||||
* @param definition - definition of parent rule
|
||||
* @returns VectorState - the maximum/loudest state for the parent and synced rules
|
||||
*/
|
||||
const maximumVectorState = (
|
||||
defaultRules: {
|
||||
[k in RuleClass]: IAnnotatedPushRule[];
|
||||
},
|
||||
rule: IAnnotatedPushRule,
|
||||
definition: VectorPushRuleDefinition,
|
||||
): VectorState | undefined => {
|
||||
if (!definition.syncedRuleIds?.length) {
|
||||
return undefined;
|
||||
}
|
||||
const vectorState = definition.syncedRuleIds.reduce<VectorState>((maxVectorState, ruleId) => {
|
||||
// already set to maximum
|
||||
if (maxVectorState === VectorState.Loud) {
|
||||
return maxVectorState;
|
||||
}
|
||||
const syncedRule = findInDefaultRules(ruleId, defaultRules);
|
||||
if (syncedRule) {
|
||||
const syncedRuleVectorState = definition.ruleToVectorState(syncedRule);
|
||||
// if syncedRule is 'louder' than current maximum
|
||||
// set maximum to louder vectorState
|
||||
if (OrderedVectorStates.indexOf(syncedRuleVectorState) > OrderedVectorStates.indexOf(maxVectorState)) {
|
||||
return syncedRuleVectorState;
|
||||
}
|
||||
}
|
||||
return maxVectorState;
|
||||
}, definition.ruleToVectorState(rule));
|
||||
|
||||
return vectorState;
|
||||
};
|
||||
|
||||
export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||
private settingWatchers: string[];
|
||||
private pushProcessor: PushProcessor;
|
||||
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
@ -145,6 +215,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
|||
this.setState({ audioNotifications: value as boolean }),
|
||||
),
|
||||
];
|
||||
|
||||
this.pushProcessor = new PushProcessor(MatrixClientPeg.get());
|
||||
}
|
||||
|
||||
private get isInhibited(): boolean {
|
||||
|
@ -281,6 +353,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
|||
ruleId: rule.rule_id,
|
||||
rule,
|
||||
vectorState,
|
||||
syncedVectorState: maximumVectorState(defaultRules, rule, definition),
|
||||
description: _t(definition.description),
|
||||
});
|
||||
}
|
||||
|
@ -388,6 +461,43 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
|||
await SettingsStore.setValue("audioNotificationsEnabled", null, SettingLevel.DEVICE, checked);
|
||||
};
|
||||
|
||||
private setPushRuleActions = async (
|
||||
ruleId: IPushRule["rule_id"],
|
||||
kind: PushRuleKind,
|
||||
actions?: PushRuleAction[],
|
||||
): Promise<void> => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (!actions) {
|
||||
await cli.setPushRuleEnabled("global", kind, ruleId, false);
|
||||
} else {
|
||||
await cli.setPushRuleActions("global", kind, ruleId, actions);
|
||||
await cli.setPushRuleEnabled("global", kind, ruleId, true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updated syncedRuleIds from rule definition
|
||||
* If a rule does not exist it is ignored
|
||||
* Synced rules are updated sequentially
|
||||
* and stop at first error
|
||||
*/
|
||||
private updateSyncedRules = async (
|
||||
syncedRuleIds: VectorPushRuleDefinition["syncedRuleIds"],
|
||||
actions?: PushRuleAction[],
|
||||
): Promise<void> => {
|
||||
// get synced rules that exist for user
|
||||
const syncedRules: ReturnType<PushProcessor["getPushRuleAndKindById"]>[] = syncedRuleIds
|
||||
?.map((ruleId) => this.pushProcessor.getPushRuleAndKindById(ruleId))
|
||||
.filter(Boolean);
|
||||
|
||||
if (!syncedRules?.length) {
|
||||
return;
|
||||
}
|
||||
for (const { kind, rule: syncedRule } of syncedRules) {
|
||||
await this.setPushRuleActions(syncedRule.rule_id, kind, actions);
|
||||
}
|
||||
};
|
||||
|
||||
private onRadioChecked = async (rule: IVectorPushRule, checkedState: VectorState): Promise<void> => {
|
||||
this.setState({ phase: Phase.Persisting });
|
||||
|
||||
|
@ -428,12 +538,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
|||
} else {
|
||||
const definition: VectorPushRuleDefinition = VectorPushRulesDefinitions[rule.ruleId];
|
||||
const actions = definition.vectorStateToActions[checkedState];
|
||||
if (!actions) {
|
||||
await cli.setPushRuleEnabled("global", rule.rule.kind, rule.rule.rule_id, false);
|
||||
} else {
|
||||
await cli.setPushRuleActions("global", rule.rule.kind, rule.rule.rule_id, actions);
|
||||
await cli.setPushRuleEnabled("global", rule.rule.kind, rule.rule.rule_id, true);
|
||||
}
|
||||
await this.setPushRuleActions(rule.rule.rule_id, rule.rule.kind, actions);
|
||||
await this.updateSyncedRules(definition.syncedRuleIds, actions);
|
||||
}
|
||||
|
||||
await this.refreshFromServer();
|
||||
|
@ -684,7 +790,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
|||
<StyledRadioButton
|
||||
key={r.ruleId + s}
|
||||
name={r.ruleId}
|
||||
checked={r.vectorState === s}
|
||||
checked={(r.syncedVectorState ?? r.vectorState) === s}
|
||||
onChange={this.onRadioChecked.bind(this, r, s)}
|
||||
disabled={this.state.phase === Phase.Persisting}
|
||||
aria-label={VectorStateToLabel[s]}
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IAnnotatedPushRule, PushRuleAction } from "matrix-js-sdk/src/@types/PushRules";
|
||||
import { IAnnotatedPushRule, PushRuleAction, RuleId } from "matrix-js-sdk/src/@types/PushRules";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { _td } from "../languageHandler";
|
||||
|
@ -29,15 +29,22 @@ type StateToActionsMap = {
|
|||
interface IVectorPushRuleDefinition {
|
||||
description: string;
|
||||
vectorStateToActions: StateToActionsMap;
|
||||
/**
|
||||
* Rules that should be updated to be kept in sync
|
||||
* when this rule changes
|
||||
*/
|
||||
syncedRuleIds?: (RuleId | string)[];
|
||||
}
|
||||
|
||||
class VectorPushRuleDefinition {
|
||||
public readonly description: string;
|
||||
public readonly vectorStateToActions: StateToActionsMap;
|
||||
public readonly syncedRuleIds?: (RuleId | string)[];
|
||||
|
||||
public constructor(opts: IVectorPushRuleDefinition) {
|
||||
this.description = opts.description;
|
||||
this.vectorStateToActions = opts.vectorStateToActions;
|
||||
this.syncedRuleIds = opts.syncedRuleIds;
|
||||
}
|
||||
|
||||
// Translate the rule actions and its enabled value into vector state
|
||||
|
@ -125,6 +132,12 @@ export const VectorPushRulesDefinitions: Record<string, VectorPushRuleDefinition
|
|||
[VectorState.Loud]: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
|
||||
[VectorState.Off]: StandardActions.ACTION_DONT_NOTIFY,
|
||||
},
|
||||
syncedRuleIds: [
|
||||
RuleId.PollStartOneToOne,
|
||||
RuleId.PollStartOneToOneUnstable,
|
||||
RuleId.PollEndOneToOne,
|
||||
RuleId.PollEndOneToOneUnstable,
|
||||
],
|
||||
}),
|
||||
|
||||
// Encrypted messages just sent to the user in a 1:1 room
|
||||
|
@ -147,6 +160,7 @@ export const VectorPushRulesDefinitions: Record<string, VectorPushRuleDefinition
|
|||
[VectorState.Loud]: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
|
||||
[VectorState.Off]: StandardActions.ACTION_DONT_NOTIFY,
|
||||
},
|
||||
syncedRuleIds: [RuleId.PollStart, RuleId.PollStartUnstable, RuleId.PollEnd, RuleId.PollEndUnstable],
|
||||
}),
|
||||
|
||||
// Encrypted messages just sent to a group chat room
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue