diff --git a/src/Maintained/Application/View/project.twig b/src/Maintained/Application/View/project.twig index ef003bf..40223cc 100644 --- a/src/Maintained/Application/View/project.twig +++ b/src/Maintained/Application/View/project.twig @@ -16,7 +16,11 @@

- Resolution time: {{ resolutionTime.formatLong() }} + Resolution time: + {{ resolutionTime.formatLong() }} +

+

+ The resolution time is the median time an issue stays open.

diff --git a/src/Maintained/Statistics/StatisticsComputer.php b/src/Maintained/Statistics/StatisticsComputer.php index 0973e50..49b96f1 100644 --- a/src/Maintained/Statistics/StatisticsComputer.php +++ b/src/Maintained/Statistics/StatisticsComputer.php @@ -2,7 +2,9 @@ namespace Maintained\Statistics; -use Maintained\Diagnostic; +use Github\Client; +use Maintained\Issue; +use Maintained\TimeInterval; /** * Computes statistics. @@ -11,13 +13,109 @@ use Maintained\Diagnostic; */ class StatisticsComputer implements StatisticsProvider { + /** + * @var Client + */ + private $github; + + public function __construct(Client $github) + { + $this->github = $github; + } + public function getStatistics($user, $repository) { - $diagnostic = new Diagnostic($user . '/' . $repository); + $issues = $this->fetchIssues($user, $repository); + $collaborators = $this->fetchCollaborators($user, $repository); + $issues = $this->excludeIssuesCreatedByCollaborators($issues, $collaborators); $statistics = new Statistics(); - $statistics->resolutionTime = $diagnostic->computeMedian(); + $statistics->resolutionTime = $this->computeResolutionTime($issues); return $statistics; } + + /** + * @param Issue[] $issues + * @return TimeInterval + */ + private function computeResolutionTime(array $issues) + { + $durations = array_map(function (Issue $issue) { + return $issue->getOpenedFor()->toSeconds(); + }, $issues); + + return new TimeInterval($this->median($durations)); + } + + /** + * @param Issue[] $issues + * @param string[] $collaborators + * @return Issue[] + */ + private function excludeIssuesCreatedByCollaborators(array $issues, array $collaborators) + { + return array_filter($issues, function (Issue $issue) use ($collaborators) { + return !in_array($issue->getAuthor(), $collaborators); + }); + } + + /** + * @param float[] $array + * @return float + */ + private function median(array $array) { + $count = count($array); + + if ($count == 0) { + return 0; + } + + sort($array, SORT_NUMERIC); + + $middleIndex = (int) floor($count / 2); + + // Handle the even case by averaging the middle 2 items + if ($count % 2 == 0) { + return ($array[$middleIndex] + $array[$middleIndex - 1]) / 2; + } + + return $array[$middleIndex]; + } + + /** + * @param string $user + * @param string $repository + * @return Issue[] + */ + private function fetchIssues($user, $repository) + { + /** @var \GitHub\Api\Issue $issueApi */ + $issueApi = $this->github->api('issue'); + $issues = $issueApi->all($user, $repository, ['state' => 'all']); + + $issues = array_map(function (array $data) { + return Issue::fromArray($data); + }, $issues); + + return $issues; + } + + /** + * @param string $user + * @param string $repository + * @return string[] + */ + private function fetchCollaborators($user, $repository) + { + /** @var \GitHub\Api\Repo $repositoryApi */ + $repositoryApi = $this->github->api('repo'); + $collaborators = $repositoryApi->collaborators()->all($user, $repository); + + $collaborators = array_map(function ($user) { + return $user['login']; + }, $collaborators); + + return $collaborators; + } } diff --git a/src/Maintained/TimeInterval.php b/src/Maintained/TimeInterval.php index 4ab9e4e..972bfb6 100644 --- a/src/Maintained/TimeInterval.php +++ b/src/Maintained/TimeInterval.php @@ -46,7 +46,7 @@ class TimeInterval case $this->seconds < self::TO_MINUTE: return sprintf('%d s', $this->seconds); case $this->seconds < self::TO_HOUR: - return sprintf('%d m', $this->toMinutes()); + return sprintf('%d min', $this->toMinutes()); case $this->seconds < self::TO_DAY: return sprintf('%d h', $this->toHours()); default: