2014-03-26 22:40:47 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class PhabricatorProjectBoardViewController
|
|
|
|
extends PhabricatorProjectBoardController {
|
|
|
|
|
|
|
|
private $id;
|
2014-06-13 23:09:21 +02:00
|
|
|
private $slug;
|
2014-03-26 22:40:47 +01:00
|
|
|
private $handles;
|
2014-05-20 20:42:05 +02:00
|
|
|
private $queryKey;
|
|
|
|
private $filter;
|
2014-08-08 17:10:29 +02:00
|
|
|
private $sortKey;
|
|
|
|
private $showHidden;
|
2014-03-26 22:40:47 +01:00
|
|
|
|
|
|
|
public function shouldAllowPublic() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-01-12 19:04:01 +01:00
|
|
|
public function handleRequest(AphrontRequest $request) {
|
2014-03-26 22:40:47 +01:00
|
|
|
$viewer = $request->getUser();
|
2015-01-12 19:04:01 +01:00
|
|
|
$id = $request->getURIData('id');
|
2014-03-26 22:40:47 +01:00
|
|
|
|
2014-06-25 21:30:20 +02:00
|
|
|
$show_hidden = $request->getBool('hidden');
|
2014-08-08 17:10:29 +02:00
|
|
|
$this->showHidden = $show_hidden;
|
2014-06-25 21:30:20 +02:00
|
|
|
|
2014-03-26 22:40:47 +01:00
|
|
|
$project = id(new PhabricatorProjectQuery())
|
|
|
|
->setViewer($viewer)
|
2014-06-13 23:09:21 +02:00
|
|
|
->needImages(true);
|
2015-01-12 19:04:01 +01:00
|
|
|
$id = $request->getURIData('id');
|
|
|
|
$slug = $request->getURIData('slug');
|
|
|
|
if ($slug) {
|
|
|
|
$project->withSlugs(array($slug));
|
2014-06-13 23:09:21 +02:00
|
|
|
} else {
|
2015-01-12 19:04:01 +01:00
|
|
|
$project->withIDs(array($id));
|
2014-06-13 23:09:21 +02:00
|
|
|
}
|
|
|
|
$project = $project->executeOne();
|
2014-03-26 22:40:47 +01:00
|
|
|
if (!$project) {
|
|
|
|
return new Aphront404Response();
|
|
|
|
}
|
2014-06-13 23:09:21 +02:00
|
|
|
|
2014-03-26 22:40:47 +01:00
|
|
|
$this->setProject($project);
|
2014-08-01 20:06:42 +02:00
|
|
|
$this->id = $project->getID();
|
2014-03-26 22:40:47 +01:00
|
|
|
|
2014-08-08 17:10:29 +02:00
|
|
|
$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;
|
|
|
|
|
2014-06-25 21:30:20 +02:00
|
|
|
$column_query = id(new PhabricatorProjectColumnQuery())
|
2014-03-26 22:40:47 +01:00
|
|
|
->setViewer($viewer)
|
2014-06-25 21:30:20 +02:00
|
|
|
->withProjectPHIDs(array($project->getPHID()));
|
|
|
|
|
|
|
|
if (!$show_hidden) {
|
|
|
|
$column_query->withStatuses(
|
|
|
|
array(PhabricatorProjectColumn::STATUS_ACTIVE));
|
|
|
|
}
|
2014-03-26 22:40:47 +01:00
|
|
|
|
2014-06-25 21:30:20 +02:00
|
|
|
$columns = $column_query->execute();
|
2014-03-26 22:40:47 +01:00
|
|
|
$columns = mpull($columns, null, 'getSequence');
|
|
|
|
|
2014-10-07 23:49:22 +02:00
|
|
|
// TODO: Expand the checks here if we add the ability
|
|
|
|
// to hide the Backlog column
|
|
|
|
if (!$columns) {
|
2014-08-05 22:40:41 +02:00
|
|
|
switch ($request->getStr('initialize-type')) {
|
|
|
|
case 'backlog-only':
|
|
|
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
|
|
|
$column = PhabricatorProjectColumn::initializeNewColumn($viewer)
|
|
|
|
->setSequence(0)
|
2014-08-09 00:50:36 +02:00
|
|
|
->setProperty('isDefault', true)
|
2014-08-05 22:40:41 +02:00
|
|
|
->setProjectPHID($project->getPHID())
|
|
|
|
->save();
|
|
|
|
$column->attachProject($project);
|
|
|
|
$columns[0] = $column;
|
|
|
|
unset($unguarded);
|
|
|
|
break;
|
|
|
|
case 'import':
|
|
|
|
return id(new AphrontRedirectResponse())
|
|
|
|
->setURI(
|
|
|
|
$this->getApplicationURI('board/'.$project->getID().'/import/'));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return $this->initializeWorkboardDialog($project);
|
|
|
|
break;
|
|
|
|
}
|
2014-03-26 22:40:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ksort($columns);
|
|
|
|
|
2014-05-20 20:42:05 +02:00
|
|
|
$board_uri = $this->getApplicationURI('board/'.$project->getID().'/');
|
|
|
|
|
|
|
|
$engine = id(new ManiphestTaskSearchEngine())
|
2014-03-26 22:40:47 +01:00
|
|
|
->setViewer($viewer)
|
2014-05-20 20:42:05 +02:00
|
|
|
->setBaseURI($board_uri)
|
|
|
|
->setIsBoardView(true);
|
|
|
|
|
|
|
|
if ($request->isFormPost()) {
|
|
|
|
$saved = $engine->buildSavedQueryFromRequest($request);
|
|
|
|
$engine->saveQuery($saved);
|
|
|
|
return id(new AphrontRedirectResponse())->setURI(
|
2014-08-08 17:10:29 +02:00
|
|
|
$this->getURIWithState(
|
|
|
|
$engine->getQueryResultsPageURI($saved->getQueryKey())));
|
2014-05-20 20:42:05 +02:00
|
|
|
}
|
|
|
|
|
2015-01-12 19:04:01 +01:00
|
|
|
$query_key = $request->getURIData('queryKey');
|
2014-05-20 20:42:05 +02:00
|
|
|
if (!$query_key) {
|
|
|
|
$query_key = 'open';
|
|
|
|
}
|
2014-08-08 17:10:29 +02:00
|
|
|
$this->queryKey = $query_key;
|
2014-05-20 20:42:05 +02:00
|
|
|
|
|
|
|
$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;
|
|
|
|
}
|
|
|
|
|
2015-01-12 19:04:01 +01:00
|
|
|
if ($request->getURIData('filter')) {
|
2014-05-20 20:42:05 +02:00
|
|
|
$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()))
|
2014-03-26 22:40:47 +01:00
|
|
|
->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY)
|
2014-05-20 20:42:05 +02:00
|
|
|
->setViewer($viewer)
|
2014-03-26 22:40:47 +01:00
|
|
|
->execute();
|
|
|
|
$tasks = mpull($tasks, null, 'getPHID');
|
2014-08-07 00:09:09 +02:00
|
|
|
|
|
|
|
if ($tasks) {
|
|
|
|
$positions = id(new PhabricatorProjectColumnPositionQuery())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withObjectPHIDs(mpull($tasks, 'getPHID'))
|
|
|
|
->withColumns($columns)
|
|
|
|
->execute();
|
|
|
|
$positions = mpull($positions, null, 'getObjectPHID');
|
|
|
|
} else {
|
|
|
|
$positions = array();
|
2014-03-26 22:40:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$task_map = array();
|
|
|
|
foreach ($tasks as $task) {
|
|
|
|
$task_phid = $task->getPHID();
|
2014-08-09 01:22:11 +02:00
|
|
|
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;
|
2014-08-07 00:09:09 +02:00
|
|
|
}
|
2014-03-26 22:40:47 +01:00
|
|
|
|
2014-08-09 01:22:11 +02:00
|
|
|
$position = $positions[$task_phid];
|
|
|
|
$task_map[$position->getColumnPHID()][] = $task_phid;
|
2014-03-26 22:40:47 +01:00
|
|
|
}
|
|
|
|
|
2014-08-08 17:11:00 +02:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-26 22:40:47 +01:00
|
|
|
$task_can_edit_map = id(new PhabricatorPolicyFilter())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT))
|
|
|
|
->apply($tasks);
|
|
|
|
|
|
|
|
$board_id = celerity_generate_unique_node_id();
|
|
|
|
|
|
|
|
$board = id(new PHUIWorkboardView())
|
|
|
|
->setUser($viewer)
|
|
|
|
->setID($board_id);
|
|
|
|
|
|
|
|
$this->initBehavior(
|
|
|
|
'project-boards',
|
|
|
|
array(
|
|
|
|
'boardID' => $board_id,
|
|
|
|
'projectPHID' => $project->getPHID(),
|
|
|
|
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
|
|
|
|
'createURI' => '/maniphest/task/create/',
|
2014-08-08 17:10:29 +02:00
|
|
|
'order' => $this->sortKey,
|
2014-03-26 22:40:47 +01:00
|
|
|
));
|
|
|
|
|
|
|
|
$this->handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks);
|
|
|
|
|
|
|
|
foreach ($columns as $column) {
|
2014-08-15 18:28:08 +02:00
|
|
|
$task_phids = idx($task_map, $column->getPHID(), array());
|
|
|
|
$column_tasks = array_select_keys($tasks, $task_phids);
|
|
|
|
|
2014-03-26 22:40:47 +01:00
|
|
|
$panel = id(new PHUIWorkpanelView())
|
|
|
|
->setHeader($column->getDisplayName())
|
2014-11-04 20:11:15 +01:00
|
|
|
->setSubHeader($column->getDisplayType())
|
Allow columns to have a point limit
Summary:
Fixes T5885. This implements optional soft point limits for workboard columns, per traditional Kanban.
- Allow columns to have a point limit set.
- When a column has a point limit, show it in the header.
- If a column has too many points in it, show the column and point count in red.
@chad, this could probably use some design tweaks. In particular:
- I changed the color of "hidden" columns to avoid confusion with "overfull" columns. We might be able to find a better color.
- UI hints for overfull columns might need adjustment.
(After T4427, we'll let you sum some custom field instead of total number of tasks, which is why this is called "points" rather than "number of tasks".)
Test Plan:
{F190914}
Note that:
- "Pre-planning" has a limit, so it shows "4/12".
- "Planning" has a limit and is overfull, so it shows "5 / 4".
- Other columns do not have limits.
- "Post-planning" is a hidden column. This might be too muted now.
Transactions:
{F190915}
Error messages / edit screen:
{F190916}
Reviewers: btrahan, chad
Reviewed By: btrahan
Subscribers: chad, epriestley
Maniphest Tasks: T5885
Differential Revision: https://secure.phabricator.com/D10276
2014-08-15 20:16:08 +02:00
|
|
|
->addSigil('workpanel');
|
|
|
|
|
|
|
|
$header_icon = $column->getHeaderIcon();
|
|
|
|
if ($header_icon) {
|
|
|
|
$panel->setHeaderIcon($header_icon);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($column->isHidden()) {
|
|
|
|
$panel->addClass('project-panel-hidden');
|
|
|
|
}
|
2014-06-25 21:30:20 +02:00
|
|
|
|
2014-08-08 19:35:51 +02:00
|
|
|
$column_menu = $this->buildColumnMenu($project, $column);
|
|
|
|
$panel->addHeaderAction($column_menu);
|
2014-03-26 22:40:47 +01:00
|
|
|
|
2014-08-15 18:28:08 +02:00
|
|
|
$tag_id = celerity_generate_unique_node_id();
|
|
|
|
$tag_content_id = celerity_generate_unique_node_id();
|
|
|
|
|
|
|
|
$count_tag = id(new PHUITagView())
|
|
|
|
->setType(PHUITagView::TYPE_SHADE)
|
|
|
|
->setShade(PHUITagView::COLOR_BLUE)
|
|
|
|
->setID($tag_id)
|
|
|
|
->setName(phutil_tag('span', array('id' => $tag_content_id), '-'))
|
|
|
|
->setStyle('display: none');
|
|
|
|
|
|
|
|
$panel->setHeaderTag($count_tag);
|
|
|
|
|
2014-03-26 22:40:47 +01:00
|
|
|
$cards = id(new PHUIObjectItemListView())
|
|
|
|
->setUser($viewer)
|
|
|
|
->setFlush(true)
|
|
|
|
->setAllowEmptyList(true)
|
|
|
|
->addSigil('project-column')
|
|
|
|
->setMetadata(
|
|
|
|
array(
|
|
|
|
'columnPHID' => $column->getPHID(),
|
2014-08-15 18:28:08 +02:00
|
|
|
'countTagID' => $tag_id,
|
|
|
|
'countTagContentID' => $tag_content_id,
|
Allow columns to have a point limit
Summary:
Fixes T5885. This implements optional soft point limits for workboard columns, per traditional Kanban.
- Allow columns to have a point limit set.
- When a column has a point limit, show it in the header.
- If a column has too many points in it, show the column and point count in red.
@chad, this could probably use some design tweaks. In particular:
- I changed the color of "hidden" columns to avoid confusion with "overfull" columns. We might be able to find a better color.
- UI hints for overfull columns might need adjustment.
(After T4427, we'll let you sum some custom field instead of total number of tasks, which is why this is called "points" rather than "number of tasks".)
Test Plan:
{F190914}
Note that:
- "Pre-planning" has a limit, so it shows "4/12".
- "Planning" has a limit and is overfull, so it shows "5 / 4".
- Other columns do not have limits.
- "Post-planning" is a hidden column. This might be too muted now.
Transactions:
{F190915}
Error messages / edit screen:
{F190916}
Reviewers: btrahan, chad
Reviewed By: btrahan
Subscribers: chad, epriestley
Maniphest Tasks: T5885
Differential Revision: https://secure.phabricator.com/D10276
2014-08-15 20:16:08 +02:00
|
|
|
'pointLimit' => $column->getPointLimit(),
|
2014-03-26 22:40:47 +01:00
|
|
|
));
|
2014-06-25 21:30:20 +02:00
|
|
|
|
2014-08-15 18:28:08 +02:00
|
|
|
foreach ($column_tasks as $task) {
|
2014-03-26 22:40:47 +01:00
|
|
|
$owner = null;
|
|
|
|
if ($task->getOwnerPHID()) {
|
|
|
|
$owner = $this->handles[$task->getOwnerPHID()];
|
|
|
|
}
|
|
|
|
$can_edit = idx($task_can_edit_map, $task->getPHID(), false);
|
|
|
|
$cards->addItem(id(new ProjectBoardTaskCard())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->setTask($task)
|
|
|
|
->setOwner($owner)
|
|
|
|
->setCanEdit($can_edit)
|
|
|
|
->getItem());
|
|
|
|
}
|
|
|
|
$panel->setCards($cards);
|
|
|
|
$board->addPanel($panel);
|
|
|
|
}
|
|
|
|
|
2014-06-25 21:30:53 +02:00
|
|
|
Javelin::initBehavior(
|
|
|
|
'boards-dropdown',
|
|
|
|
array());
|
|
|
|
|
2014-08-08 17:10:29 +02:00
|
|
|
$sort_menu = $this->buildSortMenu(
|
|
|
|
$viewer,
|
|
|
|
$sort_key);
|
|
|
|
|
2014-06-25 21:30:53 +02:00
|
|
|
$filter_menu = $this->buildFilterMenu(
|
2014-03-26 22:40:47 +01:00
|
|
|
$viewer,
|
2014-06-25 21:30:53 +02:00
|
|
|
$custom_query,
|
|
|
|
$engine,
|
|
|
|
$query_key);
|
2014-03-26 22:40:47 +01:00
|
|
|
|
2014-06-25 21:30:53 +02:00
|
|
|
$manage_menu = $this->buildManageMenu($project, $show_hidden);
|
2014-05-08 23:21:32 +02:00
|
|
|
|
2014-06-25 21:30:53 +02:00
|
|
|
$header_link = phutil_tag(
|
|
|
|
'a',
|
|
|
|
array(
|
2015-01-12 19:04:01 +01:00
|
|
|
'href' => $this->getApplicationURI('profile/'.$project->getID().'/'),
|
2014-06-25 21:30:53 +02:00
|
|
|
),
|
|
|
|
$project->getName());
|
2014-05-08 23:21:32 +02:00
|
|
|
|
2014-06-25 21:30:53 +02:00
|
|
|
$header = id(new PHUIHeaderView())
|
|
|
|
->setHeader($header_link)
|
|
|
|
->setUser($viewer)
|
|
|
|
->setNoBackground(true)
|
|
|
|
->setImage($project->getProfileImageURI())
|
2015-01-12 19:04:01 +01:00
|
|
|
->setImageURL($this->getApplicationURI('profile/'.$project->getID().'/'))
|
2014-08-08 17:10:29 +02:00
|
|
|
->addActionLink($sort_menu)
|
2014-06-25 21:30:53 +02:00
|
|
|
->addActionLink($filter_menu)
|
|
|
|
->addActionLink($manage_menu)
|
|
|
|
->setPolicyObject($project);
|
|
|
|
|
|
|
|
$board_box = id(new PHUIBoxView())
|
|
|
|
->appendChild($board)
|
|
|
|
->addClass('project-board-wrapper');
|
|
|
|
|
2015-01-12 19:04:01 +01:00
|
|
|
$nav = $this->buildIconNavView($project);
|
|
|
|
$nav->appendChild($header);
|
|
|
|
$nav->appendChild($board_box);
|
|
|
|
|
2014-06-25 21:30:53 +02:00
|
|
|
return $this->buildApplicationPage(
|
|
|
|
array(
|
2015-01-12 19:04:01 +01:00
|
|
|
$nav,
|
2014-06-25 21:30:53 +02:00
|
|
|
),
|
2014-05-20 20:42:05 +02:00
|
|
|
array(
|
2014-06-25 21:30:53 +02:00
|
|
|
'title' => pht('%s Board', $project->getName()),
|
2014-09-10 23:44:34 +02:00
|
|
|
'showFooter' => false,
|
2014-05-20 20:42:05 +02:00
|
|
|
));
|
2014-06-25 21:30:53 +02:00
|
|
|
}
|
|
|
|
|
2014-08-08 17:10:29 +02:00
|
|
|
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;
|
|
|
|
}
|
2014-06-25 21:30:53 +02:00
|
|
|
private function buildFilterMenu(
|
|
|
|
PhabricatorUser $viewer,
|
|
|
|
$custom_query,
|
|
|
|
PhabricatorApplicationSearchEngine $engine,
|
|
|
|
$query_key) {
|
2014-05-20 20:42:05 +02:00
|
|
|
|
|
|
|
$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) {
|
2014-08-08 17:10:29 +02:00
|
|
|
$uri = $this->getApplicationURI(
|
|
|
|
'board/'.$this->id.'/filter/query/'.$key.'/');
|
2014-05-20 20:42:05 +02:00
|
|
|
$item->setWorkflow(true);
|
|
|
|
} else {
|
2014-08-08 17:10:29 +02:00
|
|
|
$uri = $engine->getQueryResultsPageURI($key);
|
2014-05-20 20:42:05 +02:00
|
|
|
}
|
|
|
|
|
2014-08-08 17:10:29 +02:00
|
|
|
$uri = $this->getURIWithState($uri);
|
|
|
|
$item->setHref($uri);
|
|
|
|
|
2014-05-20 20:42:05 +02:00
|
|
|
$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('#')
|
2014-06-25 21:30:53 +02:00
|
|
|
->addSigil('boards-dropdown-menu')
|
2014-05-20 20:42:05 +02:00
|
|
|
->setMetadata(
|
|
|
|
array(
|
|
|
|
'items' => hsprintf('%s', $filter_menu),
|
|
|
|
));
|
|
|
|
|
2014-06-25 21:30:53 +02:00
|
|
|
return $filter_button;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function buildManageMenu(
|
|
|
|
PhabricatorProject $project,
|
|
|
|
$show_hidden) {
|
|
|
|
|
|
|
|
$request = $this->getRequest();
|
|
|
|
$viewer = $request->getUser();
|
|
|
|
|
|
|
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
|
|
|
$viewer,
|
|
|
|
$project,
|
|
|
|
PhabricatorPolicyCapability::CAN_EDIT);
|
|
|
|
|
|
|
|
$manage_icon = id(new PHUIIconView())
|
|
|
|
->setIconFont('fa-cog bluegrey');
|
|
|
|
|
|
|
|
$manage_items = array();
|
|
|
|
|
|
|
|
$manage_items[] = id(new PhabricatorActionView())
|
|
|
|
->setIcon('fa-plus')
|
|
|
|
->setName(pht('Add Column'))
|
2014-07-12 04:27:07 +02:00
|
|
|
->setHref($this->getApplicationURI('board/'.$this->id.'/edit/'))
|
|
|
|
->setDisabled(!$can_edit)
|
|
|
|
->setWorkflow(!$can_edit);
|
|
|
|
|
|
|
|
$manage_items[] = id(new PhabricatorActionView())
|
|
|
|
->setIcon('fa-exchange')
|
|
|
|
->setName(pht('Reorder Columns'))
|
|
|
|
->setHref($this->getApplicationURI('board/'.$this->id.'/reorder/'))
|
|
|
|
->setDisabled(!$can_edit)
|
|
|
|
->setWorkflow(true);
|
2014-03-26 22:40:47 +01:00
|
|
|
|
2014-06-25 21:30:20 +02:00
|
|
|
if ($show_hidden) {
|
2014-08-08 17:10:29 +02:00
|
|
|
$hidden_uri = $this->getURIWithState()
|
2014-06-25 21:30:20 +02:00
|
|
|
->setQueryParam('hidden', null);
|
2014-06-25 21:30:53 +02:00
|
|
|
$hidden_icon = 'fa-eye-slash';
|
2014-06-25 21:30:20 +02:00
|
|
|
$hidden_text = pht('Hide Hidden Columns');
|
|
|
|
} else {
|
2014-08-08 17:10:29 +02:00
|
|
|
$hidden_uri = $this->getURIWithState()
|
2014-06-25 21:30:20 +02:00
|
|
|
->setQueryParam('hidden', 'true');
|
2014-06-25 21:30:53 +02:00
|
|
|
$hidden_icon = 'fa-eye';
|
2014-06-25 21:30:20 +02:00
|
|
|
$hidden_text = pht('Show Hidden Columns');
|
|
|
|
}
|
|
|
|
|
2014-06-25 21:30:53 +02:00
|
|
|
$manage_items[] = id(new PhabricatorActionView())
|
2014-06-25 21:30:20 +02:00
|
|
|
->setIcon($hidden_icon)
|
2014-06-25 21:30:53 +02:00
|
|
|
->setName($hidden_text)
|
2014-06-25 21:30:20 +02:00
|
|
|
->setHref($hidden_uri);
|
|
|
|
|
2014-06-25 21:30:53 +02:00
|
|
|
$manage_menu = id(new PhabricatorActionListView())
|
|
|
|
->setUser($viewer);
|
|
|
|
foreach ($manage_items as $item) {
|
|
|
|
$manage_menu->addAction($item);
|
|
|
|
}
|
2014-03-26 22:40:47 +01:00
|
|
|
|
2014-06-25 21:30:53 +02:00
|
|
|
$manage_button = id(new PHUIButtonView())
|
|
|
|
->setText(pht('Manage Board'))
|
|
|
|
->setIcon($manage_icon)
|
|
|
|
->setTag('a')
|
|
|
|
->setHref('#')
|
|
|
|
->addSigil('boards-dropdown-menu')
|
|
|
|
->setMetadata(
|
|
|
|
array(
|
|
|
|
'items' => hsprintf('%s', $manage_menu),
|
|
|
|
));
|
2014-03-26 22:40:47 +01:00
|
|
|
|
2014-06-25 21:30:53 +02:00
|
|
|
return $manage_button;
|
2014-03-26 22:40:47 +01:00
|
|
|
}
|
|
|
|
|
2014-08-08 19:35:51 +02:00
|
|
|
private function buildColumnMenu(
|
|
|
|
PhabricatorProject $project,
|
|
|
|
PhabricatorProjectColumn $column) {
|
|
|
|
|
|
|
|
$request = $this->getRequest();
|
|
|
|
$viewer = $request->getUser();
|
|
|
|
|
|
|
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
|
|
|
$viewer,
|
|
|
|
$project,
|
|
|
|
PhabricatorPolicyCapability::CAN_EDIT);
|
|
|
|
|
|
|
|
$column_items = array();
|
|
|
|
|
|
|
|
$column_items[] = id(new PhabricatorActionView())
|
|
|
|
->setIcon('fa-plus')
|
|
|
|
->setName(pht('Create Task...'))
|
|
|
|
->setHref('/maniphest/task/create/')
|
|
|
|
->addSigil('column-add-task')
|
|
|
|
->setMetadata(
|
|
|
|
array(
|
|
|
|
'columnPHID' => $column->getPHID(),
|
|
|
|
))
|
|
|
|
->setDisabled(!$can_edit);
|
|
|
|
|
|
|
|
$edit_uri = $this->getApplicationURI(
|
|
|
|
'board/'.$this->id.'/column/'.$column->getID().'/');
|
|
|
|
|
|
|
|
$column_items[] = id(new PhabricatorActionView())
|
|
|
|
->setIcon('fa-pencil')
|
|
|
|
->setName(pht('Edit Column'))
|
|
|
|
->setHref($edit_uri)
|
|
|
|
->setDisabled(!$can_edit)
|
|
|
|
->setWorkflow(!$can_edit);
|
|
|
|
|
2014-09-04 21:47:32 +02:00
|
|
|
$can_hide = ($can_edit && !$column->isDefaultColumn());
|
|
|
|
$hide_uri = 'board/'.$this->id.'/hide/'.$column->getID().'/';
|
|
|
|
$hide_uri = $this->getApplicationURI($hide_uri);
|
|
|
|
$hide_uri = $this->getURIWithState($hide_uri);
|
|
|
|
|
|
|
|
if (!$column->isHidden()) {
|
|
|
|
$column_items[] = id(new PhabricatorActionView())
|
|
|
|
->setName(pht('Hide Column'))
|
|
|
|
->setIcon('fa-eye-slash')
|
|
|
|
->setHref($hide_uri)
|
|
|
|
->setDisabled(!$can_hide)
|
|
|
|
->setWorkflow(true);
|
|
|
|
} else {
|
|
|
|
$column_items[] = id(new PhabricatorActionView())
|
|
|
|
->setName(pht('Show Column'))
|
|
|
|
->setIcon('fa-eye')
|
|
|
|
->setHref($hide_uri)
|
|
|
|
->setDisabled(!$can_hide)
|
|
|
|
->setWorkflow(true);
|
|
|
|
}
|
|
|
|
|
2014-08-08 19:35:51 +02:00
|
|
|
$column_menu = id(new PhabricatorActionListView())
|
|
|
|
->setUser($viewer);
|
|
|
|
foreach ($column_items as $item) {
|
|
|
|
$column_menu->addAction($item);
|
|
|
|
}
|
|
|
|
|
|
|
|
$column_button = id(new PHUIIconView())
|
|
|
|
->setIconFont('fa-caret-down')
|
|
|
|
->setHref('#')
|
|
|
|
->addSigil('boards-dropdown-menu')
|
|
|
|
->setMetadata(
|
|
|
|
array(
|
|
|
|
'items' => hsprintf('%s', $column_menu),
|
|
|
|
));
|
|
|
|
|
|
|
|
return $column_button;
|
|
|
|
}
|
|
|
|
|
2014-08-05 22:40:41 +02:00
|
|
|
private function initializeWorkboardDialog(PhabricatorProject $project) {
|
|
|
|
|
|
|
|
$instructions = pht('This workboard has not been setup yet.');
|
|
|
|
$new_selector = id(new AphrontFormRadioButtonControl())
|
|
|
|
->setName('initialize-type')
|
|
|
|
->setValue('backlog-only')
|
|
|
|
->addButton(
|
|
|
|
'backlog-only',
|
|
|
|
pht('New Empty Board'),
|
|
|
|
pht('Create a new board with just a backlog column.'))
|
|
|
|
->addButton(
|
|
|
|
'import',
|
|
|
|
pht('Import Columns'),
|
|
|
|
pht('Import board columns from another project.'));
|
|
|
|
|
|
|
|
$dialog = id(new AphrontDialogView())
|
|
|
|
->setUser($this->getRequest()->getUser())
|
|
|
|
->setTitle(pht('New Workboard'))
|
|
|
|
->addSubmitButton('Continue')
|
|
|
|
->addCancelButton($this->getApplicationURI('view/'.$project->getID().'/'))
|
|
|
|
->appendParagraph($instructions)
|
|
|
|
->appendChild($new_selector);
|
|
|
|
|
|
|
|
return id(new AphrontDialogResponse())
|
|
|
|
->setDialog($dialog);
|
|
|
|
}
|
2014-06-25 21:30:53 +02:00
|
|
|
|
2014-08-08 17:10:29 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2014-03-26 22:40:47 +01:00
|
|
|
}
|