1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-26 23:40:57 +01:00

Add "Change Priority", "Open / Close", "Comment" and "Assign" actions to Maniphest batch editor

Summary: I think these are all the actions which make any sense.

Test Plan:
  - Performed and verified each action through the batch editor.
  - Performed a large batch edit which applied each action type multiple times and verified the aggregate behavior was correct.

Reviewers: btrahan, cpiro

Reviewed By: btrahan

CC: aran, epriestley

Maniphest Tasks: T441

Differential Revision: https://secure.phabricator.com/D1971
This commit is contained in:
epriestley 2012-03-21 14:01:04 -07:00
parent 5c5ead2666
commit 6c412961eb
5 changed files with 205 additions and 88 deletions

View file

@ -536,7 +536,7 @@ celerity_register_resource_map(array(
),
'javelin-behavior-differential-dropdown-menus' =>
array(
'uri' => '/res/4bb3ae9a/rsrc/js/application/differential/behavior-dropdown-menus.js',
'uri' => '/res/2549921f/rsrc/js/application/differential/behavior-dropdown-menus.js',
'type' => 'js',
'requires' =>
array(
@ -695,7 +695,7 @@ celerity_register_resource_map(array(
),
'javelin-behavior-maniphest-batch-editor' =>
array(
'uri' => '/res/d7b7f061/rsrc/js/application/maniphest/behavior-batch-editor.js',
'uri' => '/res/d22661be/rsrc/js/application/maniphest/behavior-batch-editor.js',
'type' => 'js',
'requires' =>
array(
@ -704,10 +704,7 @@ celerity_register_resource_map(array(
2 => 'javelin-util',
3 => 'phabricator-prefab',
4 => 'multirow-row-manager',
5 => 'javelin-tokenizer',
6 => 'javelin-typeahead-preloaded-source',
7 => 'javelin-typeahead',
8 => 'javelin-json',
5 => 'javelin-json',
),
'disk' => '/rsrc/js/application/maniphest/behavior-batch-editor.js',
),
@ -1649,7 +1646,7 @@ celerity_register_resource_map(array(
),
'phabricator-prefab' =>
array(
'uri' => '/res/956c8474/rsrc/js/application/core/Prefab.js',
'uri' => '/res/2734e45f/rsrc/js/application/core/Prefab.js',
'type' => 'js',
'requires' =>
array(
@ -2033,7 +2030,7 @@ celerity_register_resource_map(array(
'uri' => '/res/pkg/551249fc/differential.pkg.css',
'type' => 'css',
),
'0d08d81b' =>
'9b256876' =>
array(
'name' => 'differential.pkg.js',
'symbols' =>
@ -2056,7 +2053,7 @@ celerity_register_resource_map(array(
15 => 'javelin-behavior-differential-dropdown-menus',
16 => 'javelin-behavior-buoyant',
),
'uri' => '/res/pkg/0d08d81b/differential.pkg.js',
'uri' => '/res/pkg/9b256876/differential.pkg.js',
'type' => 'js',
),
'61f9d480' =>
@ -2147,7 +2144,7 @@ celerity_register_resource_map(array(
'aphront-typeahead-control-css' => '05e42357',
'differential-changeset-view-css' => '551249fc',
'differential-core-view-css' => '551249fc',
'differential-inline-comment-editor' => '0d08d81b',
'differential-inline-comment-editor' => '9b256876',
'differential-local-commits-view-css' => '551249fc',
'differential-revision-add-comment-css' => '551249fc',
'differential-revision-comment-css' => '551249fc',
@ -2158,27 +2155,27 @@ celerity_register_resource_map(array(
'diffusion-commit-view-css' => '61f9d480',
'javelin-behavior' => '4fbae2af',
'javelin-behavior-aphront-basic-tokenizer' => '2af849fb',
'javelin-behavior-aphront-drag-and-drop' => '0d08d81b',
'javelin-behavior-aphront-drag-and-drop-textarea' => '0d08d81b',
'javelin-behavior-aphront-drag-and-drop' => '9b256876',
'javelin-behavior-aphront-drag-and-drop-textarea' => '9b256876',
'javelin-behavior-aphront-form-disable-on-submit' => '21d01ed8',
'javelin-behavior-buoyant' => '0d08d81b',
'javelin-behavior-differential-accept-with-errors' => '0d08d81b',
'javelin-behavior-differential-add-reviewers-and-ccs' => '0d08d81b',
'javelin-behavior-differential-comment-jump' => '0d08d81b',
'javelin-behavior-differential-diff-radios' => '0d08d81b',
'javelin-behavior-differential-dropdown-menus' => '0d08d81b',
'javelin-behavior-differential-edit-inline-comments' => '0d08d81b',
'javelin-behavior-differential-feedback-preview' => '0d08d81b',
'javelin-behavior-differential-keyboard-navigation' => '0d08d81b',
'javelin-behavior-differential-populate' => '0d08d81b',
'javelin-behavior-differential-show-more' => '0d08d81b',
'javelin-behavior-buoyant' => '9b256876',
'javelin-behavior-differential-accept-with-errors' => '9b256876',
'javelin-behavior-differential-add-reviewers-and-ccs' => '9b256876',
'javelin-behavior-differential-comment-jump' => '9b256876',
'javelin-behavior-differential-diff-radios' => '9b256876',
'javelin-behavior-differential-dropdown-menus' => '9b256876',
'javelin-behavior-differential-edit-inline-comments' => '9b256876',
'javelin-behavior-differential-feedback-preview' => '9b256876',
'javelin-behavior-differential-keyboard-navigation' => '9b256876',
'javelin-behavior-differential-populate' => '9b256876',
'javelin-behavior-differential-show-more' => '9b256876',
'javelin-behavior-maniphest-batch-selector' => '86fc0b0c',
'javelin-behavior-maniphest-transaction-controls' => '86fc0b0c',
'javelin-behavior-maniphest-transaction-expand' => '86fc0b0c',
'javelin-behavior-maniphest-transaction-preview' => '86fc0b0c',
'javelin-behavior-phabricator-autofocus' => '21d01ed8',
'javelin-behavior-phabricator-keyboard-shortcuts' => '21d01ed8',
'javelin-behavior-phabricator-object-selector' => '0d08d81b',
'javelin-behavior-phabricator-object-selector' => '9b256876',
'javelin-behavior-phabricator-watch-anchor' => '21d01ed8',
'javelin-behavior-refresh-csrf' => '21d01ed8',
'javelin-behavior-workflow' => '21d01ed8',
@ -2207,7 +2204,7 @@ celerity_register_resource_map(array(
'phabricator-core-buttons-css' => '05e42357',
'phabricator-core-css' => '05e42357',
'phabricator-directory-css' => '05e42357',
'phabricator-drag-and-drop-file-upload' => '0d08d81b',
'phabricator-drag-and-drop-file-upload' => '9b256876',
'phabricator-dropdown-menu' => '21d01ed8',
'phabricator-jump-nav' => '05e42357',
'phabricator-keyboard-shortcut' => '21d01ed8',
@ -2216,7 +2213,7 @@ celerity_register_resource_map(array(
'phabricator-object-selector-css' => '551249fc',
'phabricator-paste-file-upload' => '21d01ed8',
'phabricator-remarkup-css' => '05e42357',
'phabricator-shaped-request' => '0d08d81b',
'phabricator-shaped-request' => '9b256876',
'phabricator-standard-page-view' => '05e42357',
'phabricator-transaction-view-css' => '05e42357',
'syntax-highlighting-css' => '05e42357',

View file

@ -45,8 +45,10 @@ final class ManiphestBatchEditController extends ManiphestController {
}
}
$task_ids = implode(',', mpull($tasks, 'getID'));
return id(new AphrontRedirectResponse())
->setURI('/maniphest/');
->setURI('/maniphest/view/custom/?s=oc&tasks='.$task_ids);
}
$panel = new AphrontPanelView();
@ -71,9 +73,19 @@ final class ManiphestBatchEditController extends ManiphestController {
'root' => 'maniphest-batch-edit-form',
'tokenizerTemplate' => $template,
'sources' => array(
'project' => '/typeahead/common/projects/',
'project' => array(
'src' => '/typeahead/common/projects/',
'placeholder' => 'Type a project name...',
),
'owner' => array(
'src' => '/typeahead/common/searchowner/',
'placeholder' => 'Type a user name...',
'limit' => 1,
),
),
'input' => 'batch-form-actions',
'priorityMap' => ManiphestTaskPriority::getTaskPriorityMap(),
'statusMap' => ManiphestTaskStatus::getTaskStatusMap(),
));
$form = new AphrontFormView();
@ -138,34 +150,96 @@ final class ManiphestBatchEditController extends ManiphestController {
}
private function buildTransactions($actions, ManiphestTask $task) {
$template = new ManiphestTransaction();
$template->setAuthorPHID($this->getRequest()->getUser()->getPHID());
// TODO: Set content source to "batch edit".
$project_xaction = null;
$value_map = array();
$type_map = array(
'add_comment' => ManiphestTransactionType::TYPE_NONE,
'assign' => ManiphestTransactionType::TYPE_OWNER,
'status' => ManiphestTransactionType::TYPE_STATUS,
'priority' => ManiphestTransactionType::TYPE_PRIORITY,
'add_project' => ManiphestTransactionType::TYPE_PROJECTS,
'remove_project' => ManiphestTransactionType::TYPE_PROJECTS,
);
$xactions = array();
foreach ($actions as $action) {
$value = $action['value'];
switch ($action['action']) {
case 'add_project':
case 'remove_project':
if (empty($type_map[$action['action']])) {
throw new Exception("Unknown batch edit action '{$action}'!");
}
$is_remove = ($action['action'] == 'remove_project');
$type = $type_map[$action['action']];
// We want to roll up all of the add and remove actions into a single
// transaction so we don't restore removed projects or remove added
// projects in subsequent transactions. If we've already made some
// modification to a task's projects, use that as the starting point.
// Otherwise, start with the value on the task.
// Figure out the current value, possibly after modifications by other
// batch actions of the same type. For example, if the user chooses to
// "Add Comment" twice, we should add both comments. More notably, if the
// user chooses "Remove Project..." and also "Add Project...", we should
// avoid restoring the removed project in the second transaction.
if ($project_xaction) {
$xaction = $project_xaction;
$current = $xaction->getNewValue();
} else {
if (array_key_exists($type, $value_map)) {
$current = $value_map[$type];
} else {
switch ($type) {
case ManiphestTransactionType::TYPE_NONE:
$current = null;
break;
case ManiphestTransactionType::TYPE_OWNER:
$current = $task->getOwnerPHID();
break;
case ManiphestTransactionType::TYPE_STATUS:
$current = $task->getStatus();
break;
case ManiphestTransactionType::TYPE_PRIORITY:
$current = $task->getPriority();
break;
case ManiphestTransactionType::TYPE_PROJECTS:
$current = $task->getProjectPHIDs();
break;
}
}
// Check if the value is meaningful / provided, and normalize it if
// necessary. This discards, e.g., empty comments and empty owner
// changes.
$value = $action['value'];
switch ($type) {
case ManiphestTransactionType::TYPE_NONE:
if (!strlen($value)) {
continue 2;
}
break;
case ManiphestTransactionType::TYPE_OWNER:
if (empty($value)) {
continue 2;
}
$value = head($value);
if ($value === ManiphestTaskOwner::OWNER_UP_FOR_GRABS) {
$value = null;
}
break;
case ManiphestTransactionType::TYPE_PROJECTS:
if (empty($value)) {
continue 2;
}
break;
}
// If the edit doesn't change anything, go to the next action.
if ($value == $current) {
continue;
}
// Apply the value change; for most edits this is just replacement, but
// some need to merge the current and edited values (add/remove project).
switch ($type) {
case ManiphestTransactionType::TYPE_NONE:
if (strlen($current)) {
$value = $current."\n\n".$value;
}
break;
case ManiphestTransactionType::TYPE_PROJECTS:
$is_remove = ($action['action'] == 'remove_project');
$current = array_fill_keys($current, true);
$value = array_fill_keys($value, true);
@ -190,25 +264,35 @@ final class ManiphestBatchEditController extends ManiphestController {
}
if (!$did_something) {
break;
continue 2;
}
// If this is the first project-related transaction, create a new
// transaction object and populate it with appropriate defaults.
if (!$project_xaction) {
$xaction = clone $template;
$xaction->setTransactionType(
ManiphestTransactionType::TYPE_PROJECTS);
$xactions[] = $xaction;
$project_xaction = $xaction;
}
$new = array_keys($new);
$xaction->setNewValue($new);
$value = array_keys($new);
break;
}
$value_map[$type] = $value;
}
$template = new ManiphestTransaction();
$template->setAuthorPHID($this->getRequest()->getUser()->getPHID());
// TODO: Set content source to "batch edit".
foreach ($value_map as $type => $value) {
$xaction = clone $template;
$xaction->setTransactionType($type);
switch ($type) {
case ManiphestTransactionType::TYPE_NONE:
$xaction->setComments($value);
break;
default:
$xaction->setNewValue($value);
break;
}
$xactions[] = $xaction;
}
return $xactions;

View file

@ -7,6 +7,9 @@
phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/maniphest/constants/owner');
phutil_require_module('phabricator', 'applications/maniphest/constants/priority');
phutil_require_module('phabricator', 'applications/maniphest/constants/status');
phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype');
phutil_require_module('phabricator', 'applications/maniphest/controller/base');
phutil_require_module('phabricator', 'applications/maniphest/editor/transaction');

View file

@ -35,7 +35,8 @@ JX.install('Prefab', {
* Build a Phabricator tokenizer out of a configuration with application
* sorting, datasource and placeholder rules.
*
* - `id` Root tokenizer ID.
* - `id` Root tokenizer ID (alternatively, pass `root`).
* - `root` Root tokenizer node (replaces `id`).
* - `src` Datasource URI.
* - `ondemand` Optional, use an ondemand source.
* - `value` Optional, initial value.
@ -45,7 +46,7 @@ JX.install('Prefab', {
*
*/
buildTokenizer : function(config) {
var root = JX.$(config.id);
var root = config.root || JX.$(config.id);
var datasource;
if (config.ondemand) {

View file

@ -5,9 +5,6 @@
* javelin-util
* phabricator-prefab
* multirow-row-manager
* javelin-tokenizer
* javelin-typeahead-preloaded-source
* javelin-typeahead
* javelin-json
*/
@ -25,29 +22,64 @@ JX.behavior('maniphest-batch-editor', function(config) {
var action_select = JX.Prefab.renderSelect(
{
'add_project': 'Add Projects',
'remove_project' : 'Remove Projects'/*,
'remove_project' : 'Remove Projects',
'priority': 'Change Priority',
'add_comment': 'Comment',
'status': 'Open / Close',
'assign': 'Assign'*/
'add_comment': 'Comment',
'assign': 'Assign'
});
var tokenizer = build_tokenizer(config.sources.project)
var proj_tokenizer = build_tokenizer(config.sources.project);
var owner_tokenizer = build_tokenizer(config.sources.owner);
var r = [];
r.push([null, action_select]);
r.push(['batch-editor-input', tokenizer.template]);
var priority_select = JX.Prefab.renderSelect(config.priorityMap);
var status_select = JX.Prefab.renderSelect(config.statusMap);
var comment_input = JX.$N('input', {style: {width: '100%'}});
for (var ii = 0; ii < r.length; ii++) {
r[ii] = JX.$N('td', {className : r[ii][0]}, r[ii][1]);
}
var cell = JX.$N('td', {className: 'batch-editor-input'});
var vfunc = null;
function update() {
switch (action_select.value) {
case 'add_project':
case 'remove_project':
JX.DOM.setContent(cell, proj_tokenizer.template);
vfunc = function() {
return JX.keys(proj_tokenizer.object.getTokens());
};
break;
case 'assign':
JX.DOM.setContent(cell, owner_tokenizer.template);
vfunc = function() {
return JX.keys(owner_tokenizer.object.getTokens());
};
break;
case 'add_comment':
JX.DOM.setContent(cell, comment_input);
vfunc = function() {
return comment_input.value;
};
break;
case 'priority':
JX.DOM.setContent(cell, priority_select);
vfunc = function() { return priority_select.value; };
break;
case 'status':
JX.DOM.setContent(cell, status_select);
vfunc = function() { return status_select.value; };
break;
}
};
JX.DOM.listen(action_select, 'change', null, update);
update();
return {
nodes : r,
nodes : [JX.$N('td', {}, action_select), cell],
dataCallback : function() {
return {
action: action_select.value,
value: JX.keys(tokenizer.object.getTokens())
value: vfunc()
};
}
};
@ -95,18 +127,18 @@ JX.behavior('maniphest-batch-editor', function(config) {
delete action_rows[row_id];
});
function build_tokenizer(source) {
function build_tokenizer(tconfig) {
var template = JX.$N('div', JX.$H(config.tokenizerTemplate)).firstChild;
template.id = '';
var datasource = new JX.TypeaheadPreloadedSource(source);
var typeahead = new JX.Typeahead(template);
typeahead.setDatasource(datasource);
var tokenizer = new JX.Tokenizer(template);
tokenizer.setTypeahead(typeahead);
tokenizer.start();
var build_config = JX.copy({}, tconfig);
build_config.root = template;
var built = JX.Prefab.buildTokenizer(build_config);
built.tokenizer.start();
return {
object: tokenizer,
object: built.tokenizer,
template: template
};
}