Skip to content

Commit c3de5ea

Browse files
authored
Merge pull request #49 from tinybirdco/pagerduty02
add pipe
2 parents 30da877 + 7802527 commit c3de5ea

11 files changed

+545
-43
lines changed

apps/web/src/components/tools/pagerduty/dashboard.tsx

Lines changed: 132 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import { useState, useEffect, useCallback } from 'react'
55
import { pipe } from '@/lib/tinybird'
66
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
77
import { formatDuration, formatNumber, formatPercentage } from '@/lib/utils'
8-
import Overview from '@/components/overview-chart'
8+
import Overview from '@/components/tools/pagerduty/overview-chart'
99
import BarList from '@/components/bar-list'
1010
import { DateRangePicker, DateRange } from '@/components/ui/date-range-picker'
1111
import { startOfDay, endOfDay, format } from 'date-fns'
1212
import MetricCard from '@/components/metric-card'
13-
import IncidentCategoriesTable from "@/components/incident-categories-table"
13+
import IncidentCategoriesTable from "@/components/tools/pagerduty/incident-categories-table"
1414
import { Filter } from './filters'
15+
import InterruptionsChart from "@/components/tools/pagerduty/interruptions-chart"
16+
import ResponseEffortChart from "@/components/tools/pagerduty/response-effort-chart"
17+
import SleepInterruptions from "@/components/tools/pagerduty/sleep-interruptions"
1518

1619
interface IncidentType {
1720
title: string
@@ -22,6 +25,20 @@ interface IncidentType {
2225
example_title: string
2326
}
2427

28+
interface Responder {
29+
html_url: string;
30+
id: string;
31+
self: string;
32+
summary: string;
33+
type: string;
34+
}
35+
36+
interface InterruptionData {
37+
day: string
38+
interruptions: number
39+
day_type: 'business' | 'off'
40+
}
41+
2542
export default function PagerDutyDashboard() {
2643
const [token] = useQueryState('token')
2744
const [dateRange, setDateRange] = useState<DateRange>({
@@ -52,9 +69,10 @@ export default function PagerDutyDashboard() {
5269
high_urgency_rate: number
5370
}>>([])
5471
const [responders, setResponders] = useState<Array<{
55-
responder: string
56-
responses: number
57-
avg_response_time: number
72+
responder: Responder[];
73+
responses: number;
74+
acknowledgements: number;
75+
avg_response_time: number;
5876
}>>([])
5977
const [statusDistribution, setStatusDistribution] = useState<Array<{
6078
event_type: string
@@ -65,6 +83,28 @@ export default function PagerDutyDashboard() {
6583
const pageSize = 10
6684
const [incidentTypes, setIncidentTypes] = useState<IncidentType[]>([])
6785
const [selectedService, setSelectedService] = useState<string>()
86+
const [interruptions, setInterruptions] = useState<InterruptionData[]>([])
87+
const [responseEffort, setResponseEffort] = useState<Array<{
88+
responder: Array<{
89+
html_url: string
90+
id: string
91+
self: string
92+
summary: string
93+
type: string
94+
}>
95+
hours: number
96+
}>>([])
97+
const [sleepInterruptions, setSleepInterruptions] = useState<Array<{
98+
responder: Array<{
99+
html_url: string
100+
id: string
101+
self: string
102+
summary: string
103+
type: string
104+
}>
105+
interruptions: number
106+
avg_response_time: number
107+
}>>([])
68108

69109
const fetchIncidentTypes = useCallback(async (newPage: number) => {
70110
if (!token) return
@@ -116,15 +156,21 @@ export default function PagerDutyDashboard() {
116156
statusDistributionData,
117157
resolutionTimesData,
118158
serviceDistributionData,
119-
incidentTypesData
159+
incidentTypesData,
160+
interruptionsData,
161+
responseEffortData,
162+
sleepInterruptionsData
120163
] = await Promise.all([
121164
pipe(token, 'pagerduty_incident_metrics', baseParams),
122165
pipe(token, 'pagerduty_incidents_over_time', baseParams),
123166
pipe(token, 'pagerduty_responders', baseParams),
124167
pipe(token, 'pagerduty_status_distribution', baseParams),
125168
pipe(token, 'pagerduty_resolution_times', baseParams),
126169
pipe(token, 'pagerduty_service_distribution', baseParams),
127-
pipe(token, 'pagerduty_incident_types', baseParams)
170+
pipe(token, 'pagerduty_incident_types', baseParams),
171+
pipe(token, 'pagerduty_interruptions', baseParams),
172+
pipe(token, 'pagerduty_response_effort', baseParams),
173+
pipe(token, 'pagerduty_sleep_interruptions', baseParams)
128174
])
129175

130176
setMetrics((incidentMetrics?.data?.[0] || {
@@ -143,9 +189,10 @@ export default function PagerDutyDashboard() {
143189
escalated: number
144190
}>)
145191
setResponders(respondersData.data as Array<{
146-
responder: string
147-
responses: number
148-
avg_response_time: number
192+
responder: Responder[];
193+
responses: number;
194+
acknowledgements: number;
195+
avg_response_time: number;
149196
}>)
150197
setStatusDistribution(statusDistributionData.data as Array<{
151198
event_type: string
@@ -166,6 +213,28 @@ export default function PagerDutyDashboard() {
166213
high_urgency_rate: number
167214
}>)
168215
setIncidentTypes(incidentTypesData.data as IncidentType[])
216+
setInterruptions(interruptionsData.data as InterruptionData[])
217+
setResponseEffort(responseEffortData.data as Array<{
218+
responder: Array<{
219+
html_url: string
220+
id: string
221+
self: string
222+
summary: string
223+
type: string
224+
}>
225+
hours: number
226+
}>)
227+
setSleepInterruptions(sleepInterruptionsData.data as Array<{
228+
responder: Array<{
229+
html_url: string
230+
id: string
231+
self: string
232+
summary: string
233+
type: string
234+
}>
235+
interruptions: number
236+
avg_response_time: number
237+
}>)
169238

170239
} catch (error) {
171240
console.error('Failed to fetch metrics:', error)
@@ -270,9 +339,9 @@ export default function PagerDutyDashboard() {
270339
data={incidentsOverTime}
271340
categories={['triggered', 'resolved', 'escalated']}
272341
colors={{
273-
'triggered': '#dc2626', // Red-600
274-
'resolved': '#16a34a', // Green-600
275-
'escalated': '#d97706' // Amber-600
342+
'triggered': 'hsl(var(--primary))', // Red-600
343+
'resolved': '#93c5fd', // Green-600
344+
'escalated': '#6b7280' // Amber-600
276345
}}
277346
index="hour"
278347
valueKey="count"
@@ -282,31 +351,65 @@ export default function PagerDutyDashboard() {
282351
</Card>
283352

284353
{/* Response Analysis */}
285-
<div className="grid gap-4 grid-cols-3">
354+
<div className="grid gap-4 grid-cols-2">
286355
<Card>
287356
<CardHeader>
288-
<CardTitle>Response Team Activity</CardTitle>
357+
<CardTitle>Oncall Team Activity</CardTitle>
289358
</CardHeader>
290359
<CardContent className="max-h-[400px] overflow-auto">
291360
<BarList
292-
data={(responders || []).map((item) => ({
293-
name: item.responder,
294-
value: item.responses,
295-
extra: `${formatDuration(item.avg_response_time)} avg response time`,
296-
}))}
361+
data={responders
362+
.reduce((acc, item) => {
363+
if (item.responder.length > 1) return acc;
364+
365+
const name = item.responder[0]?.summary || 'Unknown'
366+
const existing = acc.find(x => x.name === name)
367+
if (existing) {
368+
existing.value += item.responses
369+
existing.acknowledgements += item.acknowledgements
370+
existing.avgTime = (existing.avgTime * existing.count + item.avg_response_time) / (existing.count + 1)
371+
existing.count++
372+
} else {
373+
acc.push({
374+
name,
375+
value: item.responses,
376+
acknowledgements: item.acknowledgements,
377+
avgTime: item.avg_response_time,
378+
count: 1
379+
})
380+
}
381+
return acc
382+
}, [] as Array<{
383+
name: string
384+
value: number
385+
acknowledgements: number
386+
avgTime: number
387+
count: number
388+
}>)
389+
.map(item => ({
390+
name: item.name,
391+
value: item.value,
392+
extra: `${item.acknowledgements} acks • ${formatDuration(item.avgTime)} avg response time`
393+
}))
394+
}
297395
/>
298396
</CardContent>
299397
</Card>
300-
<div className="col-span-2">
301-
<IncidentCategoriesTable
302-
data={incidentTypes || []}
303-
page={page}
304-
onPageChange={handlePageChange}
305-
pageSize={pageSize}
306-
isLoading={isTableLoading}
307-
/>
308-
</div>
398+
<ResponseEffortChart data={responseEffort} />
309399
</div>
400+
401+
<div className="grid gap-4 grid-cols-2">
402+
<InterruptionsChart data={interruptions} />
403+
<SleepInterruptions data={sleepInterruptions} />
404+
</div>
405+
406+
<IncidentCategoriesTable
407+
data={incidentTypes || []}
408+
page={page}
409+
onPageChange={handlePageChange}
410+
pageSize={pageSize}
411+
isLoading={isTableLoading}
412+
/>
310413
</div>
311414
)
312415
}

apps/web/src/components/incident-categories-table.tsx renamed to apps/web/src/components/tools/pagerduty/incident-categories-table.tsx

File renamed without changes.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
2+
import { Bar, BarChart, CartesianGrid, ResponsiveContainer, XAxis, YAxis, Tooltip, Legend } from "recharts"
3+
import { format } from "date-fns"
4+
5+
interface InterruptionsChartProps {
6+
data: Array<{
7+
day: string
8+
interruptions: number
9+
day_type: 'business' | 'off'
10+
}>
11+
}
12+
13+
export default function InterruptionsChart({ data }: InterruptionsChartProps) {
14+
return (
15+
<Card>
16+
<CardHeader>
17+
<CardTitle className="flex items-center gap-2">
18+
Interruptions
19+
</CardTitle>
20+
</CardHeader>
21+
<CardContent className="h-[300px]">
22+
<ResponsiveContainer width="100%" height="100%">
23+
<BarChart data={data}>
24+
<CartesianGrid
25+
horizontal={true}
26+
vertical={false}
27+
className="stroke-muted"
28+
/>
29+
<XAxis
30+
dataKey="day"
31+
tickFormatter={(time) => format(new Date(time), 'MMM d')}
32+
tickLine={false}
33+
axisLine={false}
34+
tickMargin={8}
35+
/>
36+
<YAxis
37+
tickLine={false}
38+
axisLine={false}
39+
tickMargin={8}
40+
width={70}
41+
/>
42+
<Tooltip
43+
labelFormatter={(value) => new Date(value).toLocaleString()}
44+
formatter={(value, name: string) => [value, name.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')]}
45+
/>
46+
<Legend />
47+
<Bar dataKey="business_hours" stackId="a" fill="hsl(var(--primary))" name="Business Hours" />
48+
<Bar dataKey="sleep_hours" stackId="a" fill="#93c5fd" name="Sleep Hours" />
49+
<Bar dataKey="off_hours" stackId="a" fill="#6b7280" name="Off Hours" />
50+
</BarChart>
51+
</ResponsiveContainer>
52+
</CardContent>
53+
</Card>
54+
)
55+
}

apps/web/src/components/overview-chart.tsx renamed to apps/web/src/components/tools/pagerduty/overview-chart.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"use client"
22

3-
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Legend } from "recharts"
3+
import { BarChart, Bar, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer, Legend } from "recharts"
4+
import { format } from "date-fns"
5+
46

57
interface OverviewProps {
68
data: Array<{
@@ -20,12 +22,19 @@ export default function Overview({ data, categories, colors }: OverviewProps) {
2022
return (
2123
<ResponsiveContainer width="100%" height={350}>
2224
<BarChart data={data}>
25+
<CartesianGrid
26+
horizontal={true}
27+
vertical={false}
28+
className="stroke-muted"
29+
/>
2330
<XAxis
2431
dataKey="hour"
2532
stroke="var(--muted-foreground)"
2633
fontSize={12}
2734
tickLine={false}
2835
axisLine={false}
36+
tickFormatter={(time) => format(new Date(time), 'MMM d, HH:00')}
37+
2938
/>
3039
<YAxis
3140
stroke="var(--muted-foreground)"

0 commit comments

Comments
 (0)