Make action order configurable from plugin

``` js
Gerrit.install(function(plugin) {
  // Make cherry-pick appear first.
  plugin.changeActions().setActionPriority(
      'revision', 'cherrypick', -5);
  // Make cherry-pick appear last
  plugin.changeActions().setActionPriority(
      'revision', 'cherrypick', 5);
});
```

Default priority list is:
- Reply is always first, aligned to the left
- More actions if always after it
- Quick approve (CodeReview+2) after them, with priority at -3
- Primary actions come last, with priority of 3
- Change actions before primary, with priority of 2
- Revision actions before change actions, with priority of 1
- In case of a tie, actions are sorted alphabetically

Feature: Issue 5360
Change-Id: I7f36954608f764bff4e36a4a2f916bf0dda3859d
This commit is contained in:
Viktar Donich
2017-04-18 16:08:13 -07:00
parent 44e2ccd2ab
commit 7f412ae230
4 changed files with 94 additions and 29 deletions

View File

@@ -87,6 +87,14 @@
method: 'POST',
};
var ActionPriority = {
CHANGE: 2,
DEFAULT: 0,
PRIMARY: 3,
REVIEW: -3,
REVISION: 1,
};
Polymer({
is: 'gr-change-actions',
@@ -149,7 +157,8 @@
_allActionValues: {
type: Array,
computed: '_computeAllActions(actions.*, revisionActions.*,' +
'primaryActionKeys.*, _additionalActions.*, change)',
'primaryActionKeys.*, _additionalActions.*, change, ' +
'_actionPriorityOverrides.*)',
},
_topLevelActions: {
type: Array,
@@ -181,6 +190,10 @@
return value;
},
},
_actionPriorityOverrides: {
type: Array,
value: function() { return []; },
},
_additionalActions: {
type: Array,
value: function() { return []; },
@@ -279,6 +292,25 @@
}
},
setActionPriority: function(type, key, priority) {
if (type !== ActionType.CHANGE && type !== ActionType.REVISION) {
throw Error('Invalid action type given: ' + type);
}
var index = this._actionPriorityOverrides.findIndex(function(action) {
return action.type === type && action.key === key;
});
var action = {
type: type,
key: key,
priority: priority,
};
if (index !== -1) {
this.set('_actionPriorityOverrides', index, action);
} else {
this.push('_actionPriorityOverrides', action);
}
},
setActionHidden: function(type, key, hidden) {
if (type !== ActionType.CHANGE && type !== ActionType.REVISION) {
throw Error('Invalid action type given: ' + type);
@@ -764,10 +796,12 @@
* @param {splices} primariesRecord
* @param {splices} additionalActionsRecord
* @param {Object} change The change object.
* @param {Object} priorityOverridesRecord
* @return {Array}
*/
_computeAllActions: function(changeActionsRecord, revisionActionsRecord,
primariesRecord, additionalActionsRecord, change) {
primariesRecord, additionalActionsRecord, change,
priorityOverridesRecord) {
var revisionActionValues = this._getActionValues(revisionActionsRecord,
primariesRecord, additionalActionsRecord, ActionType.REVISION);
var changeActionValues = this._getActionValues(changeActionsRecord,
@@ -778,36 +812,43 @@
}
return revisionActionValues
.concat(changeActionValues)
.sort(this._actionComparator);
.sort(this._actionComparator.bind(this));
},
_getActionPriority: function(action) {
if (action.__type && action.__key) {
var overrideAction = this._actionPriorityOverrides.find(function(i) {
return i.type === action.__type && i.key === action.__key;
});
if (overrideAction !== undefined) {
return overrideAction.priority;
}
}
if (action.__key === 'review') {
return ActionPriority.REVIEW;
} else if (action.__primary) {
return ActionPriority.PRIMARY;
} else if (action.__type === ActionType.CHANGE) {
return ActionPriority.CHANGE;
} else if (action.__type === ActionType.REVISION) {
return ActionPriority.REVISION;
}
return ActionPriority.DEFAULT;
},
/**
* Sort comparator to define the order of change actions.
*/
_actionComparator: function(actionA, actionB) {
// The code review action always appears first.
if (actionA.__key === 'review') {
return -1;
} else if (actionB.__key === 'review') {
return 1;
var priorityDelta = this._getActionPriority(actionA) -
this._getActionPriority(actionB);
// Sort by the button label if same priority.
if (priorityDelta === 0) {
return actionA.label > actionB.label ? 1 : -1;
} else {
return priorityDelta;
}
// Primary actions always appear last.
if (actionA.__primary) {
return 1;
} else if (actionB.__primary) {
return -1;
}
// Change actions appear before revision actions.
if (actionA.__type === 'change' && actionB.__type === 'revision') {
return 1;
} else if (actionA.__type === 'revision' && actionB.__type === 'change') {
return -1;
}
// Otherwise, sort by the button label.
return actionA.label > actionB.label ? 1 : -1;
},
_computeTopLevelActions: function(actionRecord, hiddenActionsRecord,

View File

@@ -207,16 +207,15 @@ limitations under the License.
test('_actionComparator sort order', function() {
var actions = [
{label: '123', __type: 'change', __key: 'review'},
{label: 'abc', __type: 'revision'},
{label: 'abc-ro', __type: 'revision'},
{label: 'abc', __type: 'change'},
{label: 'def', __type: 'change'},
{label: 'def', __type: 'change', __primary: true},
{label: 'def-p', __type: 'change', __primary: true},
];
var result = actions.slice();
result.reverse();
result.sort(element._actionComparator);
result.sort(element._actionComparator.bind(element));
assert.deepEqual(result, actions);
});

View File

@@ -38,6 +38,11 @@
return this._el.setActionOverflow(type, key, overflow);
};
GrChangeActionsInterface.prototype.setActionPriority = function(type, key,
priority) {
return this._el.setActionPriority(type, key, priority);
};
GrChangeActionsInterface.prototype.setActionHidden = function(type, key,
hidden) {
return this._el.setActionHidden(type, key, hidden);

View File

@@ -155,5 +155,25 @@ breaking changes to gr-change-actions wont be noticed.
});
});
});
test('change actions priority', function(done) {
var key1 = changeActions.add(changeActions.ActionType.REVISION, 'Bork!');
var key2 = changeActions.add(changeActions.ActionType.CHANGE, 'Squanch?');
flush(function() {
var buttons =
Polymer.dom(element.root).querySelectorAll('[data-action-key]');
assert.equal(buttons[0].getAttribute('data-action-key'), key1);
assert.equal(buttons[1].getAttribute('data-action-key'), key2);
changeActions.setActionPriority(
changeActions.ActionType.REVISION, key1, 10);
flush(function() {
buttons =
Polymer.dom(element.root).querySelectorAll('[data-action-key]');
assert.equal(buttons[0].getAttribute('data-action-key'), key2);
assert.equal(buttons[1].getAttribute('data-action-key'), key1);
done();
});
});
});
});
</script>