diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0e53737aaf..8547f8a0a3 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,10 +9,10 @@ return array( 'names' => array( 'conpherence.pkg.css' => '3c8a0668', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => '686ae87c', + 'core.pkg.css' => '77de226f', 'core.pkg.js' => '6e5c894f', 'differential.pkg.css' => '607c84be', - 'differential.pkg.js' => 'a0212a0b', + 'differential.pkg.js' => '1b97518d', 'diffusion.pkg.css' => '42c75c37', 'diffusion.pkg.js' => 'a98c0bf7', 'maniphest.pkg.css' => '35995d6d', @@ -30,7 +30,7 @@ return array( 'rsrc/css/aphront/notification.css' => '30240bd2', 'rsrc/css/aphront/panel-view.css' => '46923d46', 'rsrc/css/aphront/phabricator-nav-view.css' => 'f8a0c1bf', - 'rsrc/css/aphront/table-view.css' => '5f13a9e4', + 'rsrc/css/aphront/table-view.css' => '0bb61df1', 'rsrc/css/aphront/tokenizer.css' => 'b52d0668', 'rsrc/css/aphront/tooltip.css' => 'e3f2412f', 'rsrc/css/aphront/typeahead-browse.css' => 'b7ed02d2', @@ -428,7 +428,7 @@ return array( 'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68', 'rsrc/js/application/releeph/releeph-request-state-change.js' => '9f081f05', 'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'aa3a100c', - 'rsrc/js/application/repository/repository-crossreference.js' => 'c15122b4', + 'rsrc/js/application/repository/repository-crossreference.js' => '1c95ea63', 'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e5bdb730', 'rsrc/js/application/search/behavior-reorder-queries.js' => 'b86f297f', 'rsrc/js/application/transactions/behavior-comment-actions.js' => '4dffaeb2', @@ -535,7 +535,7 @@ return array( 'aphront-list-filter-view-css' => 'feb64255', 'aphront-multi-column-view-css' => 'fbc00ba3', 'aphront-panel-view-css' => '46923d46', - 'aphront-table-view-css' => '5f13a9e4', + 'aphront-table-view-css' => '0bb61df1', 'aphront-tokenizer-control-css' => 'b52d0668', 'aphront-tooltip-css' => 'e3f2412f', 'aphront-typeahead-control-css' => '8779483d', @@ -682,7 +682,7 @@ return array( 'javelin-behavior-reorder-applications' => 'aa371860', 'javelin-behavior-reorder-columns' => '8ac32fd9', 'javelin-behavior-reorder-profile-menu-items' => 'e5bdb730', - 'javelin-behavior-repository-crossreference' => 'c15122b4', + 'javelin-behavior-repository-crossreference' => '1c95ea63', 'javelin-behavior-scrollbar' => '92388bae', 'javelin-behavior-search-reorder-queries' => 'b86f297f', 'javelin-behavior-select-content' => 'e8240b50', @@ -1034,6 +1034,12 @@ return array( 'javelin-install', 'javelin-util', ), + '1c95ea63' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-uri', + ), '1cab0e9a' => array( 'javelin-behavior', 'javelin-dom', @@ -1977,12 +1983,6 @@ return array( 'c03f2fb4' => array( 'javelin-install', ), - 'c15122b4' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-uri', - ), 'c2c500a7' => array( 'javelin-install', 'javelin-dom', diff --git a/resources/sql/autopatches/20191028.uriindex.01.rebuild.php b/resources/sql/autopatches/20191028.uriindex.01.rebuild.php new file mode 100644 index 0000000000..c9bd3d97ca --- /dev/null +++ b/resources/sql/autopatches/20191028.uriindex.01.rebuild.php @@ -0,0 +1,4 @@ + 'applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php', 'HarbormasterArcUnitBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php', 'HarbormasterArtifact' => 'applications/harbormaster/artifact/HarbormasterArtifact.php', + 'HarbormasterArtifactSearchConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterArtifactSearchConduitAPIMethod.php', + 'HarbormasterArtifactSearchEngine' => 'applications/harbormaster/query/HarbormasterArtifactSearchEngine.php', 'HarbormasterAutotargetsTestCase' => 'applications/harbormaster/__tests__/HarbormasterAutotargetsTestCase.php', 'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php', 'HarbormasterBuildAbortedException' => 'applications/harbormaster/exception/HarbormasterBuildAbortedException.php', @@ -4650,6 +4652,7 @@ phutil_register_library_map(array( 'PhabricatorSearchHovercardController' => 'applications/search/controller/PhabricatorSearchHovercardController.php', 'PhabricatorSearchIndexVersion' => 'applications/search/storage/PhabricatorSearchIndexVersion.php', 'PhabricatorSearchIndexVersionDestructionEngineExtension' => 'applications/search/engineextension/PhabricatorSearchIndexVersionDestructionEngineExtension.php', + 'PhabricatorSearchIntField' => 'applications/search/field/PhabricatorSearchIntField.php', 'PhabricatorSearchManagementIndexWorkflow' => 'applications/search/management/PhabricatorSearchManagementIndexWorkflow.php', 'PhabricatorSearchManagementInitWorkflow' => 'applications/search/management/PhabricatorSearchManagementInitWorkflow.php', 'PhabricatorSearchManagementNgramsWorkflow' => 'applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php', @@ -7369,6 +7372,8 @@ phutil_register_library_map(array( 'HarbormasterArcLintBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterArcUnitBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterArtifact' => 'Phobject', + 'HarbormasterArtifactSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'HarbormasterArtifactSearchEngine' => 'PhabricatorApplicationSearchEngine', 'HarbormasterAutotargetsTestCase' => 'PhabricatorTestCase', 'HarbormasterBuild' => array( 'HarbormasterDAO', @@ -7384,6 +7389,7 @@ phutil_register_library_map(array( 'HarbormasterDAO', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorConduitResultInterface', ), 'HarbormasterBuildArtifactPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', @@ -11268,6 +11274,7 @@ phutil_register_library_map(array( 'PhabricatorSearchHovercardController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchIndexVersion' => 'PhabricatorSearchDAO', 'PhabricatorSearchIndexVersionDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', + 'PhabricatorSearchIntField' => 'PhabricatorSearchField', 'PhabricatorSearchManagementIndexWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementInitWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementNgramsWorkflow' => 'PhabricatorSearchManagementWorkflow', diff --git a/src/applications/differential/phid/DifferentialRevisionPHIDType.php b/src/applications/differential/phid/DifferentialRevisionPHIDType.php index a117690d66..a7d3c9f4a7 100644 --- a/src/applications/differential/phid/DifferentialRevisionPHIDType.php +++ b/src/applications/differential/phid/DifferentialRevisionPHIDType.php @@ -44,15 +44,6 @@ final class DifferentialRevisionPHIDType extends PhabricatorPHIDType { if ($revision->isClosed()) { $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); } - - $icon = $revision->getStatusIcon(); - $color = $revision->getStatusIconColor(); - $name = $revision->getStatusDisplayName(); - - $handle - ->setStateIcon($icon) - ->setStateColor($color) - ->setStateName($name); } } diff --git a/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php b/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php index 3ce95cb3c3..cd0b3cceab 100644 --- a/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php +++ b/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php @@ -26,20 +26,45 @@ final class DiffusionHovercardEngineExtension $viewer = $this->getViewer(); - $author_phid = $commit->getAuthorPHID(); - if ($author_phid) { - $author = $viewer->renderHandle($author_phid); - } else { - $commit_data = $commit->loadCommitData(); - $author = phutil_tag('em', array(), $commit_data->getAuthorName()); + $commit = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->needIdentities(true) + ->needCommitData(true) + ->withPHIDs(array($commit->getPHID())) + ->executeOne(); + if (!$commit) { + return; } + $author_phid = $commit->getAuthorDisplayPHID(); + $committer_phid = $commit->getCommitterDisplayPHID(); + $repository_phid = $commit->getRepository()->getPHID(); + + $phids = array(); + $phids[] = $author_phid; + $phids[] = $committer_phid; + $phids[] = $repository_phid; + + $handles = $viewer->loadHandles($phids); + $hovercard->setTitle($handle->getName()); $hovercard->setDetail($commit->getSummary()); - $hovercard->addField(pht('Author'), $author); - $hovercard->addField(pht('Date'), - phabricator_date($commit->getEpoch(), $viewer)); + $repository = $handles[$repository_phid]->renderLink(); + $hovercard->addField(pht('Repository'), $repository); + + $author = $handles[$author_phid]->renderLink(); + if ($author_phid) { + $hovercard->addField(pht('Author'), $author); + } + + if ($committer_phid && ($committer_phid !== $author_phid)) { + $committer = $handles[$committer_phid]->renderLink(); + $hovercard->addField(pht('Committer'), $committer); + } + + $date = phabricator_date($commit->getEpoch(), $viewer); + $hovercard->addField(pht('Date'), $date); if (!$commit->isAuditStatusNoAudit()) { $status = $commit->getAuditStatusObject(); diff --git a/src/applications/fact/chart/PhabricatorFactChartFunction.php b/src/applications/fact/chart/PhabricatorFactChartFunction.php index 0e940d644d..1713282116 100644 --- a/src/applications/fact/chart/PhabricatorFactChartFunction.php +++ b/src/applications/fact/chart/PhabricatorFactChartFunction.php @@ -29,6 +29,7 @@ final class PhabricatorFactChartFunction $key_id = id(new PhabricatorFactKeyDimension()) ->newDimensionID($fact->getKey()); if (!$key_id) { + $this->map = array(); return; } diff --git a/src/applications/harbormaster/conduit/HarbormasterArtifactSearchConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterArtifactSearchConduitAPIMethod.php new file mode 100644 index 0000000000..63cba16af4 --- /dev/null +++ b/src/applications/harbormaster/conduit/HarbormasterArtifactSearchConduitAPIMethod.php @@ -0,0 +1,18 @@ +setLabel(pht('Targets')) + ->setKey('buildTargetPHIDs') + ->setAliases( + array( + 'buildTargetPHID', + 'buildTargets', + 'buildTarget', + 'targetPHIDs', + 'targetPHID', + 'targets', + 'target', + )) + ->setDescription( + pht('Search for artifacts attached to particular build targets.')), + ); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['buildTargetPHIDs']) { + $query->withBuildTargetPHIDs($map['buildTargetPHIDs']); + } + + return $query; + } + + protected function getURI($path) { + return '/harbormaster/artifact/'.$path; + } + + protected function getBuiltinQueryNames() { + return array( + 'all' => pht('All Artifacts'), + ); + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $artifacts, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($artifacts, 'HarbormasterBuildArtifact'); + + $viewer = $this->requireViewer(); + + $list = new PHUIObjectItemListView(); + foreach ($artifacts as $artifact) { + $id = $artifact->getID(); + + $item = id(new PHUIObjectItemView()) + ->setObjectName(pht('Artifact %d', $id)); + + $list->addItem($item); + } + + return id(new PhabricatorApplicationSearchResultView()) + ->setObjectList($list) + ->setNoDataString(pht('No artifacts found.')); + } + +} diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php index 7cd8d60b6a..8b4972c154 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php @@ -4,7 +4,8 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO implements PhabricatorPolicyInterface, - PhabricatorDestructibleInterface { + PhabricatorDestructibleInterface, + PhabricatorConduitResultInterface { protected $buildTargetPHID; protected $artifactType; @@ -18,6 +19,7 @@ final class HarbormasterBuildArtifact public static function initializeNewBuildArtifact( HarbormasterBuildTarget $build_target) { + return id(new HarbormasterBuildArtifact()) ->attachBuildTarget($build_target) ->setBuildTargetPHID($build_target->getPHID()); @@ -53,9 +55,8 @@ final class HarbormasterBuildArtifact ) + parent::getConfiguration(); } - public function generatePHID() { - return PhabricatorPHID::generateNewPHID( - HarbormasterBuildArtifactPHIDType::TYPECONST); + public function getPHIDType() { + return HarbormasterBuildArtifactPHIDType::TYPECONST; } public function attachBuildTarget(HarbormasterBuildTarget $build_target) { @@ -147,7 +148,8 @@ final class HarbormasterBuildArtifact } public function describeAutomaticCapability($capability) { - return pht('Users must be able to see a buildable to see its artifacts.'); + return pht( + 'Users must be able to see a build target to see its artifacts.'); } @@ -165,4 +167,40 @@ final class HarbormasterBuildArtifact $this->saveTransaction(); } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('buildTargetPHID') + ->setType('phid') + ->setDescription(pht('The build target this artifact is attached to.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('artifactType') + ->setType('string') + ->setDescription(pht('The artifact type.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('artifactKey') + ->setType('string') + ->setDescription(pht('The artifact key.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('isReleased') + ->setType('bool') + ->setDescription(pht('True if this artifact has been released.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'buildTargetPHID' => $this->getBuildTargetPHID(), + 'artifactType' => $this->getArtifactType(), + 'artifactKey' => $this->getArtifactKey(), + 'isReleased' => (bool)$this->getIsReleased(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } } diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index c5dba7d3b5..b6985268db 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -179,11 +179,14 @@ final class ManiphestTaskDetailController extends ManiphestController { ->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, @@ -395,58 +398,6 @@ final class ManiphestTaskDetailController extends ManiphestController { $source)); } - $edge_types = array( - ManiphestTaskHasRevisionEdgeType::EDGECONST - => pht('Differential Revisions'), - ); - - $revisions_commits = array(); - - $commit_phids = array_keys( - $edges[ManiphestTaskHasCommitEdgeType::EDGECONST]); - if ($commit_phids) { - $commit_drev = DiffusionCommitHasRevisionEdgeType::EDGECONST; - $drev_edges = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs($commit_phids) - ->withEdgeTypes(array($commit_drev)) - ->execute(); - - foreach ($commit_phids as $phid) { - $revisions_commits[$phid] = $handles->renderHandle($phid) - ->setShowHovercard(true) - ->setShowStateIcon(true); - $revision_phid = key($drev_edges[$phid][$commit_drev]); - $revision_handle = $handles->getHandleIfExists($revision_phid); - if ($revision_handle) { - $task_drev = ManiphestTaskHasRevisionEdgeType::EDGECONST; - unset($edges[$task_drev][$revision_phid]); - $revisions_commits[$phid] = hsprintf( - '%s / %s', - $revision_handle->renderHovercardLink($revision_handle->getName()), - $revisions_commits[$phid]); - } - } - } - - foreach ($edge_types as $edge_type => $edge_name) { - if (!$edges[$edge_type]) { - continue; - } - - $edge_handles = $viewer->loadHandles(array_keys($edges[$edge_type])); - - $edge_list = $edge_handles->renderList() - ->setShowStateIcons(true); - - $view->addProperty($edge_name, $edge_list); - } - - if ($revisions_commits) { - $view->addProperty( - pht('Commits'), - phutil_implode_html(phutil_tag('br'), $revisions_commits)); - } - $field_list->appendFieldsToPropertyList( $task, $viewer, @@ -596,5 +547,291 @@ final class ManiphestTaskDetailController extends ManiphestController { 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; + } + } diff --git a/src/applications/phid/PhabricatorObjectHandle.php b/src/applications/phid/PhabricatorObjectHandle.php index ba93dbcead..86f0f848c0 100644 --- a/src/applications/phid/PhabricatorObjectHandle.php +++ b/src/applications/phid/PhabricatorObjectHandle.php @@ -33,10 +33,6 @@ final class PhabricatorObjectHandle private $commandLineObjectName; private $mailStampName; - private $stateIcon; - private $stateColor; - private $stateName; - public function setIcon($icon) { $this->icon = $icon; return $this; @@ -299,55 +295,6 @@ final class PhabricatorObjectHandle return $this->complete; } - public function setStateIcon($state_icon) { - $this->stateIcon = $state_icon; - return $this; - } - - public function getStateIcon() { - return $this->stateIcon; - } - - public function setStateColor($state_color) { - $this->stateColor = $state_color; - return $this; - } - - public function getStateColor() { - return $this->stateColor; - } - - public function setStateName($state_name) { - $this->stateName = $state_name; - return $this; - } - - public function getStateName() { - return $this->stateName; - } - - public function renderStateIcon() { - $icon = $this->getStateIcon(); - if ($icon === null) { - $icon = 'fa-question-circle-o'; - } - - $color = $this->getStateColor(); - - $name = $this->getStateName(); - if ($name === null) { - $name = pht('Unknown'); - } - - return id(new PHUIIconView()) - ->setIcon($icon, $color) - ->addSigil('has-tooltip') - ->setMetadata( - array( - 'tip' => $name, - )); - } - public function renderLink($name = null) { return $this->renderLinkWithAttributes($name, array()); } diff --git a/src/applications/phid/view/PHUIHandleListView.php b/src/applications/phid/view/PHUIHandleListView.php index 24104fe76d..c5b2f19784 100644 --- a/src/applications/phid/view/PHUIHandleListView.php +++ b/src/applications/phid/view/PHUIHandleListView.php @@ -13,7 +13,6 @@ final class PHUIHandleListView private $handleList; private $asInline; private $asText; - private $showStateIcons; private $glyphLimit; public function setHandleList(PhabricatorHandleList $list) { @@ -39,15 +38,6 @@ final class PHUIHandleListView return $this->asText; } - public function setShowStateIcons($show_state_icons) { - $this->showStateIcons = $show_state_icons; - return $this; - } - - public function getShowStateIcons() { - return $this->showStateIcons; - } - public function setGlyphLimit($glyph_limit) { $this->glyphLimit = $glyph_limit; return $this; @@ -70,7 +60,6 @@ final class PHUIHandleListView protected function getTagContent() { $list = $this->handleList; - $show_state_icons = $this->getShowStateIcons(); $glyph_limit = $this->getGlyphLimit(); $items = array(); @@ -79,10 +68,6 @@ final class PHUIHandleListView ->setShowHovercard(true) ->setAsText($this->getAsText()); - if ($show_state_icons) { - $view->setShowStateIcon(true); - } - if ($glyph_limit) { $view->setGlyphLimit($glyph_limit); } diff --git a/src/applications/phid/view/PHUIHandleView.php b/src/applications/phid/view/PHUIHandleView.php index fe3c62a9ac..6cdf84f391 100644 --- a/src/applications/phid/view/PHUIHandleView.php +++ b/src/applications/phid/view/PHUIHandleView.php @@ -17,7 +17,6 @@ final class PHUIHandleView private $asText; private $useShortName; private $showHovercard; - private $showStateIcon; private $glyphLimit; public function setHandleList(PhabricatorHandleList $list) { @@ -50,15 +49,6 @@ final class PHUIHandleView return $this; } - public function setShowStateIcon($show_state_icon) { - $this->showStateIcon = $show_state_icon; - return $this; - } - - public function getShowStateIcon() { - return $this->showStateIcon; - } - public function setGlyphLimit($glyph_limit) { $this->glyphLimit = $glyph_limit; return $this; @@ -104,11 +94,6 @@ final class PHUIHandleView $link = $handle->renderLink($name); } - if ($this->showStateIcon) { - $icon = $handle->renderStateIcon(); - $link = array($icon, ' ', $link); - } - return $link; } diff --git a/src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php b/src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php index 1296f2eec8..16760d515f 100644 --- a/src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php +++ b/src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php @@ -53,7 +53,11 @@ final class PhabricatorProjectBurndownChartEngine $open_function = $this->newFunction( array( 'accumulate', - array('fact', 'tasks.open-count.create'), + array( + 'sum', + array('fact', 'tasks.open-count.create'), + array('fact', 'tasks.open-count.status'), + ), )); $closed_function = $this->newFunction( diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 88a35676cc..cb179c995f 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -65,6 +65,35 @@ final class PhabricatorProjectSearchEngine pht( 'Pass true to find only milestones, or false to omit '. 'milestones.')), + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Root Projects')) + ->setKey('isRoot') + ->setOptions( + pht('(Show All)'), + pht('Show Only Root Projects'), + pht('Hide Root Projects')) + ->setDescription( + pht( + 'Pass true to find only root projects, or false to omit '. + 'root projects.')), + id(new PhabricatorSearchIntField()) + ->setLabel(pht('Minimum Depth')) + ->setKey('minDepth') + ->setIsHidden(true) + ->setDescription( + pht( + 'Find projects with a given minimum depth. Root projects '. + 'have depth 0, their immediate children have depth 1, and '. + 'so on.')), + id(new PhabricatorSearchIntField()) + ->setLabel(pht('Maximum Depth')) + ->setKey('maxDepth') + ->setIsHidden(true) + ->setDescription( + pht( + 'Find projects with a given maximum depth. Root projects '. + 'have depth 0, their immediate children have depth 1, and '. + 'so on.')), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Subtypes')) ->setKey('subtypes') @@ -137,6 +166,42 @@ final class PhabricatorProjectSearchEngine $query->withIsMilestone($map['isMilestone']); } + $min_depth = $map['minDepth']; + $max_depth = $map['maxDepth']; + + if ($min_depth !== null || $max_depth !== null) { + if ($min_depth !== null && $max_depth !== null) { + if ($min_depth > $max_depth) { + throw new Exception( + pht( + 'Search constraint "minDepth" must be no larger than '. + 'search constraint "maxDepth".')); + } + } + } + + if ($map['isRoot'] !== null) { + if ($map['isRoot']) { + if ($max_depth === null) { + $max_depth = 0; + } else { + $max_depth = min(0, $max_depth); + } + + $query->withDepthBetween(null, 0); + } else { + if ($min_depth === null) { + $min_depth = 1; + } else { + $min_depth = max($min_depth, 1); + } + } + } + + if ($min_depth !== null || $max_depth !== null) { + $query->withDepthBetween($min_depth, $max_depth); + } + if ($map['parentPHIDs']) { $query->withParentProjectPHIDs($map['parentPHIDs']); } diff --git a/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php b/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php index a29d110a67..935d78fea5 100644 --- a/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php +++ b/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php @@ -130,10 +130,35 @@ final class PhabricatorRepositoryURINormalizer extends Phobject { $domain = $uri->getDomain(); if (!strlen($domain)) { - $domain = ''; + return ''; } - return phutil_utf8_strtolower($domain); + $domain = phutil_utf8_strtolower($domain); + + // See T13435. If the domain for a repository URI is same as the install + // base URI, store it as a "" token instead of the actual domain + // so that the index does not fall out of date if the install moves. + + $base_uri = PhabricatorEnv::getURI('/'); + $base_uri = new PhutilURI($base_uri); + $base_domain = $base_uri->getDomain(); + $base_domain = phutil_utf8_strtolower($base_domain); + if ($domain === $base_domain) { + return ''; + } + + // Likewise, store a token for the "SSH Host" domain so it can be changed + // without requiring an index rebuild. + + $ssh_host = PhabricatorEnv::getEnvConfig('diffusion.ssh-host'); + if (strlen($ssh_host)) { + $ssh_host = phutil_utf8_strtolower($ssh_host); + if ($domain === $ssh_host) { + return ''; + } + } + + return $domain; } diff --git a/src/applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php b/src/applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php index 81dd735562..8ab54a23a4 100644 --- a/src/applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php +++ b/src/applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php @@ -31,6 +31,36 @@ final class PhabricatorRepositoryURINormalizerTestCase } } + public function testDomainURINormalizer() { + $base_domain = 'base.phabricator.example.com'; + $ssh_domain = 'ssh.phabricator.example.com'; + + $env = PhabricatorEnv::beginScopedEnv(); + $env->overrideEnvConfig('phabricator.base-uri', 'http://'.$base_domain); + $env->overrideEnvConfig('diffusion.ssh-host', $ssh_domain); + + $cases = array( + '/' => '', + '/path/to/local/repo.git' => '', + 'ssh://user@domain.com/path.git' => 'domain.com', + 'ssh://user@DOMAIN.COM/path.git' => 'domain.com', + 'http://'.$base_domain.'/diffusion/X/' => '', + 'ssh://'.$ssh_domain.'/diffusion/X/' => '', + 'git@'.$ssh_domain.':bananas.git' => '', + ); + + $type_git = PhabricatorRepositoryURINormalizer::TYPE_GIT; + + foreach ($cases as $input => $expect) { + $normal = new PhabricatorRepositoryURINormalizer($type_git, $input); + + $this->assertEqual( + $expect, + $normal->getNormalizedDomain(), + pht('Normalized domain for "%s".', $input)); + } + } + public function testSVNURINormalizer() { $cases = array( 'file:///path/to/repo' => 'path/to/repo', diff --git a/src/applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php b/src/applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php index c37bdc04f9..df84f2dcfd 100644 --- a/src/applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php +++ b/src/applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php @@ -81,16 +81,6 @@ final class PhabricatorRepositoryCommitPHIDType extends PhabricatorPHIDType { $handle->setFullName($full_name); $handle->setURI($commit->getURI()); $handle->setTimestamp($commit->getEpoch()); - - $status = $commit->getAuditStatusObject(); - $icon = $status->getIcon(); - $color = $status->getColor(); - $name = $status->getName(); - - $handle - ->setStateIcon($icon) - ->setStateColor($color) - ->setStateName($name); } } diff --git a/src/applications/search/field/PhabricatorSearchIntField.php b/src/applications/search/field/PhabricatorSearchIntField.php new file mode 100644 index 0000000000..70af934470 --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchIntField.php @@ -0,0 +1,22 @@ +getInt($key); + } + + protected function newControl() { + return new AphrontFormTextControl(); + } + + protected function newConduitParameterType() { + return new ConduitIntParameterType(); + } + +} diff --git a/src/view/control/AphrontTableView.php b/src/view/control/AphrontTableView.php index cae9dabec2..a3c0a49be4 100644 --- a/src/view/control/AphrontTableView.php +++ b/src/view/control/AphrontTableView.php @@ -24,6 +24,8 @@ final class AphrontTableView extends AphrontView { protected $sortValues = array(); private $deviceReadyTable; + private $rowDividers = array(); + public function __construct(array $data) { $this->data = $data; } @@ -53,6 +55,11 @@ final class AphrontTableView extends AphrontView { return $this; } + public function setRowDividers(array $dividers) { + $this->rowDividers = $dividers; + return $this; + } + public function setNoDataString($no_data_string) { $this->noDataString = $no_data_string; return $this; @@ -258,10 +265,15 @@ final class AphrontTableView extends AphrontView { } } + $dividers = $this->rowDividers; + $data = $this->data; if ($data) { $row_num = 0; + $row_idx = 0; foreach ($data as $row) { + $is_divider = !empty($dividers[$row_num]); + $row_size = count($row); while (count($row) > count($col_classes)) { $col_classes[] = null; @@ -289,6 +301,18 @@ final class AphrontTableView extends AphrontView { $class = trim($class.' '.$this->cellClasses[$row_num][$col_num]); } + if ($is_divider) { + $tr[] = phutil_tag( + 'td', + array( + 'class' => 'row-divider', + 'colspan' => count($visibility), + ), + $value); + $row_idx = -1; + break; + } + $tr[] = phutil_tag( 'td', array( @@ -299,7 +323,7 @@ final class AphrontTableView extends AphrontView { } $class = idx($this->rowClasses, $row_num); - if ($this->zebraStripes && ($row_num % 2)) { + if ($this->zebraStripes && ($row_idx % 2)) { if ($class !== null) { $class = 'alt alt-'.$class; } else { @@ -309,6 +333,7 @@ final class AphrontTableView extends AphrontView { $table[] = phutil_tag('tr', array('class' => $class), $tr); ++$row_num; + ++$row_idx; } } else { $colspan = max(count(array_filter($visibility)), 1); diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index 3736ffe841..e92f499634 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -55,6 +55,16 @@ background-color: {$lightbluebackground}; } +.aphront-table-view td.row-divider { + background-color: {$bluebackground}; + font-weight: bold; + padding: 8px 12px; +} + +.aphront-table-view td.indent { + padding-left: 24px; +} + .aphront-table-view th { border-bottom: 1px solid {$thinblueborder}; } diff --git a/webroot/rsrc/js/application/repository/repository-crossreference.js b/webroot/rsrc/js/application/repository/repository-crossreference.js index d6ff2a06aa..ba522d5b47 100644 --- a/webroot/rsrc/js/application/repository/repository-crossreference.js +++ b/webroot/rsrc/js/application/repository/repository-crossreference.js @@ -152,7 +152,16 @@ JX.behavior('repository-crossreference', function(config, statics) { query.char = char; } - var uri = JX.$U('/diffusion/symbol/' + symbol + '/'); + var uri_symbol = symbol; + + // In some cases, lexers may include whitespace in symbol tags. Trim it, + // since symbols with semantic whitespace aren't supported. + uri_symbol = uri_symbol.trim(); + + // See T13437. Symbols like "#define" need to be encoded. + uri_symbol = encodeURIComponent(uri_symbol); + + var uri = JX.$U('/diffusion/symbol/' + uri_symbol + '/'); uri.addQueryParams(query); window.open(uri.toString());