diff --git a/frontend/src/components/SprintTable.vue b/frontend/src/components/SprintTable.vue
index 9cdbfa0..e534902 100644
--- a/frontend/src/components/SprintTable.vue
+++ b/frontend/src/components/SprintTable.vue
@@ -33,6 +33,22 @@ function formatCompletion(value: number | null): string {
return `${value.toFixed(1)}%`
}
+function formatLeadTimeInt(value: number | null): string {
+ if (value === null) {
+ return '—'
+ }
+
+ return String(value)
+}
+
+function formatLeadTimePercentile(value: number | null): string {
+ if (value === null) {
+ return '—'
+ }
+
+ return value.toFixed(1)
+}
+
const sortedSprints = computed(() =>
[...sprints.value].sort(
(left, right) =>
@@ -76,8 +92,20 @@ watch(
- | Спринт |
- Выполнение |
+ Спринт |
+ Выполнение |
+ Lead Time |
+
+
+ | Границы |
+ Персентиль |
+
+
+ | max |
+ min |
+ 95 |
+ 80 |
+ 50 |
@@ -100,6 +128,11 @@ watch(
{{ formatCompletion(sprint.sprint_completion_percentage) }}
+ {{ 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) }} |
@@ -131,16 +164,44 @@ watch(
.sprint-table__table td {
padding: 14px 18px;
text-align: left;
- border-bottom: 1px solid rgba(255, 255, 255, 0.08);
+ border: 1px solid rgba(255, 255, 255, 0.08);
background: transparent;
}
.sprint-table__table th {
- color: rgba(241, 244, 247, 0.5);
- font-size: 0.8rem;
- font-weight: 600;
+ color: #f1f4f7;
+ font-size: 0.8125rem;
+ font-weight: 700;
+ text-align: center;
+ vertical-align: middle;
+}
+
+.sprint-table__th-sprint,
+.sprint-table__th-completion {
+ text-align: left;
text-transform: uppercase;
letter-spacing: 0.04em;
+ color: rgba(241, 244, 247, 0.5);
+ font-size: 0.8rem;
+}
+
+.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-weight: 700;
+}
+
+.sprint-table__metric {
+ text-align: center;
+ font-variant-numeric: tabular-nums;
+ color: #f1f4f7;
}
.sprint-table__table tbody tr:last-child td {
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index 001ea6a..bb8683f 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -10,4 +10,9 @@ export interface Sprint {
sprint_activate_datetime: string
sprint_complete_datetime: string
sprint_completion_percentage: number | null
+ lead_time_max: number | null
+ lead_time_min: number | null
+ lead_time_p95: number | null
+ lead_time_p80: number | null
+ lead_time_p50: number | null
}
diff --git a/src/Features/Reports/ReportsApi.php b/src/Features/Reports/ReportsApi.php
index 8f61c8a..9875bb8 100644
--- a/src/Features/Reports/ReportsApi.php
+++ b/src/Features/Reports/ReportsApi.php
@@ -36,20 +36,37 @@ final class ReportsApi {
* sprint_name: string,
* sprint_activate_datetime: string,
* sprint_complete_datetime: string,
- * sprint_completion_percentage: float|null
+ * 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
* }>
*/
public function getSprintsByTeamId(int $teamId): array {
$rows = $this->db->getRowset(
- 'SELECT id,
- jira_sprint_id,
- sprint_name,
- sprint_activate_datetime,
- sprint_complete_datetime,
- sprint_completion_percentage
- FROM sprints
- WHERE team_id = :team_id
- ORDER BY sprint_activate_datetime ASC',
+ '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',
['team_id' => $teamId]
);
@@ -63,6 +80,11 @@ final class ReportsApi {
'sprint_completion_percentage' => $row['sprint_completion_percentage'] !== null
? (float) $row['sprint_completion_percentage']
: null,
+ '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,
],
$rows
);