diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -16,7 +16,7 @@ 'diffusion.pkg.js' => '3a9a8bfa', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '949a7498', - 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', + 'rsrc/css/aphront/aphront-bars.css' => '9ce6a9a2', 'rsrc/css/aphront/dark-console.css' => '6378ef3d', 'rsrc/css/aphront/dialog-view.css' => 'be0e3a46', 'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d', @@ -510,7 +510,7 @@ ), 'symbols' => array( 'almanac-css' => 'dbb9b3af', - 'aphront-bars' => '231ac33c', + 'aphront-bars' => '9ce6a9a2', 'aphront-dark-console-css' => '6378ef3d', 'aphront-dialog-view-css' => 'be0e3a46', 'aphront-list-filter-view-css' => '5d6f0526', 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 @@ -584,6 +584,7 @@ 'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php', 'DiffusionConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionConduitAPIMethod.php', 'DiffusionController' => 'applications/diffusion/controller/DiffusionController.php', + 'DiffusionCoverageBarView' => 'applications/diffusion/view/DiffusionCoverageBarView.php', 'DiffusionCreateCommentConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCreateCommentConduitAPIMethod.php', 'DiffusionCreateRepositoriesCapability' => 'applications/diffusion/capability/DiffusionCreateRepositoriesCapability.php', 'DiffusionDefaultEditCapability' => 'applications/diffusion/capability/DiffusionDefaultEditCapability.php', @@ -4607,6 +4608,7 @@ 'DiffusionCommitTagsController' => 'DiffusionController', 'DiffusionConduitAPIMethod' => 'ConduitAPIMethod', 'DiffusionController' => 'PhabricatorController', + 'DiffusionCoverageBarView' => 'AphrontBarView', 'DiffusionCreateCommentConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionCreateRepositoriesCapability' => 'PhabricatorPolicyCapability', 'DiffusionDefaultEditCapability' => 'PhabricatorPolicyCapability', diff --git a/src/applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php --- a/src/applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php @@ -70,6 +70,7 @@ $conn = $repository->establishConnection('w'); $sql = array(); + $dir_info = array(); foreach ($coverage as $path => $coverage_info) { $sql[] = qsprintf( $conn, @@ -78,6 +79,34 @@ $path_map[$path], $commit->getID(), $coverage_info); + + // add the coverage info for this file to the aggregate coverage info for + // all of its parent directories + $normal_path = DiffusionPathIDQuery::normalizePath($path); + $path_parts = explode('/', $normal_path); + array_pop($path_parts); + $dir = ''; + foreach ($path_parts as $part) { + $dir .= '/'.$part; + if (isset($dir_info[$dir])) { + $dir_info[$dir] .= $coverage_info; + } else { + $dir_info[$dir] = $coverage_info; + } + } + } + + $dir_map = id(new DiffusionPathIDQuery(array_keys($dir_info))) + ->loadPathIDs(); + + foreach ($dir_info as $dir => $coverage_info) { + $sql[] = qsprintf( + $conn, + '(%d, %d, %d, %s)', + $branch->getID(), + $dir_map[$dir], + $commit->getID(), + $coverage_info); } $table_name = 'repository_coverage'; diff --git a/src/applications/diffusion/controller/DiffusionLastModifiedController.php b/src/applications/diffusion/controller/DiffusionLastModifiedController.php --- a/src/applications/diffusion/controller/DiffusionLastModifiedController.php +++ b/src/applications/diffusion/controller/DiffusionLastModifiedController.php @@ -71,8 +71,44 @@ } $lint = $lint_query->execute(); + + $paths = array_keys($commits); + $path_map = id(new DiffusionPathIDQuery($paths))->loadPathIDs(); + + $commit_id = $drequest->loadCommit()->getID(); + $coverage_rows = queryfx_all( + id(new PhabricatorRepository())->establishConnection('r'), + 'SELECT * FROM %T + WHERE branchID = %d + AND pathID IN (%Ld) + AND commitID = %d', + PhabricatorRepository::TABLE_COVERAGE, + $branch->getID(), + $path_map, + $commit_id); + $coverage = array(); + $path_map = array_flip($path_map); + foreach ($coverage_rows as $coverage_row) { + $covered_path = $path_map[$coverage_row['pathID']]; + $report = $coverage_row['coverage']; + $cov = substr_count($report, 'C'); + $uncov = substr_count($report, 'U'); + $total = $cov + $uncov; + $is_dir = substr($covered_path, strlen($covered_path) - 1, 1) === '/'; + $coverage[$covered_path] = array( + 'cov' => $cov, + 'uncov' => $uncov, + 'total' => $total, + 'is_dir' => $is_dir, + ); + } + $largest = max(ipull($coverage, 'total')); + foreach ($coverage as $covered_path => $coverage_info) { + $coverage[$covered_path]['largest'] = $largest; + } } else { $lint = array(); + $coverage = array(); } $output = array(); @@ -84,7 +120,8 @@ $prequest, $handles, $commit, - idx($lint, $path)); + idx($lint, $path), + idx($coverage, $path)); } return id(new AphrontAjaxResponse())->setContent($output); @@ -94,7 +131,8 @@ DiffusionRequest $drequest, array $handles, PhabricatorRepositoryCommit $commit = null, - $lint = null) { + $lint = null, + $coverage = null) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $viewer = $this->getRequest()->getUser(); @@ -156,6 +194,14 @@ number_format($lint)); } + if ($coverage !== null) { + $return['coverage'] = id(new DiffusionCoverageBarView()) + ->setIsDirectory($coverage['is_dir']) + ->setCovered($coverage['cov']) + ->setUncovered($coverage['uncov']) + ->setLargestNeighbor($coverage['largest']); + } + // The client treats these results as markup, so make sure they have been // escaped correctly. foreach ($return as $key => $value) { diff --git a/src/applications/diffusion/view/DiffusionBrowseTableView.php b/src/applications/diffusion/view/DiffusionBrowseTableView.php --- a/src/applications/diffusion/view/DiffusionBrowseTableView.php +++ b/src/applications/diffusion/view/DiffusionBrowseTableView.php @@ -74,6 +74,7 @@ $dict = array( 'lint' => celerity_generate_unique_node_id(), + 'coverage' => celerity_generate_unique_node_id(), 'commit' => celerity_generate_unique_node_id(), 'date' => celerity_generate_unique_node_id(), 'author' => celerity_generate_unique_node_id(), @@ -89,6 +90,7 @@ $history_link, $browse_link, idx($dict, 'lint'), + $dict['coverage'], $dict['commit'], $dict['author'], $dict['details'], @@ -112,6 +114,7 @@ $branch = $this->getDiffusionRequest()->loadBranch(); $show_lint = ($branch && $branch->getLintCommit()); $lint = $request->getLint(); + $show_coverage = $this->getDiffusionRequest()->loadCoverage(); $view = new AphrontTableView($rows); $view->setHeaders( @@ -119,6 +122,7 @@ null, pht('Path'), ($lint ? $lint : pht('Lint')), + pht('Coverage'), pht('Modified'), pht('Author/Committer'), pht('Details'), @@ -131,6 +135,7 @@ '', '', '', + '', 'wide', '', )); @@ -139,6 +144,7 @@ true, true, $show_lint, + $show_coverage, true, true, true, @@ -150,6 +156,7 @@ true, true, false, + false, true, false, true, diff --git a/src/applications/diffusion/view/DiffusionCoverageBarView.php b/src/applications/diffusion/view/DiffusionCoverageBarView.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/view/DiffusionCoverageBarView.php @@ -0,0 +1,83 @@ +isDirectory = $is_dir; + return $this; + } + + public function setCovered($covered) { + $this->covered = $covered; + return $this; + } + + public function setUncovered($uncovered) { + $this->uncovered = $uncovered; + return $this; + } + + public function setLargestNeighbor($largest_neighbor) { + $this->largestNeighbor = $largest_neighbor; + return $this; + } + + protected function getTotal() { + return $this->covered + $this->uncovered; + } + + protected function getRatio() { + return min($this->covered, $this->getTotal()) / $this->getTotal(); + } + + public function render() { + $this->initBehavior('phabricator-tooltips'); + require_celerity_resource('aphront-tooltip-css'); + require_celerity_resource('aphront-bars'); + $scaled_total = log($this->getTotal() + 1, self::LOG_SCALE); + $scaled_maximum = log($this->largestNeighbor + 1, self::LOG_SCALE); + $bar_ratio = $scaled_total / $scaled_maximum; + $max_width = self::MAX_WIDTH; + $bar_width = $max_width * $bar_ratio; + $progress_width = $bar_width * $this->getRatio(); + $tooltip = hsprintf( + '%s/%s (%s%%)', + number_format($this->covered), + number_format($this->getTotal()), + sprintf('% 3d', 100 * $this->getRatio())); + + $color = $this->getColor(); + + return javelin_tag( + 'div', + array( + 'class' => "aphront-bar coverage color-{$color}", + 'sigil' => 'has-tooltip', + 'meta' => array('tip' => $tooltip), + ), + array( + phutil_tag( + 'div', + array( + 'style' => "width: {$bar_width}px;", + ), + phutil_tag( + 'div', + array('style' => "width: {$progress_width}px;"), + '')), + )); + } + +} diff --git a/webroot/rsrc/css/aphront/aphront-bars.css b/webroot/rsrc/css/aphront/aphront-bars.css --- a/webroot/rsrc/css/aphront/aphront-bars.css +++ b/webroot/rsrc/css/aphront/aphront-bars.css @@ -71,6 +71,28 @@ color: {$greytext}; } +/** + * Coverage bars + */ + +div.aphront-bar.coverage { + border: 0px; + width: 200px; + height: 20px; +} + +div.aphront-bar.coverage div { + border: 1px solid; + border-top-color: {$lightgreyborder}; + border-right-color: {$lightgreyborder}; + border-bottom-color: {$lightgreyborder}; + border-left-color: {$greyborder}; + background: white; +} + +div.aphront-bar.coverage div div { + height: 16px; +} /** * Common color classes @@ -83,6 +105,10 @@ color: {$blue}; } +div.aphront-bar.coverage.color-default div div { + background: {$blue}; +} + div.aphront-bar.progress.color-warning div div { background: {$yellow}; } @@ -91,6 +117,10 @@ color: {$yellow}; } +div.aphront-bar.coverage.color-warning div div { + background: {$yellow}; +} + div.aphront-bar.progress.color-danger div div { background: {$red}; } @@ -98,3 +128,7 @@ div.aphront-bar.glyph.color-danger div.glyphs div.fg { color: {$red}; } + +div.aphront-bar.coverage.color-danger div div { + background: {$red}; +}