From ad3b399b96a88fd552f62a7ff34535b24481f57c Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Tue, 9 Sep 2014 22:33:31 +0700 Subject: [PATCH] Added the "opened issues" badge --- app/config/routes.php | 2 +- .../Controller/BadgeController.php | 63 +++++++++--- .../Application/Controller/HomeController.php | 20 +++- .../Controller/ProjectController.php | 1 + .../Application/Twig/TwigExtension.php | 4 +- src/Maintained/Application/View/home.twig | 99 +++++-------------- src/Maintained/Application/View/project.twig | 19 +++- src/Maintained/Issue.php | 8 ++ src/Maintained/Statistics/Statistics.php | 9 ++ .../Statistics/StatisticsComputer.php | 23 +++++ 10 files changed, 155 insertions(+), 93 deletions(-) diff --git a/app/config/routes.php b/app/config/routes.php index 6a58a01..189d66f 100644 --- a/app/config/routes.php +++ b/app/config/routes.php @@ -14,7 +14,7 @@ return [ 'controller' => ProjectController::class, ], 'badge' => [ - 'pattern' => '/badge/{user}/{repository}.svg', + 'pattern' => '/badge/{badge}/{user}/{repository}.svg', 'controller' => BadgeController::class, ], ]; diff --git a/src/Maintained/Application/Controller/BadgeController.php b/src/Maintained/Application/Controller/BadgeController.php index 18f96fd..ea246b3 100644 --- a/src/Maintained/Application/Controller/BadgeController.php +++ b/src/Maintained/Application/Controller/BadgeController.php @@ -4,7 +4,9 @@ namespace Maintained\Application\Controller; use DI\Annotation\Inject; use Github\Exception\ApiLimitExceedException; +use Maintained\Statistics\Statistics; use Maintained\Statistics\StatisticsProvider; +use PUGX\Poser\Image; use PUGX\Poser\Poser; /** @@ -12,6 +14,9 @@ use PUGX\Poser\Poser; */ class BadgeController { + const BADGE_RESOLUTION = 'resolution'; + const BADGE_OPEN_RATIO = 'opened'; + const COLOR_OK = '18bc9c'; const COLOR_WARNING = 'CC9237'; const COLOR_DANGER = '9C3838'; @@ -28,22 +33,20 @@ class BadgeController */ private $poser; - public function __invoke($user, $repository) + public function __invoke($badge, $user, $repository) { try { $statistics = $this->statisticsProvider->getStatistics($user, $repository); - $days = $statistics->resolutionTime->toDays(); - - if ($days < 2) { - $color = self::COLOR_OK; - } elseif ($days < 8) { - $color = self::COLOR_WARNING; - } else { - $color = self::COLOR_DANGER; + switch ($badge) { + case self::BADGE_OPEN_RATIO: + $badge = $this->createOpenRatioBadge($statistics); + break; + case self::BADGE_RESOLUTION: + default: + $badge = $this->createResolutionBadge($statistics); + break; } - - $badge = $this->poser->generate('resolution', $statistics->resolutionTime, $color, 'svg'); } catch (ApiLimitExceedException $e) { $badge = $this->poser->generate('github-api', 'limit', self::COLOR_DANGER, 'svg'); } @@ -51,4 +54,42 @@ class BadgeController header('Content-type: image/svg+xml'); echo $badge; } + + /** + * @param Statistics $statistics + * @return Image + */ + private function createResolutionBadge(Statistics $statistics) + { + $days = $statistics->resolutionTime->toDays(); + + if ($days < 2) { + $color = self::COLOR_OK; + } elseif ($days < 8) { + $color = self::COLOR_WARNING; + } else { + $color = self::COLOR_DANGER; + } + + return $this->poser->generate('resolution', $statistics->resolutionTime->formatShort(), $color, 'svg'); + } + + /** + * @param Statistics $statistics + * @return Image + */ + private function createOpenRatioBadge(Statistics $statistics) + { + $ratio = $statistics->openIssuesRatio; + + if ($ratio < 0.1) { + $color = self::COLOR_OK; + } elseif ($ratio < 0.2) { + $color = self::COLOR_WARNING; + } else { + $color = self::COLOR_DANGER; + } + + return $this->poser->generate('opened issues', round($ratio * 100) . '%', $color, 'svg'); + } } diff --git a/src/Maintained/Application/Controller/HomeController.php b/src/Maintained/Application/Controller/HomeController.php index 0640314..167c8fd 100644 --- a/src/Maintained/Application/Controller/HomeController.php +++ b/src/Maintained/Application/Controller/HomeController.php @@ -2,13 +2,29 @@ namespace Maintained\Application\Controller; +use Twig_Environment; + /** * @author Matthieu Napoli */ class HomeController { - public function __invoke(\Twig_Environment $twig) + public function __invoke(Twig_Environment $twig) { - echo $twig->render('home.twig'); + $demoProjects = [ + 'symfony/symfony' => 'Symfony', + 'Ocramius/ProxyManager' => 'ProxyManager', + 'Atlantic18/DoctrineExtensions' => 'DoctrineExtensions', + 'schmittjoh/serializer' => 'JMS Serializer', + 'PHPOffice/PHPExcel' => 'PHPExcel', + 'composer/composer' => 'Composer', + 'Behat/Behat' => 'Behat', + 'kriswallsmith/assetic' => 'Assetic', + 'sebastianbergmann/phpunit' => 'PHPUnit', + ]; + + echo $twig->render('home.twig', [ + 'projects' => $demoProjects, + ]); } } diff --git a/src/Maintained/Application/Controller/ProjectController.php b/src/Maintained/Application/Controller/ProjectController.php index 6995276..bd2d208 100644 --- a/src/Maintained/Application/Controller/ProjectController.php +++ b/src/Maintained/Application/Controller/ProjectController.php @@ -28,6 +28,7 @@ class ProjectController echo $this->twig->render('project.twig', [ 'repository' => $user . '/' . $repository, 'resolutionTime' => $statistics->resolutionTime, + 'openedIssues' => round($statistics->openIssuesRatio * 100), ]); } } diff --git a/src/Maintained/Application/Twig/TwigExtension.php b/src/Maintained/Application/Twig/TwigExtension.php index 5a3065f..d193200 100644 --- a/src/Maintained/Application/Twig/TwigExtension.php +++ b/src/Maintained/Application/Twig/TwigExtension.php @@ -24,10 +24,10 @@ class TwigExtension extends Twig_Extension ]; } - public function generateBadge($repository) + public function generateBadge($repository, $type = 'resolution') { return << + HTML; } } diff --git a/src/Maintained/Application/View/home.twig b/src/Maintained/Application/View/home.twig index b6188ee..da45d3c 100644 --- a/src/Maintained/Application/View/home.twig +++ b/src/Maintained/Application/View/home.twig @@ -66,13 +66,19 @@

Resolution time

- Median time needed to close an issue. + Median time needed to close an issue or pull request. +

+

+ Issues or PR opened by the collaborators are ignored.

Closed percentage

- Percentage of closed issues. + Percentage of closed issues and pull requests. +

+

+ Issues or PR opened by the collaborators are ignored.

@@ -84,80 +90,21 @@
-
-

- - Symfony - -

- {{ badge('symfony/symfony') }} -
-
-

- - ProxyManager - -

- {{ badge('Ocramius/ProxyManager') }} -
-
-

- - DoctrineExtensions - -

- {{ badge('Atlantic18/DoctrineExtensions') }} -
- -
-

- - JMS serializer - -

- {{ badge('schmittjoh/serializer') }} -
-
-

- - PHPExcel - -

- {{ badge('PHPOffice/PHPExcel') }} -
-
-

- - Composer - -

- {{ badge('composer/composer') }} -
- -
-

- - Behat - -

- {{ badge('Behat/Behat') }} -
-
-

- - Assetic - -

- {{ badge('kriswallsmith/assetic') }} -
-
-

- - PHPUnit - -

- {{ badge('sebastianbergmann/phpunit') }} -
+ {% for repository, name in projects %} +
+

+ + {{ name }} + +

+

+ {{ badge(repository, 'resolution') }} +

+

+ {{ badge(repository, 'opened') }} +

+
+ {% endfor %}
diff --git a/src/Maintained/Application/View/project.twig b/src/Maintained/Application/View/project.twig index e10d098..6ddc9c8 100644 --- a/src/Maintained/Application/View/project.twig +++ b/src/Maintained/Application/View/project.twig @@ -23,7 +23,24 @@ {{ badge(repository) }}

- The resolution time is the median time an issue stays open. + The resolution time is the median time an issue or pull request stays open. +

+

+ Issues or PR opened by the collaborators are ignored. +

+ +

+ Open issues: + {{ openedIssues }}% +

+

+ {{ badge(repository, 'opened') }} +

+

+ The percentage of open issues and pull requests. +

+

+ Issues or PR opened by the collaborators are ignored.

diff --git a/src/Maintained/Issue.php b/src/Maintained/Issue.php index 8431fa9..9bdcfbe 100644 --- a/src/Maintained/Issue.php +++ b/src/Maintained/Issue.php @@ -78,4 +78,12 @@ class Issue { return $this->openedFor; } + + /** + * @return bool + */ + public function isOpen() + { + return $this->open; + } } diff --git a/src/Maintained/Statistics/Statistics.php b/src/Maintained/Statistics/Statistics.php index 5446cc6..3bec3b4 100644 --- a/src/Maintained/Statistics/Statistics.php +++ b/src/Maintained/Statistics/Statistics.php @@ -12,7 +12,16 @@ use Maintained\TimeInterval; class Statistics { /** + * Average time for closing an issue. + * * @var TimeInterval */ public $resolutionTime; + + /** + * Ratio of open issues. + * + * @var float + */ + public $openIssuesRatio; } diff --git a/src/Maintained/Statistics/StatisticsComputer.php b/src/Maintained/Statistics/StatisticsComputer.php index 49b96f1..5e15464 100644 --- a/src/Maintained/Statistics/StatisticsComputer.php +++ b/src/Maintained/Statistics/StatisticsComputer.php @@ -27,10 +27,12 @@ class StatisticsComputer implements StatisticsProvider { $issues = $this->fetchIssues($user, $repository); $collaborators = $this->fetchCollaborators($user, $repository); + $issues = $this->excludeIssuesCreatedByCollaborators($issues, $collaborators); $statistics = new Statistics(); $statistics->resolutionTime = $this->computeResolutionTime($issues); + $statistics->openIssuesRatio = $this->computeOpenIssueRatio($issues); return $statistics; } @@ -48,6 +50,27 @@ class StatisticsComputer implements StatisticsProvider return new TimeInterval($this->median($durations)); } + /** + * @param Issue[] $issues + * @return float + */ + private function computeOpenIssueRatio(array $issues) + { + if (empty($issues)) { + return 0; + } + + $openIssues = 0; + + foreach ($issues as $issue) { + if ($issue->isOpen()) { + $openIssues++; + } + } + + return $openIssues / count($issues); + } + /** * @param Issue[] $issues * @param string[] $collaborators