Page MenuHomePhabricator

D9211.diff
No OneTemporary

D9211.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -407,6 +407,7 @@
'rsrc/js/application/policy/behavior-policy-control.js' => '71b4cbcc',
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '263aeb8c',
'rsrc/js/application/ponder/behavior-votebox.js' => '327dbe61',
+ 'rsrc/js/application/projects/behavior-boards-filter.js' => '22f113af',
'rsrc/js/application/projects/behavior-project-boards.js' => 'd8e135db',
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
'rsrc/js/application/releeph/releeph-preview-branch.js' => '9eb2cedb',
@@ -544,6 +545,7 @@
'javelin-behavior-audio-source' => '59b251eb',
'javelin-behavior-audit-preview' => 'be81801d',
'javelin-behavior-balanced-payment-form' => '3b3e1664',
+ 'javelin-behavior-boards-filter' => '22f113af',
'javelin-behavior-config-reorder-fields' => '938aed89',
'javelin-behavior-conpherence-menu' => '7ee23816',
'javelin-behavior-conpherence-pontificate' => '53f6f2dd',
@@ -1004,6 +1006,13 @@
5 => 'javelin-json',
6 => 'phabricator-prefab',
),
+ '22f113af' =>
+ array(
+ 0 => 'javelin-behavior',
+ 1 => 'javelin-dom',
+ 2 => 'javelin-stratcom',
+ 3 => 'phuix-dropdown-menu',
+ ),
'263aeb8c' =>
array(
0 => 'javelin-behavior',
diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php
--- a/src/applications/maniphest/query/ManiphestTaskQuery.php
+++ b/src/applications/maniphest/query/ManiphestTaskQuery.php
@@ -95,6 +95,22 @@
return $this;
}
+ /**
+ * Add an additional "all projects" constraint to existing filters.
+ *
+ * This is used by boards to supplement queries.
+ *
+ * @param list<phid> List of project PHIDs to add to any existing constriant.
+ * @return this
+ */
+ public function addWithAllProjects(array $projects) {
+ if ($this->projectPHIDs === null) {
+ $this->projectPHIDs = array();
+ }
+
+ return $this->withAllProjects(array_merge($this->projectPHIDs, $projects));
+ }
+
public function withoutProjects(array $projects) {
$this->xprojectPHIDs = $projects;
return $this;
diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php
--- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php
+++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php
@@ -4,6 +4,26 @@
extends PhabricatorApplicationSearchEngine {
private $showBatchControls;
+ private $baseURI;
+ private $isBoardView;
+
+ public function setIsBoardView($is_board_view) {
+ $this->isBoardView = $is_board_view;
+ return $this;
+ }
+
+ public function getIsBoardView() {
+ return $this->isBoardView;
+ }
+
+ public function setBaseURI($base_uri) {
+ $this->baseURI = $base_uri;
+ return $this;
+ }
+
+ public function getBaseURI() {
+ return $this->baseURI;
+ }
public function setShowBatchControls($show_batch_controls) {
$this->showBatchControls = $show_batch_controls;
@@ -301,14 +321,20 @@
->setDatasource('/typeahead/common/projects/')
->setName('allProjects')
->setLabel(pht('In All Projects'))
- ->setValue($all_project_handles))
- ->appendChild(
- id(new AphrontFormCheckboxControl())
- ->addCheckbox(
- 'withNoProject',
- 1,
- pht('Show only tasks with no projects.'),
- $with_no_projects))
+ ->setValue($all_project_handles));
+
+ if (!$this->getIsBoardView()) {
+ $form
+ ->appendChild(
+ id(new AphrontFormCheckboxControl())
+ ->addCheckbox(
+ 'withNoProject',
+ 1,
+ pht('Show only tasks with no projects.'),
+ $with_no_projects));
+ }
+
+ $form
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/projects/')
@@ -340,19 +366,25 @@
->setLabel(pht('Subscribers'))
->setValue($subscriber_handles))
->appendChild($status_control)
- ->appendChild($priority_control)
- ->appendChild(
- id(new AphrontFormSelectControl())
- ->setName('group')
- ->setLabel(pht('Group By'))
- ->setValue($saved->getParameter('group'))
- ->setOptions($this->getGroupOptions()))
- ->appendChild(
- id(new AphrontFormSelectControl())
- ->setName('order')
- ->setLabel(pht('Order By'))
- ->setValue($saved->getParameter('order'))
- ->setOptions($this->getOrderOptions()))
+ ->appendChild($priority_control);
+
+ if (!$this->getIsBoardView()) {
+ $form
+ ->appendChild(
+ id(new AphrontFormSelectControl())
+ ->setName('group')
+ ->setLabel(pht('Group By'))
+ ->setValue($saved->getParameter('group'))
+ ->setOptions($this->getGroupOptions()))
+ ->appendChild(
+ id(new AphrontFormSelectControl())
+ ->setName('order')
+ ->setLabel(pht('Order By'))
+ ->setValue($saved->getParameter('order'))
+ ->setOptions($this->getOrderOptions()));
+ }
+
+ $form
->appendChild(
id(new AphrontFormTextControl())
->setName('fulltext')
@@ -382,15 +414,20 @@
'modifiedEnd',
pht('Updated Before'));
- $form
- ->appendChild(
- id(new AphrontFormTextControl())
- ->setName('limit')
- ->setLabel(pht('Page Size'))
- ->setValue($saved->getParameter('limit', 100)));
+ if (!$this->getIsBoardView()) {
+ $form
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setName('limit')
+ ->setLabel(pht('Page Size'))
+ ->setValue($saved->getParameter('limit', 100)));
+ }
}
protected function getURI($path) {
+ if ($this->baseURI) {
+ return $this->baseURI.$path;
+ }
return '/maniphest/'.$path;
}
diff --git a/src/applications/project/application/PhabricatorApplicationProject.php b/src/applications/project/application/PhabricatorApplicationProject.php
--- a/src/applications/project/application/PhabricatorApplicationProject.php
+++ b/src/applications/project/application/PhabricatorApplicationProject.php
@@ -51,7 +51,10 @@
'picture/(?P<id>[1-9]\d*)/' =>
'PhabricatorProjectEditPictureController',
'create/' => 'PhabricatorProjectCreateController',
- 'board/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectBoardViewController',
+ 'board/(?P<id>[1-9]\d*)/'.
+ '(?P<filter>filter/)?'.
+ '(?:query/(?P<queryKey>[^/]+)/)?' =>
+ 'PhabricatorProjectBoardViewController',
'move/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectMoveController',
'board/(?P<projectID>[1-9]\d*)/edit/(?:(?P<id>\d+)/)?'
=> 'PhabricatorProjectBoardEditController',
diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php
--- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php
+++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php
@@ -5,6 +5,8 @@
private $id;
private $handles;
+ private $queryKey;
+ private $filter;
public function shouldAllowPublic() {
return true;
@@ -12,6 +14,8 @@
public function willProcessRequest(array $data) {
$this->id = $data['id'];
+ $this->queryKey = idx($data, 'queryKey');
+ $this->filter = (bool)idx($data, 'filter');
}
public function processRequest() {
@@ -50,12 +54,63 @@
ksort($columns);
- $tasks = id(new ManiphestTaskQuery())
+ $board_uri = $this->getApplicationURI('board/'.$project->getID().'/');
+
+ $engine = id(new ManiphestTaskSearchEngine())
->setViewer($viewer)
- ->withAllProjects(array($project->getPHID()))
- ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants())
+ ->setBaseURI($board_uri)
+ ->setIsBoardView(true);
+
+ if ($request->isFormPost()) {
+ $saved = $engine->buildSavedQueryFromRequest($request);
+ $engine->saveQuery($saved);
+ return id(new AphrontRedirectResponse())->setURI(
+ $engine->getQueryResultsPageURI($saved->getQueryKey()));
+ }
+
+ $query_key = $this->queryKey;
+ if (!$query_key) {
+ $query_key = 'open';
+ }
+
+ $custom_query = null;
+ if ($engine->isBuiltinQuery($query_key)) {
+ $saved = $engine->buildSavedQueryFromBuiltin($query_key);
+ } else {
+ $saved = id(new PhabricatorSavedQueryQuery())
+ ->setViewer($viewer)
+ ->withQueryKeys(array($query_key))
+ ->executeOne();
+
+ if (!$saved) {
+ return new Aphront404Response();
+ }
+
+ $custom_query = $saved;
+ }
+
+ if ($this->filter) {
+ $filter_form = id(new AphrontFormView())
+ ->setUser($viewer);
+ $engine->buildSearchForm($filter_form, $saved);
+
+ return $this->newDialog()
+ ->setWidth(AphrontDialogView::WIDTH_FULL)
+ ->setTitle(pht('Advanced Filter'))
+ ->appendChild($filter_form->buildLayoutView())
+ ->setSubmitURI($board_uri)
+ ->addSubmitButton(pht('Apply Filter'))
+ ->addCancelButton($board_uri);
+ }
+
+ $task_query = $engine->buildQueryFromSavedQuery($saved);
+
+ $tasks = $task_query
+ ->addWithAllProjects(array($project->getPHID()))
->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY)
+ ->setViewer($viewer)
->execute();
+
$tasks = mpull($tasks, null, 'getPHID');
$task_phids = array_keys($tasks);
@@ -166,6 +221,87 @@
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit);
+ Javelin::initBehavior(
+ 'boards-filter',
+ array(
+ ));
+
+ $filter_icon = id(new PHUIIconView())
+ ->setIconFont('fa-search-plus bluegrey');
+
+ $named = array(
+ 'open' => pht('Open Tasks'),
+ 'all' => pht('All Tasks'),
+ );
+
+ if ($viewer->isLoggedIn()) {
+ $named['assigned'] = pht('Assigned to Me');
+ }
+
+ if ($custom_query) {
+ $named[$custom_query->getQueryKey()] = pht('Custom Filter');
+ }
+
+ $items = array();
+ foreach ($named as $key => $name) {
+ $is_selected = ($key == $query_key);
+ if ($is_selected) {
+ $active_filter = $name;
+ }
+
+ $is_custom = false;
+ if ($custom_query) {
+ $is_custom = ($key == $custom_query->getQueryKey());
+ }
+
+ $item = id(new PhabricatorActionView())
+ ->setIcon('fa-search')
+ ->setSelected($is_selected)
+ ->setName($name);
+
+ if ($is_custom) {
+ $item->setHref(
+ $this->getApplicationURI(
+ 'board/'.$this->id.'/filter/query/'.$key.'/'));
+ $item->setWorkflow(true);
+ } else {
+ $item->setHref($engine->getQueryResultsPageURI($key));
+ }
+
+ $items[] = $item;
+ }
+
+ $items[] = id(new PhabricatorActionView())
+ ->setIcon('fa-cog')
+ ->setHref($this->getApplicationURI('board/'.$this->id.'/filter/'))
+ ->setWorkflow(true)
+ ->setName(pht('Advanced Filter...'));
+
+
+
+ $filter_menu = id(new PhabricatorActionListView())
+ ->setUser($viewer);
+ foreach ($items as $item) {
+ $filter_menu->addAction($item);
+ }
+
+ $filter_button = id(new PHUIButtonView())
+ ->setText(pht('Filter: %s', $active_filter))
+ ->setIcon($filter_icon)
+ ->setTag('a')
+ ->setHref('#')
+ ->addSigil('boards-filter-menu')
+
+/*
+ TODO: @chad, this looks really gnarly right now, at least in Safari.
+ ->setDropdown(true)
+*/
+
+ ->setMetadata(
+ array(
+ 'items' => hsprintf('%s', $filter_menu),
+ ));
+
$header_link = phutil_tag(
'a',
array(
@@ -179,6 +315,7 @@
->setNoBackground(true)
->setImage($project->getProfileImageURI())
->setImageURL($this->getApplicationURI('view/'.$project->getID().'/'))
+ ->addActionLink($filter_button)
->addActionLink($add_button)
->setPolicyObject($project);
diff --git a/src/applications/project/view/ProjectBoardTaskCard.php b/src/applications/project/view/ProjectBoardTaskCard.php
--- a/src/applications/project/view/ProjectBoardTaskCard.php
+++ b/src/applications/project/view/ProjectBoardTaskCard.php
@@ -53,6 +53,7 @@
->setGrippable($can_edit)
->setHref('/T'.$task->getID())
->addSigil('project-card')
+ ->setDisabled($task->isClosed())
->setMetadata(
array(
'objectPHID' => $task->getPHID(),
diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php
--- a/src/applications/search/controller/PhabricatorApplicationSearchController.php
+++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php
@@ -90,7 +90,7 @@
if ($request->isFormPost()) {
$saved_query = $engine->buildSavedQueryFromRequest($request);
- $this->saveQuery($saved_query);
+ $engine->saveQuery($saved_query);
return id(new AphrontRedirectResponse())->setURI(
$engine->getQueryResultsPageURI($saved_query->getQueryKey()).'#R');
}
@@ -145,7 +145,7 @@
// Save the query to generate a query key, so "Save Custom Query..." and
// other features like Maniphest's "Export..." work correctly.
- $this->saveQuery($saved_query);
+ $engine->saveQuery($saved_query);
}
$nav->selectFilter(
@@ -353,18 +353,6 @@
));
}
- private function saveQuery(PhabricatorSavedQuery $query) {
- $query->setEngineClassName(get_class($this->getSearchEngine()));
-
- $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
- try {
- $query->save();
- } catch (AphrontQueryDuplicateKeyException $ex) {
- // Ignore, this is just a repeated search.
- }
- unset($unguarded);
- }
-
protected function buildApplicationMenu() {
return $this->getDelegatingController()->buildApplicationMenu();
}
diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
--- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
+++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
@@ -35,6 +35,18 @@
return $this->viewer;
}
+ public function saveQuery(PhabricatorSavedQuery $query) {
+ $query->setEngineClassName(get_class($this));
+
+ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+ try {
+ $query->save();
+ } catch (AphrontQueryDuplicateKeyException $ex) {
+ // Ignore, this is just a repeated search.
+ }
+ unset($unguarded);
+ }
+
/**
* Create a saved query object from the request.
*
diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php
--- a/src/view/layout/PhabricatorActionView.php
+++ b/src/view/layout/PhabricatorActionView.php
@@ -12,6 +12,16 @@
private $objectURI;
private $sigils = array();
private $metadata;
+ private $selected;
+
+ public function setSelected($selected) {
+ $this->selected = $selected;
+ return $this;
+ }
+
+ public function getSelected() {
+ return $this->selected;
+ }
public function setMetadata($metadata) {
$this->metadata = $metadata;
@@ -167,6 +177,10 @@
$classes[] = 'phabricator-action-view-disabled';
}
+ if ($this->selected) {
+ $classes[] = 'phabricator-action-view-selected';
+ }
+
return phutil_tag(
'li',
array(
diff --git a/webroot/rsrc/js/application/projects/behavior-boards-filter.js b/webroot/rsrc/js/application/projects/behavior-boards-filter.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/application/projects/behavior-boards-filter.js
@@ -0,0 +1,35 @@
+/**
+ * @provides javelin-behavior-boards-filter
+ * @requires javelin-behavior
+ * javelin-dom
+ * javelin-stratcom
+ * phuix-dropdown-menu
+ */
+
+JX.behavior('boards-filter', function(config) {
+
+ JX.Stratcom.listen('click', 'boards-filter-menu', function(e) {
+ var data = e.getNodeData('boards-filter-menu');
+ if (data.menu) {
+ return;
+ }
+
+ e.kill();
+
+ var list = JX.$H(data.items).getFragment().firstChild;
+
+ var button = e.getNode('boards-filter-menu');
+ data.menu = new JX.PHUIXDropdownMenu(button);
+ data.menu.setContent(list);
+ data.menu.open();
+
+ JX.DOM.listen(list, 'click', 'tag:a', function(e) {
+ if (!e.isNormalClick()) {
+ return;
+ }
+ data.menu.close();
+ });
+ });
+
+
+});

File Metadata

Mime Type
text/plain
Expires
Thu, Jan 30, 4:06 PM (2 h, 58 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7074218
Default Alt Text
D9211.diff (16 KB)

Event Timeline