mirror of
https://we.phorge.it/source/phorge.git
synced 2025-04-13 04:48:34 +02:00
Summary: Depends on D20966. Ref T13486. Curtains currently render subscribers in a plain text list, but the new ref list element is a good fit for this. Also, improve the sorting and ordering behavior. This makes the subscriber list take up a bit more space, but it should make it a lot easier to read at a glance. Test Plan: Viewed object subscriber lists at varying limits and subscriber counts, saw sensible subscriber lists. Maniphest Tasks: T13486 Differential Revision: https://secure.phabricator.com/D20967
832 lines
24 KiB
PHP
832 lines
24 KiB
PHP
<?php
|
|
|
|
final class ManiphestTaskDetailController extends ManiphestController {
|
|
|
|
public function shouldAllowPublic() {
|
|
return true;
|
|
}
|
|
|
|
public function handleRequest(AphrontRequest $request) {
|
|
$viewer = $this->getViewer();
|
|
$id = $request->getURIData('id');
|
|
|
|
$task = id(new ManiphestTaskQuery())
|
|
->setViewer($viewer)
|
|
->withIDs(array($id))
|
|
->needSubscriberPHIDs(true)
|
|
->executeOne();
|
|
if (!$task) {
|
|
return new Aphront404Response();
|
|
}
|
|
|
|
$field_list = PhabricatorCustomField::getObjectFields(
|
|
$task,
|
|
PhabricatorCustomField::ROLE_VIEW);
|
|
$field_list
|
|
->setViewer($viewer)
|
|
->readFieldsFromStorage($task);
|
|
|
|
$edit_engine = id(new ManiphestEditEngine())
|
|
->setViewer($viewer)
|
|
->setTargetObject($task);
|
|
|
|
$edge_types = array(
|
|
ManiphestTaskHasCommitEdgeType::EDGECONST,
|
|
ManiphestTaskHasRevisionEdgeType::EDGECONST,
|
|
ManiphestTaskHasMockEdgeType::EDGECONST,
|
|
PhabricatorObjectMentionedByObjectEdgeType::EDGECONST,
|
|
PhabricatorObjectMentionsObjectEdgeType::EDGECONST,
|
|
ManiphestTaskHasDuplicateTaskEdgeType::EDGECONST,
|
|
);
|
|
|
|
$phid = $task->getPHID();
|
|
|
|
$query = id(new PhabricatorEdgeQuery())
|
|
->withSourcePHIDs(array($phid))
|
|
->withEdgeTypes($edge_types);
|
|
$edges = idx($query->execute(), $phid);
|
|
$phids = array_fill_keys($query->getDestinationPHIDs(), true);
|
|
|
|
if ($task->getOwnerPHID()) {
|
|
$phids[$task->getOwnerPHID()] = true;
|
|
}
|
|
$phids[$task->getAuthorPHID()] = true;
|
|
|
|
$phids = array_keys($phids);
|
|
$handles = $viewer->loadHandles($phids);
|
|
|
|
$timeline = $this->buildTransactionTimeline(
|
|
$task,
|
|
new ManiphestTransactionQuery());
|
|
|
|
$monogram = $task->getMonogram();
|
|
$crumbs = $this->buildApplicationCrumbs()
|
|
->addTextCrumb($monogram)
|
|
->setBorder(true);
|
|
|
|
$header = $this->buildHeaderView($task);
|
|
$details = $this->buildPropertyView($task, $field_list, $edges, $handles);
|
|
$description = $this->buildDescriptionView($task);
|
|
$curtain = $this->buildCurtain($task, $edit_engine);
|
|
|
|
$title = pht('%s %s', $monogram, $task->getTitle());
|
|
|
|
$comment_view = $edit_engine
|
|
->buildEditEngineCommentView($task);
|
|
|
|
$timeline->setQuoteRef($monogram);
|
|
$comment_view->setTransactionTimeline($timeline);
|
|
|
|
$related_tabs = array();
|
|
$graph_menu = null;
|
|
|
|
$graph_limit = 200;
|
|
$overflow_message = null;
|
|
$task_graph = id(new ManiphestTaskGraph())
|
|
->setViewer($viewer)
|
|
->setSeedPHID($task->getPHID())
|
|
->setLimit($graph_limit)
|
|
->loadGraph();
|
|
if (!$task_graph->isEmpty()) {
|
|
$parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
|
|
$subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
|
|
$parent_map = $task_graph->getEdges($parent_type);
|
|
$subtask_map = $task_graph->getEdges($subtask_type);
|
|
$parent_list = idx($parent_map, $task->getPHID(), array());
|
|
$subtask_list = idx($subtask_map, $task->getPHID(), array());
|
|
$has_parents = (bool)$parent_list;
|
|
$has_subtasks = (bool)$subtask_list;
|
|
|
|
// First, get a count of direct parent tasks and subtasks. If there
|
|
// are too many of these, we just don't draw anything. You can use
|
|
// the search button to browse tasks with the search UI instead.
|
|
$direct_count = count($parent_list) + count($subtask_list);
|
|
|
|
if ($direct_count > $graph_limit) {
|
|
$overflow_message = pht(
|
|
'This task is directly connected to more than %s other tasks. '.
|
|
'Use %s to browse parents or subtasks, or %s to show more of the '.
|
|
'graph.',
|
|
new PhutilNumber($graph_limit),
|
|
phutil_tag('strong', array(), pht('Search...')),
|
|
phutil_tag('strong', array(), pht('View Standalone Graph')));
|
|
|
|
$graph_table = null;
|
|
} else {
|
|
// If there aren't too many direct tasks, but there are too many total
|
|
// tasks, we'll only render directly connected tasks.
|
|
if ($task_graph->isOverLimit()) {
|
|
$task_graph->setRenderOnlyAdjacentNodes(true);
|
|
|
|
$overflow_message = pht(
|
|
'This task is connected to more than %s other tasks. '.
|
|
'Only direct parents and subtasks are shown here. Use '.
|
|
'%s to show more of the graph.',
|
|
new PhutilNumber($graph_limit),
|
|
phutil_tag('strong', array(), pht('View Standalone Graph')));
|
|
}
|
|
|
|
$graph_table = $task_graph->newGraphTable();
|
|
}
|
|
|
|
if ($overflow_message) {
|
|
$overflow_view = $this->newTaskGraphOverflowView(
|
|
$task,
|
|
$overflow_message,
|
|
true);
|
|
|
|
$graph_table = array(
|
|
$overflow_view,
|
|
$graph_table,
|
|
);
|
|
}
|
|
|
|
$graph_menu = $this->newTaskGraphDropdownMenu(
|
|
$task,
|
|
$has_parents,
|
|
$has_subtasks,
|
|
true);
|
|
|
|
$related_tabs[] = id(new PHUITabView())
|
|
->setName(pht('Task Graph'))
|
|
->setKey('graph')
|
|
->appendChild($graph_table);
|
|
}
|
|
|
|
$related_tabs[] = $this->newMocksTab($task, $query);
|
|
$related_tabs[] = $this->newMentionsTab($task, $query);
|
|
$related_tabs[] = $this->newDuplicatesTab($task, $query);
|
|
|
|
$tab_view = null;
|
|
|
|
$related_tabs = array_filter($related_tabs);
|
|
if ($related_tabs) {
|
|
$tab_group = new PHUITabGroupView();
|
|
foreach ($related_tabs as $tab) {
|
|
$tab_group->addTab($tab);
|
|
}
|
|
|
|
$related_header = id(new PHUIHeaderView())
|
|
->setHeader(pht('Related Objects'));
|
|
|
|
if ($graph_menu) {
|
|
$related_header->addActionLink($graph_menu);
|
|
}
|
|
|
|
$tab_view = id(new PHUIObjectBoxView())
|
|
->setHeader($related_header)
|
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
|
->addTabGroup($tab_group);
|
|
}
|
|
|
|
$changes_view = $this->newChangesView($task, $edges);
|
|
|
|
$view = id(new PHUITwoColumnView())
|
|
->setHeader($header)
|
|
->setCurtain($curtain)
|
|
->setMainColumn(
|
|
array(
|
|
$changes_view,
|
|
$tab_view,
|
|
$timeline,
|
|
$comment_view,
|
|
))
|
|
->addPropertySection(pht('Description'), $description)
|
|
->addPropertySection(pht('Details'), $details);
|
|
|
|
|
|
return $this->newPage()
|
|
->setTitle($title)
|
|
->setCrumbs($crumbs)
|
|
->setPageObjectPHIDs(
|
|
array(
|
|
$task->getPHID(),
|
|
))
|
|
->appendChild($view);
|
|
|
|
}
|
|
|
|
private function buildHeaderView(ManiphestTask $task) {
|
|
$view = id(new PHUIHeaderView())
|
|
->setHeader($task->getTitle())
|
|
->setUser($this->getRequest()->getUser())
|
|
->setPolicyObject($task);
|
|
|
|
$priority_name = ManiphestTaskPriority::getTaskPriorityName(
|
|
$task->getPriority());
|
|
$priority_color = ManiphestTaskPriority::getTaskPriorityColor(
|
|
$task->getPriority());
|
|
|
|
$status = $task->getStatus();
|
|
$status_name = ManiphestTaskStatus::renderFullDescription(
|
|
$status, $priority_name);
|
|
$view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name);
|
|
|
|
$view->setHeaderIcon(ManiphestTaskStatus::getStatusIcon(
|
|
$task->getStatus()).' '.$priority_color);
|
|
|
|
if (ManiphestTaskPoints::getIsEnabled()) {
|
|
$points = $task->getPoints();
|
|
if ($points !== null) {
|
|
$points_name = pht('%s %s',
|
|
$task->getPoints(),
|
|
ManiphestTaskPoints::getPointsLabel());
|
|
$tag = id(new PHUITagView())
|
|
->setName($points_name)
|
|
->setColor(PHUITagView::COLOR_BLUE)
|
|
->setType(PHUITagView::TYPE_SHADE);
|
|
|
|
$view->addTag($tag);
|
|
}
|
|
}
|
|
|
|
$subtype = $task->newSubtypeObject();
|
|
if ($subtype && $subtype->hasTagView()) {
|
|
$subtype_tag = $subtype->newTagView();
|
|
$view->addTag($subtype_tag);
|
|
}
|
|
|
|
return $view;
|
|
}
|
|
|
|
|
|
private function buildCurtain(
|
|
ManiphestTask $task,
|
|
PhabricatorEditEngine $edit_engine) {
|
|
$viewer = $this->getViewer();
|
|
|
|
$id = $task->getID();
|
|
$phid = $task->getPHID();
|
|
|
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
|
$viewer,
|
|
$task,
|
|
PhabricatorPolicyCapability::CAN_EDIT);
|
|
|
|
$can_interact = PhabricatorPolicyFilter::canInteract($viewer, $task);
|
|
|
|
// We expect a policy dialog if you can't edit the task, and expect a
|
|
// lock override dialog if you can't interact with it.
|
|
$workflow_edit = (!$can_edit || !$can_interact);
|
|
|
|
$curtain = $this->newCurtainView($task);
|
|
|
|
$curtain->addAction(
|
|
id(new PhabricatorActionView())
|
|
->setName(pht('Edit Task'))
|
|
->setIcon('fa-pencil')
|
|
->setHref($this->getApplicationURI("/task/edit/{$id}/"))
|
|
->setDisabled(!$can_edit)
|
|
->setWorkflow($workflow_edit));
|
|
|
|
$subtype_map = $task->newEditEngineSubtypeMap();
|
|
$subtask_options = $subtype_map->getCreateFormsForSubtype(
|
|
$edit_engine,
|
|
$task);
|
|
|
|
// If no forms are available, we want to show the user an error.
|
|
// If one form is available, we take them user directly to the form.
|
|
// If two or more forms are available, we give the user a choice.
|
|
|
|
// The "subtask" controller handles the first case (no forms) and the
|
|
// third case (more than one form). In the case of one form, we link
|
|
// directly to the form.
|
|
$subtask_uri = "/task/subtask/{$id}/";
|
|
$subtask_workflow = true;
|
|
|
|
if (count($subtask_options) == 1) {
|
|
$subtask_form = head($subtask_options);
|
|
$form_key = $subtask_form->getIdentifier();
|
|
$subtask_uri = id(new PhutilURI("/task/edit/form/{$form_key}/"))
|
|
->replaceQueryParam('parent', $id)
|
|
->replaceQueryParam('template', $id)
|
|
->replaceQueryParam('status', ManiphestTaskStatus::getDefaultStatus());
|
|
$subtask_workflow = false;
|
|
}
|
|
|
|
$subtask_uri = $this->getApplicationURI($subtask_uri);
|
|
|
|
$subtask_item = id(new PhabricatorActionView())
|
|
->setName(pht('Create Subtask'))
|
|
->setHref($subtask_uri)
|
|
->setIcon('fa-level-down')
|
|
->setDisabled(!$subtask_options)
|
|
->setWorkflow($subtask_workflow);
|
|
|
|
$relationship_list = PhabricatorObjectRelationshipList::newForObject(
|
|
$viewer,
|
|
$task);
|
|
|
|
$submenu_actions = array(
|
|
$subtask_item,
|
|
ManiphestTaskHasParentRelationship::RELATIONSHIPKEY,
|
|
ManiphestTaskHasSubtaskRelationship::RELATIONSHIPKEY,
|
|
ManiphestTaskMergeInRelationship::RELATIONSHIPKEY,
|
|
ManiphestTaskCloseAsDuplicateRelationship::RELATIONSHIPKEY,
|
|
);
|
|
|
|
$task_submenu = $relationship_list->newActionSubmenu($submenu_actions)
|
|
->setName(pht('Edit Related Tasks...'))
|
|
->setIcon('fa-anchor');
|
|
|
|
$curtain->addAction($task_submenu);
|
|
|
|
$relationship_submenu = $relationship_list->newActionMenu();
|
|
if ($relationship_submenu) {
|
|
$curtain->addAction($relationship_submenu);
|
|
}
|
|
|
|
$viewer_phid = $viewer->getPHID();
|
|
$owner_phid = $task->getOwnerPHID();
|
|
$author_phid = $task->getAuthorPHID();
|
|
$handles = $viewer->loadHandles(array($owner_phid, $author_phid));
|
|
|
|
$assigned_refs = id(new PHUICurtainObjectRefListView())
|
|
->setViewer($viewer)
|
|
->setEmptyMessage(pht('None'));
|
|
|
|
if ($owner_phid) {
|
|
$assigned_ref = $assigned_refs->newObjectRefView()
|
|
->setHandle($handles[$owner_phid])
|
|
->setHighlighted($owner_phid === $viewer_phid);
|
|
}
|
|
|
|
$curtain->newPanel()
|
|
->setHeaderText(pht('Assigned To'))
|
|
->appendChild($assigned_refs);
|
|
|
|
$author_refs = id(new PHUICurtainObjectRefListView())
|
|
->setViewer($viewer);
|
|
|
|
$author_ref = $author_refs->newObjectRefView()
|
|
->setHandle($handles[$author_phid])
|
|
->setEpoch($task->getDateCreated())
|
|
->setHighlighted($author_phid === $viewer_phid);
|
|
|
|
$curtain->newPanel()
|
|
->setHeaderText(pht('Authored By'))
|
|
->appendChild($author_refs);
|
|
|
|
return $curtain;
|
|
}
|
|
|
|
private function buildPropertyView(
|
|
ManiphestTask $task,
|
|
PhabricatorCustomFieldList $field_list,
|
|
array $edges,
|
|
$handles) {
|
|
|
|
$viewer = $this->getRequest()->getUser();
|
|
$view = id(new PHUIPropertyListView())
|
|
->setUser($viewer);
|
|
|
|
$source = $task->getOriginalEmailSource();
|
|
if ($source) {
|
|
$subject = '[T'.$task->getID().'] '.$task->getTitle();
|
|
$view->addProperty(
|
|
pht('From Email'),
|
|
phutil_tag(
|
|
'a',
|
|
array(
|
|
'href' => 'mailto:'.$source.'?subject='.$subject,
|
|
),
|
|
$source));
|
|
}
|
|
|
|
$field_list->appendFieldsToPropertyList(
|
|
$task,
|
|
$viewer,
|
|
$view);
|
|
|
|
if ($view->hasAnyProperties()) {
|
|
return $view;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function buildDescriptionView(ManiphestTask $task) {
|
|
$viewer = $this->getViewer();
|
|
|
|
$section = null;
|
|
|
|
$description = $task->getDescription();
|
|
if (strlen($description)) {
|
|
$section = new PHUIPropertyListView();
|
|
$section->addTextContent(
|
|
phutil_tag(
|
|
'div',
|
|
array(
|
|
'class' => 'phabricator-remarkup',
|
|
),
|
|
id(new PHUIRemarkupView($viewer, $description))
|
|
->setContextObject($task)));
|
|
}
|
|
|
|
return $section;
|
|
}
|
|
|
|
private function newMocksTab(
|
|
ManiphestTask $task,
|
|
PhabricatorEdgeQuery $edge_query) {
|
|
|
|
$mock_type = ManiphestTaskHasMockEdgeType::EDGECONST;
|
|
$mock_phids = $edge_query->getDestinationPHIDs(array(), array($mock_type));
|
|
if (!$mock_phids) {
|
|
return null;
|
|
}
|
|
|
|
$viewer = $this->getViewer();
|
|
$handles = $viewer->loadHandles($mock_phids);
|
|
|
|
// TODO: It would be nice to render this as pinboard-style thumbnails,
|
|
// similar to "{M123}", instead of a list of links.
|
|
|
|
$view = id(new PHUIPropertyListView())
|
|
->addProperty(pht('Mocks'), $handles->renderList());
|
|
|
|
return id(new PHUITabView())
|
|
->setName(pht('Mocks'))
|
|
->setKey('mocks')
|
|
->appendChild($view);
|
|
}
|
|
|
|
private function newMentionsTab(
|
|
ManiphestTask $task,
|
|
PhabricatorEdgeQuery $edge_query) {
|
|
|
|
$in_type = PhabricatorObjectMentionedByObjectEdgeType::EDGECONST;
|
|
$out_type = PhabricatorObjectMentionsObjectEdgeType::EDGECONST;
|
|
|
|
$in_phids = $edge_query->getDestinationPHIDs(array(), array($in_type));
|
|
$out_phids = $edge_query->getDestinationPHIDs(array(), array($out_type));
|
|
|
|
// Filter out any mentioned users from the list. These are not generally
|
|
// very interesting to show in a relationship summary since they usually
|
|
// end up as subscribers anyway.
|
|
|
|
$user_type = PhabricatorPeopleUserPHIDType::TYPECONST;
|
|
foreach ($out_phids as $key => $out_phid) {
|
|
if (phid_get_type($out_phid) == $user_type) {
|
|
unset($out_phids[$key]);
|
|
}
|
|
}
|
|
|
|
if (!$in_phids && !$out_phids) {
|
|
return null;
|
|
}
|
|
|
|
$viewer = $this->getViewer();
|
|
$in_handles = $viewer->loadHandles($in_phids);
|
|
$out_handles = $viewer->loadHandles($out_phids);
|
|
|
|
$in_handles = $this->getCompleteHandles($in_handles);
|
|
$out_handles = $this->getCompleteHandles($out_handles);
|
|
|
|
if (!count($in_handles) && !count($out_handles)) {
|
|
return null;
|
|
}
|
|
|
|
$view = new PHUIPropertyListView();
|
|
|
|
if (count($in_handles)) {
|
|
$view->addProperty(pht('Mentioned In'), $in_handles->renderList());
|
|
}
|
|
|
|
if (count($out_handles)) {
|
|
$view->addProperty(pht('Mentioned Here'), $out_handles->renderList());
|
|
}
|
|
|
|
return id(new PHUITabView())
|
|
->setName(pht('Mentions'))
|
|
->setKey('mentions')
|
|
->appendChild($view);
|
|
}
|
|
|
|
private function newDuplicatesTab(
|
|
ManiphestTask $task,
|
|
PhabricatorEdgeQuery $edge_query) {
|
|
|
|
$in_type = ManiphestTaskHasDuplicateTaskEdgeType::EDGECONST;
|
|
$in_phids = $edge_query->getDestinationPHIDs(array(), array($in_type));
|
|
|
|
$viewer = $this->getViewer();
|
|
$in_handles = $viewer->loadHandles($in_phids);
|
|
$in_handles = $this->getCompleteHandles($in_handles);
|
|
|
|
$view = new PHUIPropertyListView();
|
|
|
|
if (!count($in_handles)) {
|
|
return null;
|
|
}
|
|
|
|
$view->addProperty(
|
|
pht('Duplicates Merged Here'), $in_handles->renderList());
|
|
|
|
return id(new PHUITabView())
|
|
->setName(pht('Duplicates'))
|
|
->setKey('duplicates')
|
|
->appendChild($view);
|
|
}
|
|
|
|
private function getCompleteHandles(PhabricatorHandleList $handles) {
|
|
$phids = array();
|
|
|
|
foreach ($handles as $phid => $handle) {
|
|
if (!$handle->isComplete()) {
|
|
continue;
|
|
}
|
|
$phids[] = $phid;
|
|
}
|
|
|
|
return $handles->newSublist($phids);
|
|
}
|
|
|
|
private function newChangesView(ManiphestTask $task, array $edges) {
|
|
$viewer = $this->getViewer();
|
|
|
|
$revision_type = ManiphestTaskHasRevisionEdgeType::EDGECONST;
|
|
$commit_type = ManiphestTaskHasCommitEdgeType::EDGECONST;
|
|
|
|
$revision_phids = idx($edges, $revision_type, array());
|
|
$revision_phids = array_keys($revision_phids);
|
|
$revision_phids = array_fuse($revision_phids);
|
|
|
|
$commit_phids = idx($edges, $commit_type, array());
|
|
$commit_phids = array_keys($commit_phids);
|
|
$commit_phids = array_fuse($commit_phids);
|
|
|
|
if (!$revision_phids && !$commit_phids) {
|
|
return null;
|
|
}
|
|
|
|
if ($commit_phids) {
|
|
$link_type = DiffusionCommitHasRevisionEdgeType::EDGECONST;
|
|
$link_query = id(new PhabricatorEdgeQuery())
|
|
->withSourcePHIDs($commit_phids)
|
|
->withEdgeTypes(array($link_type));
|
|
$link_query->execute();
|
|
|
|
$commits = id(new DiffusionCommitQuery())
|
|
->setViewer($viewer)
|
|
->withPHIDs($commit_phids)
|
|
->execute();
|
|
$commits = mpull($commits, null, 'getPHID');
|
|
} else {
|
|
$commits = array();
|
|
}
|
|
|
|
if ($revision_phids) {
|
|
$revisions = id(new DifferentialRevisionQuery())
|
|
->setViewer($viewer)
|
|
->withPHIDs($revision_phids)
|
|
->execute();
|
|
$revisions = mpull($revisions, null, 'getPHID');
|
|
} else {
|
|
$revisions = array();
|
|
}
|
|
|
|
$handle_phids = array();
|
|
$any_linked = false;
|
|
$any_status = false;
|
|
|
|
$idx = 0;
|
|
$objects = array();
|
|
foreach ($commit_phids as $commit_phid) {
|
|
$handle_phids[] = $commit_phid;
|
|
|
|
$link_phids = $link_query->getDestinationPHIDs(array($commit_phid));
|
|
foreach ($link_phids as $link_phid) {
|
|
$handle_phids[] = $link_phid;
|
|
unset($revision_phids[$link_phid]);
|
|
$any_linked = true;
|
|
}
|
|
|
|
$commit = idx($commits, $commit_phid);
|
|
if ($commit) {
|
|
$repository_phid = $commit->getRepository()->getPHID();
|
|
$handle_phids[] = $repository_phid;
|
|
} else {
|
|
$repository_phid = null;
|
|
}
|
|
|
|
$status_view = null;
|
|
if ($commit) {
|
|
$status = $commit->getAuditStatusObject();
|
|
if (!$status->isNoAudit()) {
|
|
$status_view = id(new PHUITagView())
|
|
->setType(PHUITagView::TYPE_SHADE)
|
|
->setIcon($status->getIcon())
|
|
->setColor($status->getColor())
|
|
->setName($status->getName());
|
|
}
|
|
}
|
|
|
|
$object_link = null;
|
|
if ($commit) {
|
|
$commit_monogram = $commit->getDisplayName();
|
|
$commit_monogram = phutil_tag(
|
|
'span',
|
|
array(
|
|
'class' => 'object-name',
|
|
),
|
|
$commit_monogram);
|
|
|
|
$commit_link = javelin_tag(
|
|
'a',
|
|
array(
|
|
'href' => $commit->getURI(),
|
|
'sigil' => 'hovercard',
|
|
'meta' => array(
|
|
'hoverPHID' => $commit->getPHID(),
|
|
),
|
|
),
|
|
$commit->getSummary());
|
|
|
|
$object_link = array(
|
|
$commit_monogram,
|
|
' ',
|
|
$commit_link,
|
|
);
|
|
}
|
|
|
|
$objects[] = array(
|
|
'objectPHID' => $commit_phid,
|
|
'objectLink' => $object_link,
|
|
'repositoryPHID' => $repository_phid,
|
|
'revisionPHIDs' => $link_phids,
|
|
'status' => $status_view,
|
|
'order' => id(new PhutilSortVector())
|
|
->addInt($repository_phid ? 1 : 0)
|
|
->addString((string)$repository_phid)
|
|
->addInt(1)
|
|
->addInt($idx++),
|
|
);
|
|
}
|
|
|
|
foreach ($revision_phids as $revision_phid) {
|
|
$handle_phids[] = $revision_phid;
|
|
|
|
$revision = idx($revisions, $revision_phid);
|
|
if ($revision) {
|
|
$repository_phid = $revision->getRepositoryPHID();
|
|
$handle_phids[] = $repository_phid;
|
|
} else {
|
|
$repository_phid = null;
|
|
}
|
|
|
|
if ($revision) {
|
|
$icon = $revision->getStatusIcon();
|
|
$color = $revision->getStatusIconColor();
|
|
$name = $revision->getStatusDisplayName();
|
|
|
|
$status_view = id(new PHUITagView())
|
|
->setType(PHUITagView::TYPE_SHADE)
|
|
->setIcon($icon)
|
|
->setColor($color)
|
|
->setName($name);
|
|
} else {
|
|
$status_view = null;
|
|
}
|
|
|
|
$object_link = null;
|
|
if ($revision) {
|
|
$revision_monogram = $revision->getMonogram();
|
|
$revision_monogram = phutil_tag(
|
|
'span',
|
|
array(
|
|
'class' => 'object-name',
|
|
),
|
|
$revision_monogram);
|
|
|
|
$revision_link = javelin_tag(
|
|
'a',
|
|
array(
|
|
'href' => $revision->getURI(),
|
|
'sigil' => 'hovercard',
|
|
'meta' => array(
|
|
'hoverPHID' => $revision->getPHID(),
|
|
),
|
|
),
|
|
$revision->getTitle());
|
|
|
|
$object_link = array(
|
|
$revision_monogram,
|
|
' ',
|
|
$revision_link,
|
|
);
|
|
}
|
|
|
|
$objects[] = array(
|
|
'objectPHID' => $revision_phid,
|
|
'objectLink' => $object_link,
|
|
'repositoryPHID' => $repository_phid,
|
|
'revisionPHIDs' => array(),
|
|
'status' => $status_view,
|
|
'order' => id(new PhutilSortVector())
|
|
->addInt($repository_phid ? 1 : 0)
|
|
->addString((string)$repository_phid)
|
|
->addInt(0)
|
|
->addInt($idx++),
|
|
);
|
|
}
|
|
|
|
$handles = $viewer->loadHandles($handle_phids);
|
|
|
|
$order = ipull($objects, 'order');
|
|
$order = msortv($order, 'getSelf');
|
|
$objects = array_select_keys($objects, array_keys($order));
|
|
|
|
$last_repository = false;
|
|
$rows = array();
|
|
$rowd = array();
|
|
foreach ($objects as $object) {
|
|
$repository_phid = $object['repositoryPHID'];
|
|
if ($repository_phid !== $last_repository) {
|
|
$repository_link = null;
|
|
if ($repository_phid) {
|
|
$repository_handle = $handles[$repository_phid];
|
|
$rows[] = array(
|
|
$repository_handle->renderLink(),
|
|
);
|
|
$rowd[] = true;
|
|
}
|
|
|
|
$last_repository = $repository_phid;
|
|
}
|
|
|
|
$object_phid = $object['objectPHID'];
|
|
$handle = $handles[$object_phid];
|
|
|
|
$object_link = $object['objectLink'];
|
|
if ($object_link === null) {
|
|
$object_link = $handle->renderLink();
|
|
}
|
|
|
|
$object_icon = id(new PHUIIconView())
|
|
->setIcon($handle->getIcon());
|
|
|
|
$status_view = $object['status'];
|
|
if ($status_view) {
|
|
$any_status = true;
|
|
}
|
|
|
|
$revision_tags = array();
|
|
foreach ($object['revisionPHIDs'] as $link_phid) {
|
|
$revision_handle = $handles[$link_phid];
|
|
|
|
$revision_name = $revision_handle->getName();
|
|
$revision_tags[] = $revision_handle
|
|
->renderHovercardLink($revision_name);
|
|
}
|
|
$revision_tags = phutil_implode_html(
|
|
phutil_tag('br'),
|
|
$revision_tags);
|
|
|
|
$rowd[] = false;
|
|
$rows[] = array(
|
|
$object_icon,
|
|
$status_view,
|
|
$revision_tags,
|
|
$object_link,
|
|
);
|
|
}
|
|
|
|
$changes_table = id(new AphrontTableView($rows))
|
|
->setNoDataString(pht('This task has no related commits or revisions.'))
|
|
->setRowDividers($rowd)
|
|
->setColumnClasses(
|
|
array(
|
|
'indent center',
|
|
null,
|
|
null,
|
|
'wide pri object-link',
|
|
))
|
|
->setColumnVisibility(
|
|
array(
|
|
true,
|
|
$any_status,
|
|
$any_linked,
|
|
true,
|
|
))
|
|
->setDeviceVisibility(
|
|
array(
|
|
false,
|
|
$any_status,
|
|
false,
|
|
true,
|
|
));
|
|
|
|
$changes_header = id(new PHUIHeaderView())
|
|
->setHeader(pht('Revisions and Commits'));
|
|
|
|
$changes_view = id(new PHUIObjectBoxView())
|
|
->setHeader($changes_header)
|
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
|
->setTable($changes_table);
|
|
|
|
return $changes_view;
|
|
}
|
|
|
|
|
|
}
|