1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-30 10:42:41 +01:00
phorge-phorge/webroot/rsrc/js/application/herald/HeraldRuleEditor.js
epriestley fbfcc37531 Respect token limits for "Assign to" and custom datasource fields in Herald
Summary:
See PHI173. Currently, Herald has an "Assign to" action for tasks, and you can specify custom fields with datasource values (like users or projects) that have a limit (like 1 "Owner", or 12 "Jury Members").

Herald doesn't support these limits right now, so you can write `[ Assign to ][ X, Y, Z ]`. This just means "Assign to X", but make it more clear by actually enforcing the limit in the UI.

Test Plan:
  - Created a "projects" custom field with limit 1.
  - Tried to create actions that 'assign to' or 'set custom field to' more than one thing, got helpfully rebuffed by the UI.
  - Created an "add subscribers" action with more than one value.

Reviewers: amckinley

Reviewed By: amckinley

Differential Revision: https://secure.phabricator.com/D18887
2018-01-22 11:54:12 -08:00

423 lines
12 KiB
JavaScript

/**
* @requires multirow-row-manager
* javelin-install
* javelin-util
* javelin-dom
* javelin-stratcom
* javelin-json
* phabricator-prefab
* @provides herald-rule-editor
* @javelin
*/
JX.install('HeraldRuleEditor', {
construct : function(config) {
var root = JX.$(config.root);
this._root = root;
JX.DOM.listen(
root,
'click',
'create-condition',
JX.bind(this, this._onnewcondition));
JX.DOM.listen(
root,
'click',
'create-action',
JX.bind(this, this._onnewaction));
JX.DOM.listen(root, 'change', null, JX.bind(this, this._onchange));
JX.DOM.listen(root, 'submit', null, JX.bind(this, this._onsubmit));
var conditionsTable = JX.DOM.find(root, 'table', 'rule-conditions');
var actionsTable = JX.DOM.find(root, 'table', 'rule-actions');
this._conditionsRowManager = new JX.MultirowRowManager(conditionsTable);
this._conditionsRowManager.listen(
'row-removed',
JX.bind(this, function(row_id) {
delete this._config.conditions[row_id];
}));
this._actionsRowManager = new JX.MultirowRowManager(actionsTable);
this._actionsRowManager.listen(
'row-removed',
JX.bind(this, function(row_id) {
delete this._config.actions[row_id];
}));
this._conditionGetters = {};
this._conditionTypes = {};
this._actionGetters = {};
this._actionTypes = {};
this._config = config;
var conditions = this._config.conditions;
this._config.conditions = [];
var actions = this._config.actions;
this._config.actions = [];
this._renderConditions(conditions);
this._renderActions(actions);
},
members : {
_config : null,
_root : null,
_conditionGetters : null,
_conditionTypes : null,
_actionGetters : null,
_actionTypes : null,
_conditionsRowManager : null,
_actionsRowManager : null,
_onnewcondition : function(e) {
this._newCondition();
e.kill();
},
_onnewaction : function(e) {
this._newAction();
e.kill();
},
_onchange : function(e) {
var target = e.getTarget();
var row = e.getNode(JX.MultirowRowManager.getRowSigil());
if (!row) {
// Changing the "when all of / any of these..." dropdown.
return;
}
if (JX.Stratcom.hasSigil(target, 'field-select')) {
this._onfieldchange(row);
} else if (JX.Stratcom.hasSigil(target, 'condition-select')) {
this._onconditionchange(row);
} else if (JX.Stratcom.hasSigil(target, 'action-select')) {
this._onactionchange(row);
}
},
_onsubmit : function() {
var rule = JX.DOM.find(this._root, 'input', 'rule');
var k;
for (k in this._config.conditions) {
this._config.conditions[k][2] = this._getConditionValue(k);
}
for (k in this._config.actions) {
this._config.actions[k][1] = this._getActionTarget(k);
}
rule.value = JX.JSON.stringify({
conditions: this._config.conditions,
actions: this._config.actions
});
},
_getConditionValue : function(id) {
if (this._conditionGetters[id]) {
return this._conditionGetters[id]();
}
return this._config.conditions[id][2];
},
_getActionTarget : function(id) {
if (this._actionGetters[id]) {
return this._actionGetters[id]();
}
return this._config.actions[id][1];
},
_onactionchange : function(r) {
var target = JX.DOM.find(r, 'select', 'action-select');
var row_id = this._actionsRowManager.getRowID(r);
this._config.actions[row_id][0] = target.value;
var target_cell = JX.DOM.find(r, 'td', 'target-cell');
var target_input = this._renderTargetInputForRow(row_id);
JX.DOM.setContent(target_cell, target_input);
},
_onfieldchange : function(r) {
var target = JX.DOM.find(r, 'select', 'field-select');
var row_id = this._actionsRowManager.getRowID(r);
this._config.conditions[row_id][0] = target.value;
var condition_cell = JX.DOM.find(r, 'td', 'condition-cell');
var condition_select = this._renderSelect(
this._selectKeys(
this._config.info.conditions,
this._config.info.conditionMap[target.value]),
this._config.conditions[row_id][1],
'condition-select');
JX.DOM.setContent(condition_cell, condition_select);
this._onconditionchange(r);
var condition_name = this._config.conditions[row_id][1];
if (condition_name == 'unconditionally') {
JX.DOM.hide(condition_select);
}
},
_onconditionchange : function(r) {
var target = JX.DOM.find(r, 'select', 'condition-select');
var row_id = this._conditionsRowManager.getRowID(r);
this._config.conditions[row_id][1] = target.value;
var value_cell = JX.DOM.find(r, 'td', 'value-cell');
var value_input = this._renderValueInputForRow(row_id);
JX.DOM.setContent(value_cell, value_input);
},
_renderTargetInputForRow : function(row_id) {
var action = this._config.actions[row_id];
var type = this._config.info.targets[action[0]];
var input = this._buildInput(type);
var node = input[0];
var get_fn = input[1];
var set_fn = input[2];
if (node) {
JX.Stratcom.addSigil(node, 'action-target');
}
var old_type = this._actionTypes[row_id];
if (old_type == type || !old_type) {
set_fn(this._getActionTarget(row_id));
}
this._actionTypes[row_id] = type;
this._actionGetters[row_id] = get_fn;
return node;
},
_buildInput : function(type) {
var spec = this._config.info.valueMap[type];
var input;
var get_fn;
var set_fn;
switch (spec.control) {
case 'herald.control.none':
input = null;
get_fn = JX.bag;
set_fn = JX.bag;
break;
case 'herald.control.text':
input = JX.$N('input', {type: 'text'});
get_fn = function() { return input.value; };
set_fn = function(v) { input.value = v; };
break;
case 'herald.control.remarkup':
input = JX.$N('textarea');
get_fn = function() { return input.value; };
set_fn = function(v) { input.value = v; };
break;
case 'herald.control.select':
var options;
// NOTE: This is a hacky special case for "Another Herald Rule",
// which we don't currently generate normal options for.
if (spec.key == 'select.rule') {
options = this._config.template.rules;
} else {
options = spec.template.options;
}
input = this._renderSelect(options);
get_fn = function() { return input.value; };
set_fn = function(v) { input.value = v; };
if (spec.template.default) {
set_fn(spec.template.default);
}
break;
case 'herald.control.tokenizer':
var tokenizer = this._newTokenizer(spec.template.tokenizer);
input = tokenizer[0];
get_fn = tokenizer[1];
set_fn = tokenizer[2];
break;
default:
JX.$E('No rules to build control "' + spec.control + '".');
break;
}
return [input, get_fn, set_fn];
},
_renderValueInputForRow : function(row_id) {
var cond = this._config.conditions[row_id];
var type = this._config.info.values[cond[0]][cond[1]];
var input = this._buildInput(type);
var node = input[0];
var get_fn = input[1];
var set_fn = input[2];
if (node) {
JX.Stratcom.addSigil(node, 'condition-value');
}
var old_type = this._conditionTypes[row_id];
if (old_type == type || !old_type) {
set_fn(this._getConditionValue(row_id));
}
this._conditionTypes[row_id] = type;
this._conditionGetters[row_id] = get_fn;
return node;
},
_newTokenizer : function(spec) {
var tokenizerConfig = {
src: spec.datasourceURI,
placeholder: spec.placeholder,
browseURI: spec.browseURI,
limit: spec.limit
};
var build = JX.Prefab.newTokenizerFromTemplate(
this._config.template.markup,
tokenizerConfig);
build.tokenizer.start();
return [
build.node,
function() {
return build.tokenizer.getTokens();
},
function(map) {
for (var k in map) {
var v = map[k];
// The control value may be set from wire values from the server,
// or a transformed value from another control, or a bare string
// value from another control.
if (typeof v == 'string') {
v = v;
} else if (!v.hasOwnProperty('id')) {
v = JX.Prefab.transformDatasourceResults(v);
}
build.tokenizer.addToken(k, v);
}
}];
},
_selectKeys : function(map, keys) {
var r = {};
for (var ii = 0; ii < keys.length; ii++) {
r[keys[ii]] = map[keys[ii]];
}
return r;
},
_renderConditions : function(conditions) {
for (var k in conditions) {
this._newCondition(conditions[k]);
}
},
_newCondition : function(data) {
var row = this._conditionsRowManager.addRow([]);
var row_id = this._conditionsRowManager.getRowID(row);
var default_condition = [
this._config.default.field,
this._config.default.condition,
null
];
this._config.conditions[row_id] = data || default_condition;
var r = this._conditionsRowManager.updateRow(
row_id,
this._renderCondition(row_id));
this._onfieldchange(r);
},
_renderCondition : function(row_id) {
var groups = this._config.info.fields;
var attrs = {
sigil: 'field-select'
};
var field_select = this._renderGroupSelect(groups, attrs);
field_select.value = this._config.conditions[row_id][0];
var field_cell = JX.$N('td', {sigil: 'field-cell'}, field_select);
var condition_cell = JX.$N('td', {sigil: 'condition-cell'});
var value_cell = JX.$N('td', {className : 'value', sigil: 'value-cell'});
return [field_cell, condition_cell, value_cell];
},
_renderActions : function(actions) {
for (var k in actions) {
this._newAction(actions[k]);
delete actions[k];
}
},
_renderGroupSelect: function(groups, attrs) {
var optgroups = [];
for (var ii = 0; ii < groups.length; ii++) {
var group = groups[ii];
var options = [];
for (var k in group.options) {
options.push(JX.$N('option', {value: k}, group.options[k]));
}
optgroups.push(JX.$N('optgroup', {label: group.label}, options));
}
return JX.$N('select', attrs, optgroups);
},
_newAction : function(data) {
var default_action = [
this._config.default.action,
null
];
data = data || default_action;
var temprow = this._actionsRowManager.addRow([]);
var row_id = this._actionsRowManager.getRowID(temprow);
this._config.actions[row_id] = data;
var r = this._actionsRowManager.updateRow(row_id,
this._renderAction(data));
this._onactionchange(r);
},
_renderAction : function(action) {
var groups = this._config.info.actions;
var attrs = {
sigil: 'action-select'
};
var action_select = this._renderGroupSelect(groups, attrs);
action_select.value = action[0];
var action_cell = JX.$N('td', {sigil: 'action-cell'}, action_select);
var target_cell = JX.$N(
'td',
{className : 'target', sigil : 'target-cell'});
return [action_cell, target_cell];
},
_renderSelect : function(map, selected, sigil) {
var attrs = {
sigil : sigil
};
return JX.Prefab.renderSelect(map, selected, attrs);
}
}
});