Page MenuHomePhabricator

D10610.diff
No OneTemporary

D10610.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2304,6 +2304,7 @@
'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php',
'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php',
'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php',
+ 'PhabricatorProjectColumnContentController' => 'applications/project/controller/PhabricatorProjectColumnContentController.php',
'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php',
'PhabricatorProjectColumnEditController' => 'applications/project/controller/PhabricatorProjectColumnEditController.php',
'PhabricatorProjectColumnHideController' => 'applications/project/controller/PhabricatorProjectColumnHideController.php',
@@ -5726,6 +5727,7 @@
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
+ 'PhabricatorProjectColumnContentController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectColumnDetailController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectColumnEditController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectColumnHideController' => 'PhabricatorProjectBoardController',
diff --git a/src/applications/project/application/PhabricatorProjectApplication.php b/src/applications/project/application/PhabricatorProjectApplication.php
--- a/src/applications/project/application/PhabricatorProjectApplication.php
+++ b/src/applications/project/application/PhabricatorProjectApplication.php
@@ -74,12 +74,16 @@
=> 'PhabricatorProjectColumnEditController',
'hide/(?:(?P<id>\d+)/)?'
=> 'PhabricatorProjectColumnHideController',
- 'column/(?:(?P<id>\d+)/)?'
+ 'detail/(?:(?P<id>\d+)/)?'
=> 'PhabricatorProjectColumnDetailController',
'import/'
=> 'PhabricatorProjectBoardImportController',
'reorder/'
=> 'PhabricatorProjectBoardReorderController',
+ 'column/(?:(?P<id>\d+)/)'.
+ '(?P<filter>filter/)?'.
+ '(?:query/(?P<queryKey>[^/]+)/)?'
+ => 'PhabricatorProjectColumnContentController',
),
'update/(?P<id>[1-9]\d*)/(?P<action>[^/]+)/'
=> 'PhabricatorProjectUpdateController',
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
@@ -286,6 +286,8 @@
$panel = id(new PHUIWorkpanelView())
->setHeader($column->getDisplayName())
->setSubHeader($column->getDisplayType())
+ ->setHeaderURI($this->getApplicationURI(
+ 'board/'.$this->id.'/column/'.$column->getID().'/'))
->addSigil('workpanel');
$header_icon = $column->getHeaderIcon();
@@ -637,7 +639,7 @@
->setDisabled(!$can_batch_edit);
$edit_uri = $this->getApplicationURI(
- 'board/'.$this->id.'/column/'.$column->getID().'/');
+ 'board/'.$this->id.'/detail/'.$column->getID().'/');
$column_items[] = id(new PhabricatorActionView())
->setIcon('fa-pencil')
diff --git a/src/applications/project/controller/PhabricatorProjectColumnContentController.php b/src/applications/project/controller/PhabricatorProjectColumnContentController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/project/controller/PhabricatorProjectColumnContentController.php
@@ -0,0 +1,429 @@
+<?php
+
+final class PhabricatorProjectColumnContentController
+ extends PhabricatorProjectBoardController {
+
+ private $id;
+ private $projectID;
+ private $queryKey;
+ private $filter;
+ private $sortKey;
+
+ public function willProcessRequest(array $data) {
+ $this->projectID = $data['projectID'];
+ $this->id = idx($data, 'id');
+ $this->queryKey = idx($data, 'queryKey');
+ $this->filter = (bool)idx($data, 'filter');
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $project = id(new PhabricatorProjectQuery())
+ ->setViewer($viewer)
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ ))
+ ->withIDs(array($this->projectID))
+ ->executeOne();
+
+ if (!$project) {
+ return new Aphront404Response();
+ }
+ $this->setProject($project);
+
+ $column = id(new PhabricatorProjectColumnQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ ))
+ ->executeOne();
+ if (!$column) {
+ return new Aphront404Response();
+ }
+
+ $sort_key = $request->getStr('order');
+ switch ($sort_key) {
+ case PhabricatorProjectColumn::ORDER_NATURAL:
+ case PhabricatorProjectColumn::ORDER_PRIORITY:
+ break;
+ default:
+ $sort_key = PhabricatorProjectColumn::DEFAULT_ORDER;
+ break;
+ }
+ $this->sortKey = $sort_key;
+
+ // We already know who we are, this is for the side nav later
+ $column_query = id(new PhabricatorProjectColumnQuery())
+ ->setViewer($viewer)
+ ->withStatuses(
+ array(PhabricatorProjectColumn::STATUS_ACTIVE))
+ ->withProjectPHIDs(array($project->getPHID()));
+ $columns = $column_query->execute();
+ $columns = array_reverse(mpull($columns, null, 'getSequence'));
+
+ $title = pht('%s', $column->getDisplayName());
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addTextCrumb(
+ pht('Board'),
+ $this->getApplicationURI('board/'.$project->getID().'/'));
+ $crumbs->addTextCrumb($title);
+
+ $column_uri = $this->getApplicationURI('board/'.$project->getID().
+ '/column/'.$this->id.'/');
+ // copy pasted from view starts
+
+ $engine = id(new ManiphestTaskSearchEngine())
+ ->setViewer($viewer)
+ ->setBaseURI($column_uri)
+ ->setIsBoardView(true);
+
+ if ($request->isFormPost()) {
+ $saved = $engine->buildSavedQueryFromRequest($request);
+ $engine->saveQuery($saved);
+ return id(new AphrontRedirectResponse())->setURI(
+ $this->getURIWithState(
+ $engine->getQueryResultsPageURI($saved->getQueryKey())));
+ }
+
+ $query_key = $this->queryKey;
+ if (!$query_key) {
+ $query_key = 'open';
+ }
+ $this->queryKey = $query_key;
+
+ $custom_query = null;
+ if ($engine->isBuiltinQuery($query_key)) {
+ $saved = $engine->buildSavedQueryFromBuiltin($query_key);
+ $saved->setParameter('group', 'none');
+ } 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($column_uri)
+ ->addSubmitButton(pht('Apply Filter'))
+ ->addCancelButton($column_uri);
+ }
+
+ $task_query = $engine->buildQueryFromSavedQuery($saved);
+
+ $tasks = $task_query
+ ->withEdgeLogicPHIDs(
+ PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
+ PhabricatorQueryConstraint::OPERATOR_AND,
+ array($project->getPHID()))
+ ->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY)
+ ->setGroupBy(ManiphestTaskQuery::GROUP_NONE)
+ ->setViewer($viewer)
+ ->execute();
+ $tasks = mpull($tasks, null, 'getPHID');
+
+ if ($tasks) {
+ $positions = id(new PhabricatorProjectColumnPositionQuery())
+ ->setViewer($viewer)
+ ->withObjectPHIDs(mpull($tasks, 'getPHID'))
+ ->withColumns(array($column))
+ ->execute();
+ $positions = mpull($positions, null, 'getObjectPHID');
+ } else {
+ $positions = array();
+ }
+
+ $task_map = array();
+ foreach ($tasks as $task) {
+ $task_phid = $task->getPHID();
+ if (empty($positions[$task_phid])) {
+ // This shouldn't normally be possible because we create positions on
+ // demand, but we might have raced as an object was removed from the
+ // board. Just drop the task if we don't have a position for it.
+ continue;
+ }
+
+ $position = $positions[$task_phid];
+ $task_map[$position->getColumnPHID()][] = $task_phid;
+ }
+
+ // We get all open tasks for the project (or whatever the query
+ // is) and then filter down to tasks for a specific column. This is
+ // potentially inefficient for large projects, but we aleady need to
+ // display everything on a the main workboard anyway so we should be okay
+ // here.
+ $tasks = array_select_keys($tasks, array_keys($positions));
+
+ // If we're showing the board in "natural" order, sort columns by their
+ // column positions.
+ if ($this->sortKey == PhabricatorProjectColumn::ORDER_NATURAL) {
+ foreach ($task_map as $column_phid => $task_phids) {
+ $order = array();
+ foreach ($task_phids as $task_phid) {
+ if (isset($positions[$task_phid])) {
+ $order[$task_phid] = $positions[$task_phid]->getOrderingKey();
+ } else {
+ $order[$task_phid] = 0;
+ }
+ }
+ asort($order);
+ $task_map[$column_phid] = array_keys($order);
+ }
+ }
+
+ if (!empty($tasks)) {
+ $tasks = array_select_keys(mpull($tasks, null, 'getPHID'),
+ $task_map[$column->getPHID()]);
+ }
+
+ $task_can_edit_map = id(new PhabricatorPolicyFilter())
+ ->setViewer($viewer)
+ ->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT))
+ ->apply($tasks);
+
+ $results_view = id(new ManiphestTaskResultListView())
+ ->setUser($viewer)
+ ->setTasks($tasks)
+ ->setSavedQuery($saved)
+ ->setCanBatchEdit(true)
+ ->setCanEditPriority(true)
+ ->setShowBatchControls(true);
+
+ $sort_menu = $this->buildSortMenu(
+ $viewer,
+ $sort_key);
+
+ $filter_menu = $this->buildFilterMenu(
+ $viewer,
+ $custom_query,
+ $engine,
+ $query_key);
+
+
+ $header = id(new PHUIHeaderView())
+ // FIXME: PHUIHeaderView with only ActionLinks cuts off menus
+ ->setHeader('dummy')
+ ->setNoBackground(true)
+ ->addActionLink($sort_menu)
+ ->addActionLink($filter_menu);
+
+ $nav = $this->buildSideNav($this->getApplicationURI('board/'.
+ $project->getID().
+ '/column'),
+ $columns);
+
+ $nav->appendChild(
+ array(
+ $crumbs,
+ $header,
+ $results_view->render(),
+ ));
+
+ return $this->buildApplicationPage(
+ $nav,
+ array(
+ 'title' => $title,
+ ));
+ }
+
+
+ private function buildSideNav($base_uri, array $columns) {
+ $nav = new AphrontSideNavFilterView();
+
+ $nav->setBaseURI(new PhutilURI($base_uri.'/'))
+ ->addLabel('Columns');
+
+ foreach ($columns as $column) {
+ $nav->addFilter($column->getID(), $column->getDisplayName());
+ }
+ $valid_filter = $nav->selectFilter($this->id);
+
+ return $nav;
+ }
+
+
+
+ // FIXME: pure copy-paste from PhabricatorProjectBoardViewController
+
+ private function buildSortMenu(
+ PhabricatorUser $viewer,
+ $sort_key) {
+
+ $sort_icon = id(new PHUIIconView())
+ ->setIconFont('fa-sort-amount-asc bluegrey');
+
+ $named = array(
+ PhabricatorProjectColumn::ORDER_NATURAL => pht('Natural'),
+ PhabricatorProjectColumn::ORDER_PRIORITY => pht('Sort by Priority'),
+ );
+
+ $base_uri = $this->getURIWithState();
+
+ $items = array();
+ foreach ($named as $key => $name) {
+ $is_selected = ($key == $sort_key);
+ if ($is_selected) {
+ $active_order = $name;
+ }
+
+ $item = id(new PhabricatorActionView())
+ ->setIcon('fa-sort-amount-asc')
+ ->setSelected($is_selected)
+ ->setName($name);
+
+ $uri = $base_uri->alter('order', $key);
+ $item->setHref($uri);
+
+ $items[] = $item;
+ }
+
+ $sort_menu = id(new PhabricatorActionListView())
+ ->setUser($viewer);
+ foreach ($items as $item) {
+ $sort_menu->addAction($item);
+ }
+
+ $sort_button = id(new PHUIButtonView())
+ ->setText(pht('Sort: %s', $active_order))
+ ->setIcon($sort_icon)
+ ->setTag('a')
+ ->setHref('#')
+ ->addSigil('boards-dropdown-menu')
+ ->setMetadata(
+ array(
+ 'items' => hsprintf('%s', $sort_menu),
+ ));
+
+ return $sort_button;
+ }
+
+ private function buildFilterMenu(
+ PhabricatorUser $viewer,
+ $custom_query,
+ PhabricatorApplicationSearchEngine $engine,
+ $query_key) {
+
+ $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) {
+ $uri = $this->getApplicationURI('board/'.$this->projectID.
+ '/column/'.$this->id.'/filter/query/'.$key.'/');
+ $item->setWorkflow(true);
+ } else {
+ $uri = $engine->getQueryResultsPageURI($key);
+ }
+
+ $uri = $this->getURIWithState($uri);
+ $item->setHref($uri);
+
+ $items[] = $item;
+ }
+
+ $items[] = id(new PhabricatorActionView())
+ ->setIcon('fa-cog')
+ ->setHref($this->getApplicationURI('board/'.$this->projectID.
+ '/column/'.$this->id.'/filter/')) // mod
+ ->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-dropdown-menu')
+ ->setMetadata(
+ array(
+ 'items' => hsprintf('%s', $filter_menu),
+ ));
+
+ return $filter_button;
+ }
+
+
+ /**
+ * Add current state parameters (like order and the visibility of hidden
+ * columns) to a URI.
+ *
+ * This allows actions which toggle or adjust one piece of state to keep
+ * the rest of the board state persistent. If no URI is provided, this method
+ * starts with the request URI.
+ *
+ * @param string|null URI to add state parameters to.
+ * @return PhutilURI URI with state parameters.
+ */
+ private function getURIWithState($base = null) {
+ if ($base === null) {
+ $base = $this->getRequest()->getRequestURI();
+ }
+
+ $base = new PhutilURI($base);
+
+ if ($this->sortKey != PhabricatorProjectColumn::DEFAULT_ORDER) {
+ $base->setQueryParam('order', $this->sortKey);
+ } else {
+ $base->setQueryParam('order', null);
+ }
+
+ // $base->setQueryParam('hidden', $this->showHidden ? 'true' : null);
+
+ return $base;
+ }
+
+
+}
diff --git a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php
--- a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php
+++ b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php
@@ -91,7 +91,7 @@
$base_uri = '/board/'.$project_id.'/';
$actions = id(new PhabricatorActionListView())
- ->setObjectURI($this->getApplicationURI($base_uri.'column/'.$id.'/'))
+ ->setObjectURI($this->getApplicationURI($base_uri.'detail/'.$id.'/'))
->setUser($viewer);
$can_edit = PhabricatorPolicyFilter::hasCapability(
diff --git a/src/applications/project/controller/PhabricatorProjectColumnEditController.php b/src/applications/project/controller/PhabricatorProjectColumnEditController.php
--- a/src/applications/project/controller/PhabricatorProjectColumnEditController.php
+++ b/src/applications/project/controller/PhabricatorProjectColumnEditController.php
@@ -62,7 +62,7 @@
// we want to go back to the board
$view_uri = $this->getApplicationURI($base_uri);
} else {
- $view_uri = $this->getApplicationURI($base_uri.'column/'.$this->id.'/');
+ $view_uri = $this->getApplicationURI($base_uri.'detail/'.$this->id.'/');
}
if ($request->isFormPost()) {
diff --git a/src/view/phui/PHUIWorkpanelView.php b/src/view/phui/PHUIWorkpanelView.php
--- a/src/view/phui/PHUIWorkpanelView.php
+++ b/src/view/phui/PHUIWorkpanelView.php
@@ -5,6 +5,7 @@
private $cards = array();
private $header;
private $subheader = null;
+ private $headerURI;
private $footerAction;
private $headerColor = PHUIActionHeaderView::HEADER_GREY;
private $headerActions = array();
@@ -35,6 +36,11 @@
return $this;
}
+ public function setHeaderURI($header_uri) {
+ $this->headerURI = $header_uri;
+ return $this;
+ }
+
public function setFooterAction(PHUIListItemView $footer_action) {
$this->footerAction = $footer_action;
return $this;
@@ -108,8 +114,11 @@
array(
'class' => implode(' ', $classes),
),
+
array(
- $header,
+ isset($this->headerURI) ?
+ phutil_tag('a', array('href' => $this->headerURI), $header):
+ $header,
$body,
$footer,
));

File Metadata

Mime Type
text/plain
Expires
Wed, May 15, 10:56 AM (3 w, 3 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6275942
Default Alt Text
D10610.diff (18 KB)

Event Timeline