2026-06-19 17:23:25 +03:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace Nechaev\Reports\Features\Reports;
|
|
|
|
|
|
|
|
|
|
use Nechaev\Reports\Core\App;
|
|
|
|
|
use Nechaev\Reports\Framework\Database\Database;
|
|
|
|
|
|
|
|
|
|
final class ReportsApi {
|
|
|
|
|
private Database $db;
|
|
|
|
|
|
|
|
|
|
public function __construct(?Database $db = null) {
|
|
|
|
|
$this->db = $db ?? App::getDb();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return array<int, array{id: int, name: string}>
|
|
|
|
|
*/
|
|
|
|
|
public function getTeams(): array {
|
|
|
|
|
$rows = $this->db->getRowset(
|
|
|
|
|
'SELECT id, TRIM(team_name) AS name FROM teams ORDER BY TRIM(team_name)'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return array_map(
|
|
|
|
|
static fn (array $row): array => [
|
|
|
|
|
'id' => (int) $row['id'],
|
|
|
|
|
'name' => $row['name'],
|
|
|
|
|
],
|
|
|
|
|
$rows
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return array<int, array{
|
|
|
|
|
* id: int,
|
|
|
|
|
* jira_sprint_id: int,
|
|
|
|
|
* sprint_name: string,
|
|
|
|
|
* sprint_activate_datetime: string,
|
|
|
|
|
* sprint_complete_datetime: string,
|
2026-06-19 17:29:46 +03:00
|
|
|
* sprint_completion_percentage: float|null,
|
|
|
|
|
* lead_time_max: int|null,
|
|
|
|
|
* lead_time_min: int|null,
|
|
|
|
|
* lead_time_p95: float|null,
|
|
|
|
|
* lead_time_p80: float|null,
|
|
|
|
|
* lead_time_p50: float|null
|
2026-06-19 17:23:25 +03:00
|
|
|
* }>
|
|
|
|
|
*/
|
|
|
|
|
public function getSprintsByTeamId(int $teamId): array {
|
|
|
|
|
$rows = $this->db->getRowset(
|
2026-06-19 17:29:46 +03:00
|
|
|
'SELECT s.id,
|
|
|
|
|
s.jira_sprint_id,
|
|
|
|
|
s.sprint_name,
|
|
|
|
|
s.sprint_activate_datetime,
|
|
|
|
|
s.sprint_complete_datetime,
|
|
|
|
|
s.sprint_completion_percentage,
|
|
|
|
|
MAX(t.lead_time_days) AS lead_time_max,
|
|
|
|
|
MIN(t.lead_time_days) AS lead_time_min,
|
|
|
|
|
percentile_cont(0.95) WITHIN GROUP (ORDER BY t.lead_time_days) AS lead_time_p95,
|
|
|
|
|
percentile_cont(0.80) WITHIN GROUP (ORDER BY t.lead_time_days) AS lead_time_p80,
|
|
|
|
|
percentile_cont(0.50) WITHIN GROUP (ORDER BY t.lead_time_days) AS lead_time_p50
|
|
|
|
|
FROM sprints s
|
|
|
|
|
LEFT JOIN tasks t ON t.sprint_id = s.id AND t.lead_time_days IS NOT NULL
|
|
|
|
|
WHERE s.team_id = :team_id
|
|
|
|
|
GROUP BY s.id,
|
|
|
|
|
s.jira_sprint_id,
|
|
|
|
|
s.sprint_name,
|
|
|
|
|
s.sprint_activate_datetime,
|
|
|
|
|
s.sprint_complete_datetime,
|
|
|
|
|
s.sprint_completion_percentage
|
|
|
|
|
ORDER BY s.sprint_activate_datetime ASC',
|
2026-06-19 17:23:25 +03:00
|
|
|
['team_id' => $teamId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return array_map(
|
|
|
|
|
static fn (array $row): array => [
|
|
|
|
|
'id' => (int) $row['id'],
|
|
|
|
|
'jira_sprint_id' => (int) $row['jira_sprint_id'],
|
|
|
|
|
'sprint_name' => $row['sprint_name'],
|
|
|
|
|
'sprint_activate_datetime' => $row['sprint_activate_datetime'],
|
|
|
|
|
'sprint_complete_datetime' => $row['sprint_complete_datetime'],
|
|
|
|
|
'sprint_completion_percentage' => $row['sprint_completion_percentage'] !== null
|
|
|
|
|
? (float) $row['sprint_completion_percentage']
|
|
|
|
|
: null,
|
2026-06-19 17:29:46 +03:00
|
|
|
'lead_time_max' => $row['lead_time_max'] !== null ? (int) $row['lead_time_max'] : null,
|
|
|
|
|
'lead_time_min' => $row['lead_time_min'] !== null ? (int) $row['lead_time_min'] : null,
|
|
|
|
|
'lead_time_p95' => $row['lead_time_p95'] !== null ? (float) $row['lead_time_p95'] : null,
|
|
|
|
|
'lead_time_p80' => $row['lead_time_p80'] !== null ? (float) $row['lead_time_p80'] : null,
|
|
|
|
|
'lead_time_p50' => $row['lead_time_p50'] !== null ? (float) $row['lead_time_p50'] : null,
|
2026-06-19 17:23:25 +03:00
|
|
|
],
|
|
|
|
|
$rows
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|