Skip to content

Commit 3d13ba9

Browse files
committed
feat: Add total duration statistics
1 parent 076457b commit 3d13ba9

8 files changed

Lines changed: 87 additions & 26 deletions

File tree

docs/docsv1/docs.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,12 @@ const docTemplate = `{
13671367
},
13681368
"date": {
13691369
"type": "string"
1370+
},
1371+
"total_duration": {
1372+
"type": "integer"
1373+
},
1374+
"total_duration_human": {
1375+
"type": "string"
13701376
}
13711377
}
13721378
},

docs/docsv1/swagger.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,12 @@
13601360
},
13611361
"date": {
13621362
"type": "string"
1363+
},
1364+
"total_duration": {
1365+
"type": "integer"
1366+
},
1367+
"total_duration_human": {
1368+
"type": "string"
13631369
}
13641370
}
13651371
},

docs/docsv1/swagger.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,10 @@ definitions:
361361
type: integer
362362
date:
363363
type: string
364+
total_duration:
365+
type: integer
366+
total_duration_human:
367+
type: string
364368
type: object
365369
web.healthCheckResponse:
366370
properties:

frontend/src/pages/dashboard/incidents-stats.tsx

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const description = "An interactive area chart";
2626
const ChartType = {
2727
IncidentsCount: "incidentsCount",
2828
IncidentsAvgDuration: "incidentsAvgDuration",
29+
IncidentsTotalDuration: "incidentsTotalDuration",
2930
} as const;
3031

3132
type ChartType = (typeof ChartType)[keyof typeof ChartType];
@@ -35,6 +36,8 @@ interface IncidentStatsItem {
3536
count: number;
3637
avg_duration: number; // in seconds
3738
avg_duration_human: string; // for human-readable format
39+
total_duration: number; // in seconds
40+
total_duration_human: string; // for human-readable format
3841
}
3942

4043
export function ChartIncidentsStats() {
@@ -52,11 +55,15 @@ export function ChartIncidentsStats() {
5255
label:
5356
chartType === ChartType.IncidentsCount
5457
? "Incidents Count"
55-
: "Avg Duration",
58+
: chartType === ChartType.IncidentsAvgDuration
59+
? "Avg Duration"
60+
: "Total Duration",
5661
color:
5762
chartType === ChartType.IncidentsCount
58-
? "var(--chart-1)"
59-
: "var(--chart-2)",
63+
? "var(--chart-6)"
64+
: chartType === ChartType.IncidentsAvgDuration
65+
? "var(--chart-2)"
66+
: "var(--chart-3)",
6067
},
6168
}),
6269
[chartType]
@@ -92,6 +99,8 @@ export function ChartIncidentsStats() {
9299
count: item.count || 0,
93100
avg_duration: item.avg_duration || 0,
94101
avg_duration_human: item.avg_duration_human || "",
102+
total_duration: item.total_duration || 0,
103+
total_duration_human: item.total_duration_human || "",
95104
}));
96105
setIncidentsData(formattedData);
97106
})
@@ -109,13 +118,16 @@ export function ChartIncidentsStats() {
109118
const dataKey =
110119
chartType === ChartType.IncidentsCount
111120
? item.count
112-
: Math.round(item.avg_duration);
121+
: chartType === ChartType.IncidentsAvgDuration
122+
? Math.round(item.avg_duration)
123+
: Math.round(item.total_duration);
113124

114125
return {
115126
date: item.date,
116127
incidents: dataKey,
117128
count: item.count,
118129
avg_duration_human: item.avg_duration_human,
130+
total_duration_human: item.total_duration_human,
119131
formattedDate: date.toLocaleDateString("en-US", {
120132
month: "short",
121133
day: "numeric",
@@ -159,6 +171,12 @@ export function ChartIncidentsStats() {
159171
>
160172
Average duration
161173
</SelectItem>
174+
<SelectItem
175+
value={ChartType.IncidentsTotalDuration}
176+
className="rounded-lg"
177+
>
178+
Total duration
179+
</SelectItem>
162180
</SelectContent>
163181
</Select>
164182

@@ -194,17 +212,21 @@ export function ChartIncidentsStats() {
194212
offset="5%"
195213
stopColor={
196214
chartType === ChartType.IncidentsCount
197-
? "var(--chart-1)"
198-
: "var(--chart-2)"
215+
? "var(--chart-6)"
216+
: chartType === ChartType.IncidentsAvgDuration
217+
? "var(--chart-2)"
218+
: "var(--chart-3)"
199219
}
200220
stopOpacity={0.8}
201221
/>
202222
<stop
203223
offset="95%"
204224
stopColor={
205225
chartType === ChartType.IncidentsCount
206-
? "var(--chart-1)"
207-
: "var(--chart-2)"
226+
? "var(--chart-6)"
227+
: chartType === ChartType.IncidentsAvgDuration
228+
? "var(--chart-2)"
229+
: "var(--chart-3)"
208230
}
209231
stopOpacity={0.1}
210232
/>
@@ -248,12 +270,16 @@ export function ChartIncidentsStats() {
248270
<span className="text-[0.70rem] uppercase text-muted-foreground">
249271
{chartType === ChartType.IncidentsCount
250272
? "Count"
251-
: "Duration"}
273+
: chartType === ChartType.IncidentsAvgDuration
274+
? "Avg Duration"
275+
: "Total Duration"}
252276
</span>
253277
<span className="font-bold">
254-
{chartType === ChartType.IncidentsAvgDuration
255-
? data.avg_duration_human || "0s"
256-
: data.count}
278+
{chartType === ChartType.IncidentsCount
279+
? data.count
280+
: chartType === ChartType.IncidentsAvgDuration
281+
? data.avg_duration_human || "0s"
282+
: data.total_duration_human || "0s"}
257283
</span>
258284
</div>
259285
</div>
@@ -269,8 +295,10 @@ export function ChartIncidentsStats() {
269295
fill="url(#fillIncidents)"
270296
stroke={
271297
chartType === ChartType.IncidentsCount
272-
? "var(--chart-1)"
273-
: "var(--chart-2)"
298+
? "var(--chart-6)"
299+
: chartType === ChartType.IncidentsAvgDuration
300+
? "var(--chart-2)"
301+
: "var(--chart-3)"
274302
}
275303
/>
276304
</AreaChart>

frontend/src/shared/styles/index.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@
7979
--chart-3: oklch(0.398 0.07 227.392);
8080
--chart-4: oklch(0.828 0.189 84.429);
8181
--chart-5: oklch(0.769 0.188 70.08);
82+
--chart-6: oklch(0.488 0.243 264.376);
83+
8284
--sidebar: oklch(0.985 0 0);
8385
--sidebar-foreground: oklch(0.141 0.005 285.823);
8486
--sidebar-primary: oklch(0.21 0.006 285.885);
@@ -114,6 +116,8 @@
114116
--chart-3: oklch(0.769 0.188 70.08);
115117
--chart-4: oklch(0.627 0.265 303.9);
116118
--chart-5: oklch(0.645 0.246 16.439);
119+
--chart-6: oklch(0.398 0.07 227.392);
120+
117121
--sidebar: oklch(0.205 0 0);
118122
--sidebar-foreground: oklch(0.985 0 0);
119123
--sidebar-primary: oklch(0.488 0.243 264.376);

frontend/src/shared/types/model/webGetIncidentsStatsItem.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ export interface WebGetIncidentsStatsItem {
1111
avg_duration_human?: string;
1212
count?: number;
1313
date?: string;
14+
total_duration?: number;
15+
total_duration_human?: string;
1416
}

internal/storage/incidents.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -332,9 +332,10 @@ func (o *ORMStorage) DeleteIncident(ctx context.Context, incidentID string) erro
332332
}
333333

334334
type GetIncidentsStatsByDateRangeItem struct {
335-
Date time.Time `json:"date"`
336-
Count int64 `json:"count"`
337-
AvgDuration time.Duration `json:"avg_duration"`
335+
Date time.Time `json:"date"`
336+
Count int64 `json:"count"`
337+
AvgDuration time.Duration `json:"avg_duration"`
338+
TotalDuration time.Duration `json:"total_duration"`
338339
}
339340

340341
type GetIncidentsStatsByDateRangeData []GetIncidentsStatsByDateRangeItem
@@ -355,6 +356,7 @@ func (o *ORMStorage) GetIncidentsStatsByDateRange(ctx context.Context, startTime
355356
sb.Select(
356357
"COUNT(*) as count",
357358
"AVG(CASE WHEN resolved = true THEN duration_ns ELSE (strftime('%s', 'now') - strftime('%s', start_time)) * 1000000000 END) as avg_duration",
359+
"SUM(CASE WHEN resolved = true THEN duration_ns ELSE (strftime('%s', 'now') - strftime('%s', start_time)) * 1000000000 END) as total_duration",
358360
)
359361
sb.From("incidents")
360362
sb.Where(sb.GreaterEqualThan("start_time", dayStart))
@@ -365,8 +367,9 @@ func (o *ORMStorage) GetIncidentsStatsByDateRange(ctx context.Context, startTime
365367

366368
var count int64
367369
var avgDurationNS *float64
370+
var totalDurationNS *float64
368371

369-
if err := row.Scan(&count, &avgDurationNS); err != nil {
372+
if err := row.Scan(&count, &avgDurationNS, &totalDurationNS); err != nil {
370373
return nil, fmt.Errorf("failed to scan incident stats for date %s: %w", d.Format("2006-01-02"), err)
371374
}
372375

@@ -380,6 +383,10 @@ func (o *ORMStorage) GetIncidentsStatsByDateRange(ctx context.Context, startTime
380383
item.AvgDuration = time.Duration(int64(*avgDurationNS))
381384
}
382385

386+
if totalDurationNS != nil {
387+
item.TotalDuration = time.Duration(int64(*totalDurationNS))
388+
}
389+
383390
result = append(result, item)
384391
}
385392

internal/web/handlers.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -706,10 +706,12 @@ func (s *Server) handleAPIDeleteIncident(c *fiber.Ctx) error {
706706
}
707707

708708
type getIncidentsStatsItem struct {
709-
Date time.Time `json:"date"`
710-
Count int64 `json:"count"`
711-
AvgDuration uint32 `json:"avg_duration"`
712-
AvgDurationHuman string `json:"avg_duration_human"`
709+
Date time.Time `json:"date"`
710+
Count int64 `json:"count"`
711+
AvgDuration uint32 `json:"avg_duration"`
712+
AvgDurationHuman string `json:"avg_duration_human"`
713+
TotalDuration uint32 `json:"total_duration"`
714+
TotalDurationHuman string `json:"total_duration_human"`
713715
}
714716

715717
type getIncidentsStatsData []getIncidentsStatsItem
@@ -751,10 +753,12 @@ func (s *Server) handleAPIGetIncidentsStats(c *fiber.Ctx) error {
751753
response := make(getIncidentsStatsData, 0, len(stats))
752754
for _, item := range stats {
753755
response = append(response, getIncidentsStatsItem{
754-
Date: item.Date,
755-
Count: item.Count,
756-
AvgDuration: uint32(item.AvgDuration.Seconds()),
757-
AvgDurationHuman: item.AvgDuration.Round(time.Second).String(),
756+
Date: item.Date,
757+
Count: item.Count,
758+
AvgDuration: uint32(item.AvgDuration.Seconds()),
759+
AvgDurationHuman: item.AvgDuration.Round(time.Second).String(),
760+
TotalDuration: uint32(item.TotalDuration.Seconds()),
761+
TotalDurationHuman: item.TotalDuration.Round(time.Second).String(),
758762
})
759763
}
760764

0 commit comments

Comments
 (0)