diff --git a/frontend/src/components/SprintTable.vue b/frontend/src/components/SprintTable.vue index e534902..5b9cada 100644 --- a/frontend/src/components/SprintTable.vue +++ b/frontend/src/components/SprintTable.vue @@ -33,7 +33,7 @@ function formatCompletion(value: number | null): string { return `${value.toFixed(1)}%` } -function formatLeadTimeInt(value: number | null): string { +function formatMetricInt(value: number | null): string { if (value === null) { return '—' } @@ -41,7 +41,7 @@ function formatLeadTimeInt(value: number | null): string { return String(value) } -function formatLeadTimePercentile(value: number | null): string { +function formatMetricPercentile(value: number | null): string { if (value === null) { return '—' } @@ -92,20 +92,22 @@ watch( - - + + - - - - + - - - + + + + + + + + @@ -128,11 +130,16 @@ watch( {{ formatCompletion(sprint.sprint_completion_percentage) }} - - - - - + + + + + + + + + +
СпринтВыполнениеСпринтВыполнение Lead Time
ГраницыПерсентильКоличество заходов на тестирование
max min95805095pp80pp50ppmaxmin95pp80pp50pp
{{ formatLeadTimeInt(sprint.lead_time_max) }}{{ formatLeadTimeInt(sprint.lead_time_min) }}{{ formatLeadTimePercentile(sprint.lead_time_p95) }}{{ formatLeadTimePercentile(sprint.lead_time_p80) }}{{ formatLeadTimePercentile(sprint.lead_time_p50) }}{{ formatMetricInt(sprint.lead_time_max) }}{{ formatMetricInt(sprint.lead_time_min) }}{{ formatMetricPercentile(sprint.lead_time_p95) }}{{ formatMetricPercentile(sprint.lead_time_p80) }}{{ formatMetricPercentile(sprint.lead_time_p50) }}{{ formatMetricInt(sprint.test_count_max) }}{{ formatMetricInt(sprint.test_count_min) }}{{ formatMetricPercentile(sprint.test_count_p95) }}{{ formatMetricPercentile(sprint.test_count_p80) }}{{ formatMetricPercentile(sprint.test_count_p50) }}
@@ -152,7 +159,7 @@ watch( } .sprint-table__wrapper { - overflow: hidden; + overflow-x: auto; } .sprint-table__table { @@ -162,7 +169,7 @@ watch( .sprint-table__table th, .sprint-table__table td { - padding: 14px 18px; + padding: 10px 14px; text-align: left; border: 1px solid rgba(255, 255, 255, 0.08); background: transparent; @@ -170,10 +177,11 @@ watch( .sprint-table__table th { color: #f1f4f7; - font-size: 0.8125rem; + font-size: 0.75rem; font-weight: 700; text-align: center; vertical-align: middle; + padding: 8px 10px; } .sprint-table__th-sprint, @@ -186,15 +194,11 @@ watch( } .sprint-table__th-group { - font-size: 0.875rem; -} - -.sprint-table__th-subgroup { font-size: 0.8125rem; } .sprint-table__th-metric { - font-size: 0.8125rem; + font-size: 0.75rem; font-weight: 700; } diff --git a/frontend/src/types.ts b/frontend/src/types.ts index bb8683f..b6404f1 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -15,4 +15,9 @@ export interface Sprint { lead_time_p95: number | null lead_time_p80: number | null lead_time_p50: number | null + test_count_max: number | null + test_count_min: number | null + test_count_p95: number | null + test_count_p80: number | null + test_count_p50: number | null } diff --git a/src/Features/Reports/ReportsApi.php b/src/Features/Reports/ReportsApi.php index 9875bb8..b49c7e1 100644 --- a/src/Features/Reports/ReportsApi.php +++ b/src/Features/Reports/ReportsApi.php @@ -41,7 +41,12 @@ final class ReportsApi { * lead_time_min: int|null, * lead_time_p95: float|null, * lead_time_p80: float|null, - * lead_time_p50: float|null + * lead_time_p50: float|null, + * test_count_max: int|null, + * test_count_min: int|null, + * test_count_p95: float|null, + * test_count_p80: float|null, + * test_count_p50: float|null * }> */ public function getSprintsByTeamId(int $teamId): array { @@ -52,13 +57,25 @@ final class ReportsApi { 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 + MAX(t.lead_time_days) FILTER (WHERE t.lead_time_days IS NOT NULL) AS lead_time_max, + MIN(t.lead_time_days) FILTER (WHERE t.lead_time_days IS NOT NULL) AS lead_time_min, + percentile_cont(0.95) WITHIN GROUP (ORDER BY t.lead_time_days) + FILTER (WHERE t.lead_time_days IS NOT NULL) AS lead_time_p95, + percentile_cont(0.80) WITHIN GROUP (ORDER BY t.lead_time_days) + FILTER (WHERE t.lead_time_days IS NOT NULL) AS lead_time_p80, + percentile_cont(0.50) WITHIN GROUP (ORDER BY t.lead_time_days) + FILTER (WHERE t.lead_time_days IS NOT NULL) AS lead_time_p50, + MAX(t.test_count) FILTER (WHERE e.employee_role IN (\'backend\', \'frontend\')) AS test_count_max, + MIN(t.test_count) FILTER (WHERE e.employee_role IN (\'backend\', \'frontend\')) AS test_count_min, + percentile_cont(0.95) WITHIN GROUP (ORDER BY t.test_count) + FILTER (WHERE e.employee_role IN (\'backend\', \'frontend\')) AS test_count_p95, + percentile_cont(0.80) WITHIN GROUP (ORDER BY t.test_count) + FILTER (WHERE e.employee_role IN (\'backend\', \'frontend\')) AS test_count_p80, + percentile_cont(0.50) WITHIN GROUP (ORDER BY t.test_count) + FILTER (WHERE e.employee_role IN (\'backend\', \'frontend\')) AS test_count_p50 FROM sprints s - LEFT JOIN tasks t ON t.sprint_id = s.id AND t.lead_time_days IS NOT NULL + LEFT JOIN tasks t ON t.sprint_id = s.id + LEFT JOIN employees e ON e.id = t.employee_id WHERE s.team_id = :team_id GROUP BY s.id, s.jira_sprint_id, @@ -85,6 +102,11 @@ final class ReportsApi { '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, + 'test_count_max' => $row['test_count_max'] !== null ? (int) $row['test_count_max'] : null, + 'test_count_min' => $row['test_count_min'] !== null ? (int) $row['test_count_min'] : null, + 'test_count_p95' => $row['test_count_p95'] !== null ? (float) $row['test_count_p95'] : null, + 'test_count_p80' => $row['test_count_p80'] !== null ? (float) $row['test_count_p80'] : null, + 'test_count_p50' => $row['test_count_p50'] !== null ? (float) $row['test_count_p50'] : null, ], $rows );