Update the actions of default rules instead of overriding.

The Matrix CS API, and synapse now supports setting the actions for default
rules. Doing that makes managing the rules much simpler from a vector
persepctive since the ON/LOUD/OFF toggle buttons can be implemented by
setting the actions and enabling/disabling the default rules rather than
overidding them.

Overriding the default rules was difficult because it was not possible
to intermingle the evaluation of user-specified rules with the default
rules. So even though you could add a rule with the same conditions as a
default rule, it would evaluate before *all* the other default rules.

Also creating new rules under a im.vector namespace creates challenges
if we want vector to cooperate with other matrix clients that want to
provide a similar set of toggle switches for the push rules.
This commit is contained in:
Mark Haines 2016-02-26 20:51:16 +00:00
parent c3b819b4da
commit 731d94eea4
1 changed files with 99 additions and 290 deletions

View File

@ -37,6 +37,40 @@ var PushRuleVectorState = {
OFF: "off"
};
var ACTION_NOTIFY = [ "notify" ];
var ACTION_NOTIFY_DEFAULT_SOUND = [
"notify",
{
"set_tweak": "sound",
"value": "default"
}
];
var ACTION_NOTIFY_RING_SOUND = [
"notify",
{
"set_tweak": "sound",
"value": "ring"
}
];
var ACTION_HIGHLIGHT_DEFAULT_SOUND = [
"notify",
{
"set_tweak": "sound",
"value": "default"
},
{
"set_tweak":"highlight"
}
];
var ACTION_DONT_NOTIFY = [ "dont_notify" ];
var ACTION_DISABLED = null;
/**
* The descriptions of rules managed by the Vector UI.
* Each rule is described so that if the server does not have it in its default
@ -48,225 +82,71 @@ var VectorPushRulesDefinitions = {
// Messages containing user's display name
// (skip contains_user_name which is too geeky)
"im.vector.rule.contains_display_name": {
".m.rule.contains_display_name": {
kind: "underride",
hsDefaultRuleId: ".m.rule.contains_display_name",
description: "Messages containing my name",
conditions: [{
"kind": "contains_display_name"
}],
vectorStateToActions: { // The actions for each vector state
on: [
"notify"
],
loud: [
"notify",
{
"set_tweak": "sound",
"value": "default"
},
{
"set_tweak":"highlight"
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
on: ACTION_NOTIFY,
loud: ACTION_HIGHLIGHT_DEFAULT,
off: ACTION_DISABLED
}
]
},
vectorStateToHsDefaultRuleEnabled: { // If it exists, the hs default push rule enabled expected value for each vector state
on: undefined, // ON (and its actions) does not corresponds to the default hs push rule, so NA
loud: true, // LOUD corresponds to the default rule when its enabled value is true
off: false // OFF corresponds to the default rule when its enabled value is false
},
},
// Messages just sent to the user in a 1:1 room
"im.vector.rule.room_one_to_one": {
".m.rule.room_one_to_one": {
kind: "underride",
hsDefaultRuleId: ".m.rule.room_one_to_one",
description: "Messages in one-to-one chats",
conditions: [{
"is": "2",
"kind": "room_member_count"
}],
vectorStateToActions: {
on: [
"notify"
],
loud: [
"notify",
{
"set_tweak": "sound",
"value": "default"
}
],
off: [
"dont_notify"
]
},
vectorStateToHsDefaultRuleEnabled: {
on: undefined,
loud: true,
off: undefined
on: ACTION_NOTIFY,
loud: ACTION_NOTIFY_DEFAULT_SOUND,
off: ACTION_DONT_NOTIFY
}
},
// Messages just sent to a group chat room
// 1:1 room messages are catched by the .m.rule.room_one_to_one rule if any defined
// By opposition, all other room messages are from group chat rooms.
"im.vector.rule.room_message": {
".m.rule.room_message": {
kind: "underride",
description: "Messages in group chats",
conditions: [{
"pattern": "m.room.message",
"kind": "event_match",
"key": "type"
}],
hsDefaultRuleId: ".m.rule.message",
vectorStateToActions: {
on: [
"notify"
],
loud: [
"notify",
{
"set_tweak": "sound",
"value": "default"
}
],
off: [
"dont_notify"
]
},
vectorStateToHsDefaultRuleEnabled: {
on: true,
loud: undefined,
off: undefined
on: ACTION_NOTIFY,
loud: ACTION_NOTIFY_DEFAULT_SOUND,
off: ACTION_DONT_NOTIFY
}
},
// Invitation for the user
"im.vector.rule.invite_for_me": {
".m.rule.invite_for_me": {
kind: "underride",
hsDefaultRuleId: ".m.rule.invite_for_me",
description: "When I'm invited to a room",
conditions: [
{
"key": "type",
"kind": "event_match",
"pattern": "m.room.member"
},
{
"key": "content.membership",
"kind": "event_match",
"pattern": "invite"
},
{
"key": "state_key",
"kind": "event_match",
"pattern": "" // It is updated at runtime the user id
}
],
vectorStateToActions: {
on: [
"notify"
],
loud: [
"notify",
{
"set_tweak": "sound",
"value": "default"
}
]
},
vectorStateToHsDefaultRuleEnabled: {
on: undefined,
loud: true,
off: false
on: ACTION_NOTIFY,
loud: ACTION_NOTIFY_DEFAULT_SOUND,
off: ACTION_DISABLED
}
},
// When people join or leave a room
/*"im.vector.rule.member_event": {
hsDefaultRuleId: ".m.rule.member_event",
description: "When people join or leave a room",
conditions: [{
"pattern": "m.room.member",
"kind": "event_match",
"key": "type"
}],
vectorStateToActions: {
on: [
"notify"
],
loud: [
"notify",
{
"set_tweak": "sound",
"value": "default"
}
]
},
vectorStateToHsDefaultRuleEnabled: {
on: true,
loud: undefined,
off: false
}
},*/
// Incoming call
"im.vector.rule.call": {
".m.rule.call": {
kind: "underride",
hsDefaultRuleId: ".m.rule.call",
description: "Call invitation",
conditions: [{
"pattern": "m.room.member",
"kind": "event_match",
"key": "type"
}],
vectorStateToActions: {
on: [
"notify"
],
loud: [
"notify",
{
"set_tweak": "sound",
"value": "ring"
}
],
},
vectorStateToHsDefaultRuleEnabled: {
on: undefined,
loud: true,
off: false
on: ACTION_NOTIFY,
loud: ACTION_NOTIFY_RING_SOUND,
off: ACTION_DISABLED
}
},
// Notifications from bots
"im.vector.rule.notices": {
".m.rule.suppress_notices": {
kind: "override",
hsDefaultRuleId: ".m.rule.suppress_notices",
description: "Messages sent by bot",
conditions: [{
"kind": "event_match",
"key": "content.msgtype",
"pattern": "m.notice"
}],
vectorStateToActions: {
on: undefined, // ON for vector UI means that the .m.rule.suppress_notices rule is disabled.
loud: [
"notify",
{
"set_tweak": "sound",
"value": "ring"
}
],
off: [
"dont_notify"
]
},
vectorStateToHsDefaultRuleEnabled: {
on: false, // .m.rule.suppress_notices is a "negative" rule, we have to invert its enabled value for vector UI
loud: undefined,
off: true
// .m.rule.suppress_notices is a "negative" rule, we have to invert its enabled value for vector UI
on: ACTION_DISABLED,
loud: ACTION_NOTIFY_DEFAULT_SOUND,
off: ACTION_DONT_NOTIFY,
}
}
};
@ -295,9 +175,6 @@ module.exports = React.createClass({
},
componentWillMount: function() {
// Finalise the vector definitions
VectorPushRulesDefinitions["im.vector.rule.invite_for_me"].conditions[2].pattern = MatrixClientPeg.get().credentials.userId;
this._refreshFromServer();
},
@ -389,13 +266,10 @@ module.exports = React.createClass({
_actionsFor: function(pushRuleVectorState) {
if (pushRuleVectorState === PushRuleVectorState.ON) {
return ['notify'];
return ACTIONS_NOTIFY;
}
else if (pushRuleVectorState === PushRuleVectorState.LOUD) {
return ['notify',
{'set_tweak': 'sound', 'value': 'default'},
{'set_tweak': 'highlight', 'value': 'true'}
];;
return ACTIONS_HIGHLIGHT_DEFAULT_SOUND;
}
},
@ -437,34 +311,16 @@ module.exports = React.createClass({
var ruleDefinition = VectorPushRulesDefinitions[rule.vectorRuleId];
if (rule.rule) {
if (undefined !== ruleDefinition.vectorStateToHsDefaultRuleEnabled[newPushRuleVectorState] && rule.hsDefaultRule) {
// The new state corresponds to the default hs rule
// Enable or disable it according to the rule definition
deferreds.push(cli.setPushRuleEnabled('global', rule.hsDefaultRule.kind, ruleDefinition.hsDefaultRuleId,
ruleDefinition.vectorStateToHsDefaultRuleEnabled[newPushRuleVectorState]));
var actions = ruleDefinition.vectorStateToActions(newPushRuleVectorState);
// Remove the vector rule if any
if (!rule.isHSDefaultRule) {
deferreds.push(cli.deletePushRule('global', rule.rule.kind, rule.rule.rule_id))
}
if (actions === ACTIONS_DISABLED) {
// The new state corresponds to disabling the rule.
deferreds.push(cli.setPushRuleEnabled('global', rule.rule.kind, rule.rule.rule_id, false));
}
else {
// The new state (and its implied actions) does not correspond to a default hs rule
// or the HS does not expose this default rule.
if (rule.isHSDefaultRule) {
// Create a new rule that will override the default one
deferreds.push(this._addOverridingVectorPushRule(rule.vectorRuleId, newPushRuleVectorState));
// The new state corresponds to enabling the rule and setting specific actions
deferreds.push(this._updatePushRuleActions(rule.rule, actions, true));
}
else {
// Change the actions of the existing overriding Vector rule
deferreds.push(this._updatePushRuleActions(rule.rule, ruleDefinition.vectorStateToActions[newPushRuleVectorState]));
}
}
}
else {
// This is a Vector rule which does not exist yet server side
// Create it
deferreds.push(this._addOverridingVectorPushRule(rule.vectorRuleId, newPushRuleVectorState));
}
q.all(deferreds).done(function() {
@ -690,8 +546,6 @@ module.exports = React.createClass({
// HS default rules
var defaultRules = {master: [], vector: {}, others: []};
// Push rules defined py Vector to override hs default rules
var vectorOverridingRules = {};
// Content/keyword rules
var contentRules = {on: [], on_but_disabled:[], loud: [], loud_but_disabled: [], other: []};
@ -701,29 +555,16 @@ module.exports = React.createClass({
var cat = rule_categories[r.rule_id];
r.kind = kind;
if (r.rule_id[0] === '.') {
if (cat) {
if (cat === 'vector') {
// Remove disabled, useless actions
r.actions = r.actions.reduce(function(array, action){
if (action.value !== false) {
array.push(action);
}
return array;
},[]);
defaultRules.vector[r.rule_id] = r;
}
else {
defaultRules[cat].push(r);
}
else if (cat === 'master') {
defaultRules.master.push(r);
}
else {
defaultRules['others'].push(r);
}
}
else if (r.rule_id.startsWith('im.vector')) {
vectorOverridingRules[r.rule_id] = r;
}
else if (kind === 'content') {
switch (self._contentRuleVectorStateKind(r)) {
case PushRuleVectorState.ON:
@ -804,14 +645,14 @@ module.exports = React.createClass({
self.state.vectorPushRules = [];
var vectorRuleIds = [
'im.vector.rule.contains_display_name',
'.m.rule.contains_display_name',
'_keywords',
'im.vector.rule.room_one_to_one',
'im.vector.rule.room_message',
'im.vector.rule.invite_for_me',
'.m.rule.room_one_to_one',
'.m.rule.room_message',
'.m.rule.invite_for_me',
//'im.vector.rule.member_event',
'im.vector.rule.call',
'im.vector.rule.notices'
'.m.rule.call',
'.m.rule.notices'
];
for (var i in vectorRuleIds) {
var vectorRuleId = vectorRuleIds[i];
@ -828,13 +669,7 @@ module.exports = React.createClass({
});
}
else {
var rule = vectorOverridingRules[vectorRuleId];
var isHSDefaultRule = false;
if (!rule) {
// If the rule is not defined, look at the hs default one
rule = defaultRules.vector[ruleDefinition.hsDefaultRuleId];
isHSDefaultRule = true;
}
var rule = defaultRules.vector[vectorRuleId];
// Translate the rule actions and its enabled value into vector state
var vectorState;
@ -843,9 +678,9 @@ module.exports = React.createClass({
var state = PushRuleVectorState[stateKey];
var vectorStateToActions = ruleDefinition.vectorStateToActions[state];
if (!vectorStateToActions) {
if (vectorStateToActions === ACTIONS_DISABLED) {
// No defined actions means that this vector state expects a disabled default hs rule
if (isHSDefaultRule && rule.enabled === ruleDefinition.vectorStateToHsDefaultRuleEnabled[state]) {
if (rule.enabled === false) {
vectorState = state;
break;
}
@ -853,14 +688,8 @@ module.exports = React.createClass({
else {
// The actions must match to the ones expected by vector state
if (JSON.stringify(rule.actions) === JSON.stringify(vectorStateToActions)) {
if (isHSDefaultRule) {
// In the case of a default hs push rule, the enabled value must also match
if (rule.enabled === ruleDefinition.vectorStateToHsDefaultRuleEnabled[state]) {
vectorState = state;
break;
}
}
else {
// And the rule must be enabled.
if (rule.enabled === true) {
vectorState = state;
break;
}
@ -882,8 +711,6 @@ module.exports = React.createClass({
"description" : ruleDefinition.description,
"rule": rule,
"vectorState": vectorState,
"isHSDefaultRule": isHSDefaultRule,
"hsDefaultRule": defaultRules.vector[ruleDefinition.hsDefaultRuleId]
});
}
}
@ -913,37 +740,19 @@ module.exports = React.createClass({
},
_updatePushRuleActions: function(rule, actions, enabled) {
// Workaround for SYN-590 : Push rule update fails
// Remove the rule and recreate it with the new actions
var cli = MatrixClientPeg.get();
var deferred = q.defer();
cli.deletePushRule('global', rule.kind, rule.rule_id).done(function() {
cli.addPushRule('global', rule.kind, rule.rule_id, {
conditions: rule.conditions,
actions: actions,
pattern: rule.pattern
}).done(function() {
return cli.setPushRuleActions(
'global', rule.kind, rule.rule_id, actions
).then( function() {
// Then, if requested, enabled or disabled the rule
if (undefined != enabled) {
cli.setPushRuleEnabled('global', rule.kind, rule.rule_id, enabled).done(function() {
deferred.resolve();
}, function(err) {
deferred.reject(err);
});
return cli.setPushRuleEnabled(
'global', rule.kind, rule.rule_id, enabled
);
}
else {
deferred.resolve();
}
}, function(err) {
deferred.reject(err);
});
}, function(err) {
deferred.reject(err);
});
return deferred.promise;
},
renderNotifRulesTableRow: function(title, className, pushRuleVectorState) {