2011-03-23 01:08:08 +01:00
|
|
|
/**
|
|
|
|
* @requires multirow-row-manager
|
Bring Javelin into Phabricator via git submodule, not copy-and-paste
Summary:
Javelin is currently embedded in Phabricator via copy-and-paste of prebuilt
packages. This is not so great.
Pull it in as a submodule instead and make all the Phabriator resources declare
proper dependency trees. Add Javelin linting.
Test Plan:
I tried to run through pretty much all the JS functionality on the site. This is
still a high-risk change, but I did a pretty thorough test
Differential: inline comments, revealing diffs, list tokenizers, comment
preview, editing/deleting comments, add review action.
Maniphest: list tokenizer, comment actions
Herald: rule editing, tokenizers, add/remove rows
Reviewed By: tomo
Reviewers: aran, tomo, mroch, jungejason, tuomaspelkonen
CC: aran, tomo, epriestley
Differential Revision: 223
2011-05-04 00:11:55 +02:00
|
|
|
* javelin-install
|
|
|
|
* javelin-util
|
|
|
|
* javelin-dom
|
|
|
|
* javelin-stratcom
|
|
|
|
* javelin-json
|
2011-07-16 07:30:55 +02:00
|
|
|
* phabricator-prefab
|
2011-03-23 01:08:08 +01:00
|
|
|
* @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);
|
|
|
|
}
|
|
|
|
},
|
2014-06-23 19:27:47 +02:00
|
|
|
_onsubmit : function() {
|
2011-03-23 01:08:08 +01:00
|
|
|
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);
|
|
|
|
}
|
2011-07-05 23:17:38 +02:00
|
|
|
rule.value = JX.JSON.stringify({
|
2011-03-23 01:08:08 +01:00
|
|
|
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);
|
2013-10-04 02:53:12 +02:00
|
|
|
|
|
|
|
var condition_name = this._config.conditions[row_id][1];
|
2013-10-05 00:17:01 +02:00
|
|
|
if (condition_name == 'unconditionally') {
|
|
|
|
JX.DOM.hide(condition_select);
|
2013-10-04 02:53:12 +02:00
|
|
|
}
|
2011-03-23 01:08:08 +01:00
|
|
|
},
|
|
|
|
_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;
|
|
|
|
},
|
|
|
|
|
2015-07-16 23:12:00 +02:00
|
|
|
_buildStandardInput: function(spec) {
|
|
|
|
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.select':
|
|
|
|
input = this._renderSelect(spec.template.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, true);
|
|
|
|
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];
|
|
|
|
},
|
|
|
|
|
2011-03-23 01:08:08 +01:00
|
|
|
_buildInput : function(type) {
|
2015-07-16 23:12:00 +02:00
|
|
|
if (this._config.info.valueMap[type]) {
|
|
|
|
return this._buildStandardInput(this._config.info.valueMap[type]);
|
|
|
|
}
|
|
|
|
|
2011-03-23 01:08:08 +01:00
|
|
|
var input;
|
|
|
|
var get_fn;
|
|
|
|
var set_fn;
|
|
|
|
switch (type) {
|
|
|
|
case 'rule':
|
|
|
|
input = this._renderSelect(this._config.template.rules);
|
|
|
|
get_fn = function() { return input.value; };
|
|
|
|
set_fn = function(v) { input.value = v; };
|
|
|
|
break;
|
|
|
|
case 'email':
|
2011-03-23 04:41:02 +01:00
|
|
|
case 'user':
|
2011-03-23 01:08:08 +01:00
|
|
|
case 'repository':
|
|
|
|
case 'tag':
|
|
|
|
case 'package':
|
Allow Herald to trigger audits for users or projects
Summary:
Allows you to write a commit rule that triggers an audit by a user (personal
rules) or a project (global rules).
Mostly this is trying to make auditing more lightweight and accessible in
environments where setting up Owners packages doesn't make sense.
For instance, Disqus wants a rule like "trigger an audit for everything that
didn't have a Differential revision". While not necessarily scalable, this is a
perfectly reasonable rule for a small company, but a lot of work to implement
with Owners (and you'll get a lot of collateral damage if you don't make every
committer a project owner).
Instead, they can create a project called 'Unreviewed Commits' and write a rule
like:
- When: Differential revision does not exist
- Action: Trigger an Audit for project: "Unreviewed Commits"
Then whoever cares can join that project and they'll see those audits in their
queue, and when they approve/raise on commits their actions will affect the
project audit.
Similarly, if I want to look at all commits that match some other rule (say,
XSS) but only want to do it like once a month, I can just set up an audit rule
and go through the queue when I feel like it.
NOTE: This abuses the 'packagePHID' field to also store user and project PHIDs.
Through the magic of handles, this (apparently) works fine for now; I'll do a
big schema patch soon but have several other edits I want to make at the same
time.
Also:
- Adds an "active" fiew for /audit/, eventually this will be like the
Differential "active" view (stuff that is relevant to you right now).
- On commits, highlight triggered audits you are responsible for.
Test Plan: Added personal and global audit triggers to Herald, reparsed some
commits with --herald, got audits. Browsed all audit interfaces to make sure
nothing exploded. Viewed a commit where I was responsible for only some audits.
Performed audits and made sure the triggers I am supposed to be responsible for
updated properly.
Reviewers: btrahan, jungejason
Reviewed By: jungejason
CC: aran, epriestley
Maniphest Tasks: T904
Differential Revision: https://secure.phabricator.com/D1690
2012-02-27 18:36:30 +01:00
|
|
|
case 'project':
|
2013-10-05 19:36:26 +02:00
|
|
|
case 'userorproject':
|
2013-11-09 01:48:17 +01:00
|
|
|
case 'buildplan':
|
2014-02-06 20:42:31 +01:00
|
|
|
case 'taskpriority':
|
2014-11-12 21:54:29 +01:00
|
|
|
case 'taskstatus':
|
Allow Herald to "Require legal signatures" for reviews
Summary:
Ref T3116. Add a Herald action "Require legal signatures" which requires revision authors to accept legal agreements before their revisions can be accepted.
- Herald will check which documents the author has signed, and trigger a "you have to sign X, Y, Z" for other documents.
- If the author has already signed everything, we don't spam the revision -- basically, this only triggers when signatures are missing.
- The UI will show which documents must be signed and warn that the revision can't be accepted until they're completed.
- Users aren't allowed to "Accept" the revision until documents are cleared.
Fixes T1157. The original install making the request (Hive) no longer uses Phabricator, and this satisfies our requirements.
Test Plan:
- Added a Herald rule.
- Created a revision, saw the rule trigger.
- Viewed as author and non-author, saw field UI (generic for non-author, specific for author), transaction UI, and accept-warning UI.
- Tried to accept revision.
- Signed document, saw UI update. Note that signatures don't currently //push// an update to the revision, but could eventually (like blocking tasks work).
- Accepted revision.
- Created another revision, saw rules not add the document (since it's already signed, this is the "no spam" case).
Reviewers: btrahan, chad
Reviewed By: chad
Subscribers: asherkin, epriestley
Maniphest Tasks: T1157, T3116
Differential Revision: https://secure.phabricator.com/D9771
2014-06-29 16:53:53 +02:00
|
|
|
case 'legaldocuments':
|
2015-01-29 23:15:38 +01:00
|
|
|
case 'applicationemail':
|
2015-06-11 19:14:06 +02:00
|
|
|
case 'space':
|
2011-03-23 01:08:08 +01:00
|
|
|
var tokenizer = this._newTokenizer(type);
|
|
|
|
input = tokenizer[0];
|
|
|
|
get_fn = tokenizer[1];
|
|
|
|
set_fn = tokenizer[2];
|
|
|
|
break;
|
|
|
|
default:
|
Update form styles, implement in many places
Summary:
This creates a common form look and feel across the site. I spent a bit of time working out a number of kinks in our various renderings. Some things:
- Font Styles are correctly applied for form elements now.
- Everything lines up!
- Selects are larger, easier to read, interact.
- Inputs have been squared.
- Consistant CSS applied glow (try it!)
- Improved Mobile Responsiveness
- CSS applied to all form elements, not just Aphront
- Many other minor tweaks.
I tried to hit as many high profile forms as possible in an effort to increase consistency. Stopped for now and will follow up after this lands. I know Evan is not a super fan of the glow, but after working with it for a week, it's way cleaner and responsive than the OS controls. Give it a try.
Test Plan: Tested many applications, forms, mobile and tablet.
Reviewers: epriestley, btrahan
Reviewed By: epriestley
CC: aran, Korvin
Differential Revision: https://secure.phabricator.com/D5860
2013-05-07 23:07:06 +02:00
|
|
|
input = JX.$N('input', {type: 'text'});
|
2011-03-23 01:08:08 +01:00
|
|
|
get_fn = function() { return input.value; };
|
|
|
|
set_fn = function(v) { input.value = v; };
|
|
|
|
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;
|
|
|
|
},
|
|
|
|
|
2015-07-16 23:12:00 +02:00
|
|
|
_newTokenizer : function(type, is_config) {
|
|
|
|
var tokenizerConfig;
|
|
|
|
|
|
|
|
if (is_config) {
|
|
|
|
tokenizerConfig = type;
|
|
|
|
} else {
|
|
|
|
tokenizerConfig = {
|
|
|
|
src : this._config.template.source[type].uri,
|
|
|
|
placeholder: this._config.template.source[type].placeholder,
|
|
|
|
browseURI: this._config.template.source[type].browseURI,
|
|
|
|
icons : this._config.template.icons,
|
|
|
|
username : this._config.username
|
|
|
|
};
|
|
|
|
}
|
2011-03-23 01:08:08 +01:00
|
|
|
|
2015-04-16 23:46:10 +02:00
|
|
|
var build = JX.Prefab.newTokenizerFromTemplate(
|
|
|
|
this._config.template.markup,
|
|
|
|
tokenizerConfig);
|
2014-04-10 21:38:15 +02:00
|
|
|
build.tokenizer.start();
|
2011-03-23 01:08:08 +01:00
|
|
|
|
|
|
|
return [
|
2015-04-16 23:46:10 +02:00
|
|
|
build.node,
|
2011-03-23 01:08:08 +01:00
|
|
|
function() {
|
2014-04-10 21:38:15 +02:00
|
|
|
return build.tokenizer.getTokens();
|
2011-03-23 01:08:08 +01:00
|
|
|
},
|
|
|
|
function(map) {
|
|
|
|
for (var k in map) {
|
2014-04-10 21:38:15 +02:00
|
|
|
build.tokenizer.addToken(k, map[k]);
|
2011-03-23 01:08:08 +01:00
|
|
|
}
|
|
|
|
}];
|
|
|
|
},
|
|
|
|
_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);
|
|
|
|
this._config.conditions[row_id] = data || [null, null, ''];
|
|
|
|
var r = this._conditionsRowManager.updateRow(
|
|
|
|
row_id,
|
|
|
|
this._renderCondition(row_id));
|
|
|
|
|
|
|
|
this._onfieldchange(r);
|
|
|
|
},
|
|
|
|
_renderCondition : function(row_id) {
|
|
|
|
var field_select = this._renderSelect(
|
|
|
|
this._config.info.fields,
|
|
|
|
this._config.conditions[row_id][0],
|
|
|
|
'field-select');
|
|
|
|
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];
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_newAction : function(data) {
|
|
|
|
data = data || [];
|
|
|
|
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 action_select = this._renderSelect(
|
|
|
|
this._config.info.actions,
|
|
|
|
action[0],
|
|
|
|
'action-select');
|
|
|
|
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) {
|
2011-07-16 07:30:55 +02:00
|
|
|
var attrs = {
|
|
|
|
sigil : sigil
|
|
|
|
};
|
|
|
|
return JX.Prefab.renderSelect(map, selected, attrs);
|
2011-03-23 01:08:08 +01:00
|
|
|
}
|
|
|
|
}
|
2011-03-23 01:19:52 +01:00
|
|
|
});
|