Skip to content

Commit cb1c261

Browse files
committed
Final pass grafana-dashboard-hygiene-pruning-actionable-metrics article
1 parent 67c6386 commit cb1c261

8 files changed

Lines changed: 218 additions & 108 deletions

File tree

‎_TODO.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,14 @@ src/content/articles/internal-developer-portal-platform-self-service-actions/pdf
322322
]}
323323
/>
324324

325+
### Stretching the badge out to the same size and centering text:
326+
327+
<List
328+
variant="badge-list"
329+
classes={{
330+
titleClass: "block w-full text-center",
331+
}}
332+
325333
### Spacing out the boxes:
326334

327335
<List
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
export type Props = {
3+
color: string
4+
classes?: string
5+
size: number
6+
accessible: boolean
7+
focusable: boolean
8+
isListMarker?: boolean
9+
id?: string
10+
}
11+
12+
const { color, classes, size, accessible, focusable, isListMarker, id } = Astro.props
13+
---
14+
15+
<svg
16+
id={id}
17+
xmlns="http://www.w3.org/2000/svg"
18+
class:list={["shrink-0", `w-${size} h-${size}`, `text-${color}`, classes, isListMarker ? "mt-0.5" : ""]}
19+
fill="currentColor"
20+
stroke="none"
21+
viewBox="0 0 24 24"
22+
aria-hidden={accessible ? "false" : "true"}
23+
focusable={focusable ? "true" : "false"}
24+
>
25+
{accessible && <title>User Avatar Icon</title>}
26+
<path
27+
d="M 23.547421,10.930935 13.068776,0.45285245 c -0.603095,-0.60346993 -1.581847,-0.60346993 -2.185692,0 L 8.707142,2.6287942 11.467429,5.3890808 c 0.641438,-0.2167504 1.377002,-0.071344 1.888316,0.4399696 0.513751,0.5145948 0.658126,1.25644 0.436126,1.8999413 l 2.660161,2.6602553 c 0.643595,-0.221719 1.38619,-0.07828 1.900223,0.436594 0.718407,0.718127 0.718407,1.882035 0,2.600631 -0.718596,0.718688 -1.882504,0.718688 -2.601474,0 -0.540376,-0.540939 -0.673877,-1.334534 -0.400314,-2.000254 L 12.86965,8.9454003 12.869369,15.473882 a 1.8581286,1.8581286 0 0 1 0.486376,0.347813 c 0.718407,0.718126 0.718407,1.882129 0,2.601286 -0.718595,0.718314 -1.883066,0.718314 -2.60063,0 -0.718408,-0.719157 -0.718408,-1.883066 0,-2.601192 a 1.8425661,1.8425661 0 0 1 0.60197,-0.401345 V 8.8311188 A 1.816691,1.816691 0 0 1 10.755115,8.4297743 C 10.210801,7.8862107 10.079926,7.0875529 10.358833,6.4192078 L 7.6378274,3.6979213 0.45281338,10.882467 c -0.60375117,0.604032 -0.60375117,1.582878 0,2.186441 L 10.931646,23.547179 c 0.603283,0.603563 1.581847,0.603563 2.18588,0 L 23.547327,13.117565 c 0.603564,-0.603751 0.603564,-1.58316 0,-2.18663"
28+
></path>
29+
</svg>

‎src/components/Icon/icons/git.svg‎

Lines changed: 44 additions & 0 deletions
Loading

‎src/components/Icon/icons/turtle.svg‎

Lines changed: 0 additions & 54 deletions
This file was deleted.

‎src/components/List/__tests__/index.spec.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,34 @@ describe('List (Astro)', () => {
132132
expect(lead?.getAttribute('style')).toBeNull()
133133
})
134134
})
135+
136+
test('applies interactive card styling for the card-grid-list variant', async () => {
137+
const List = (await import('@components/List/index.astro')).default
138+
139+
const renderedHtml = await container.renderToString(List, {
140+
props: {
141+
variant: 'card-grid-list',
142+
items: [
143+
{
144+
lead: 'Owner clarity matters.',
145+
text: 'Each card should clearly communicate its primary action.',
146+
},
147+
],
148+
},
149+
})
150+
151+
await withJsdomEnvironment(async ({ window }) => {
152+
window.document.body.innerHTML = renderedHtml
153+
154+
const card = window.document.querySelector('ul li')
155+
const lead = window.document.querySelector('ul li em')
156+
157+
expect(card).toBeTruthy()
158+
expect(card?.className).toContain('hover:-translate-y-1')
159+
expect(card?.className).toContain('hover:bg-page-base')
160+
expect(card?.className).toContain('hover:shadow-md')
161+
expect(card?.className).toContain('focus-within:border-primary')
162+
expect(lead?.textContent).toContain('Owner clarity matters.')
163+
})
164+
})
135165
})

‎src/components/List/layouts/CardGridList.astro‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ export type Props = {
1717
const { items, classes }: Props = Astro.props
1818
1919
const ulClass = ["grid grid-cols-1 md:grid-cols-2 gap-4", classes?.ul]
20-
const liClass = ["bg-page-offset border border-trim rounded-lg p-5 hover:border-primary transition-colors", classes?.li]
20+
const liClass = [
21+
"bg-page-offset border border-trim rounded-lg p-5 transition-all duration-200 ease-out hover:-translate-y-1 hover:border-primary hover:bg-page-base hover:shadow-md focus-within:-translate-y-1 focus-within:border-primary focus-within:bg-page-base focus-within:shadow-md",
22+
classes?.li,
23+
]
2124
const emClass = ["block text-page-inverse font-semibold not-italic mb-2", classes?.em]
2225
---
2326

‎src/content/articles/grafana-dashboard-hygiene-pruning-actionable-metrics/index.mdx‎

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Every dashboard you create is a promise to maintain it. Dashboards without owner
2828
Dashboard accumulation isn't anyone's fault — it's a natural consequence of how teams work. Every dashboard enters your inventory through a legitimate path, and almost none ever leave.
2929

3030
<List
31-
variant="check-icons-list"
31+
variant="zebra-list"
3232
items={[
3333
{
3434
lead: "New services get dashboards",
@@ -53,8 +53,11 @@ Dashboard accumulation isn't anyone's fault — it's a natural consequence of ho
5353
]}
5454
/>
5555

56+
Each of these patterns has a different expected lifecycle, but they all share the same outcome: permanent residency in your Grafana instance. Understanding the intended lifespan of each type is key to building cleanup policies that make sense — you wouldn't apply the same retention rules to a service overview dashboard and an incident debug dashboard.
57+
5658
<Table
57-
variant="vertical-column-delineation-table"
59+
variant="criteria-highlight-table"
60+
fullWidth={false}
5861
content={{
5962
figure: "Dashboard types and their lifecycle patterns.",
6063
thead: {
@@ -89,18 +92,21 @@ Dashboard accumulation isn't anyone's fault — it's a natural consequence of ho
8992

9093
The asymmetry is stark: creating a dashboard takes minutes, deciding to delete one feels like it requires a committee.
9194

92-
Beyond the clutter, dashboards aren't free. Every panel issues queries to your backend — Prometheus, InfluxDB, your SQL warehouse. A dashboard with 20 panels viewed by 50 people triggers 1,000 queries every refresh cycle. Grafana-managed alerts linked to dashboard panels run continuously, checking for firing conditions even when nobody's watching. That DEBUG dashboard from last quarter? Its alert rules are still querying your metrics database every minute.
93-
9495
<Newsletter />
9596

97+
Beyond the clutter, dashboards aren't free. Every panel issues queries to your backend — Prometheus, InfluxDB, your SQL warehouse. A dashboard with 20 panels viewed by 50 people triggers 1,000 queries every refresh cycle. Grafana-managed alerts linked to dashboard panels run continuously, checking for firing conditions even when nobody's watching. That DEBUG dashboard from last quarter? Its alert rules are still querying your metrics database every minute.
98+
9699
## Measuring Dashboard Health
97100

98101
You can't make evidence-based decisions about what to keep or delete without usage data. The first step in any hygiene program is instrumenting your Grafana instance to track who's looking at what.
99102

100103
For each dashboard, you need four categories of metrics:
101104

102105
<List
103-
variant="check-icons-list"
106+
variant="side-by-side-list"
107+
classes={{
108+
wrapper: "sm:mr-12 mb-6",
109+
}}
104110
items={[
105111
{
106112
lead: "View metrics",
@@ -130,41 +136,51 @@ Raw metrics are useful but overwhelming when you have hundreds of dashboards. A
130136
A reasonable weighting:
131137

132138
<List
133-
variant="check-icons-list"
139+
variant="badge-list"
140+
classes={{
141+
titleClass: "block w-full text-center",
142+
}}
134143
items={[
135144
{
136-
lead: "Usage score (35%)",
137-
text: "Based on views and unique viewers in the last 30 days. Zero views scores zero; 100+ views with 10+ unique viewers scores full marks.",
145+
title: "Usage score (35%)",
146+
lead: "Based on views and unique viewers in the last 30 days.",
147+
text: "Zero views scores zero; 100+ views with 10+ unique viewers scores full marks.",
138148
},
139149
{
140-
lead: "Freshness score (25%)",
141-
text: "Based on time since last edit. Edited in the last 30 days scores 100; over a year without edits scores 0.",
150+
title: "Freshness score (25%)",
151+
lead: "Based on time since last edit.",
152+
text: "Edited in the last 30 days scores 100; over a year without edits scores 0.",
142153
},
143154
{
144-
lead: "Ownership score (25%)",
145-
text: "Has an assigned owner? Has a team tag? Has any tags at all? Each adds points.",
155+
title: "Ownership score (25%)",
156+
lead: "Has an assigned owner?",
157+
text: "Has a team tag? Has any tags at all? Each adds points.",
146158
},
147159
{
148-
lead: "Quality score (15%)",
149-
text: "Penalize broken panels heavily (they actively mislead), excessive complexity (30+ panels), and suspiciously simple dashboards (fewer than 3 panels might be incomplete).",
160+
title: "Quality score (15%)",
161+
lead: "Penalize broken panels heavily (they actively mislead),",
162+
text: "excessive complexity (30+ panels), and suspiciously simple dashboards (fewer than 3 panels might be incomplete).",
150163
},
151164
]}
152165
/>
153166

154167
Beyond the numeric score, flag dashboards with specific problems: "no-recent-views," "stale," "no-owner," "broken-panels," "unmaintained," "likely-clone" (title contains "copy"), or "incident-artifact" (title contains "debug").
155168

156-
These flags help prioritize review. A dashboard scoring 40 with "broken-panels" needs immediate attention — it's actively misleading anyone who looks at it. A dashboard scoring 40 with "no-recent-views" can wait for the next cleanup cycle.
157-
158169
<Callout type="success">
159170
Automate usage tracking from day one. Without data, hygiene decisions become political debates instead of evidence-based choices. "Nobody uses this dashboard" is much more persuasive when you can prove it with numbers.
160171
</Callout>
161172

173+
These flags help prioritize review. A dashboard scoring 40 with "broken-panels" needs immediate attention — it's actively misleading anyone who looks at it. A dashboard scoring 40 with "no-recent-views" can wait for the next cleanup cycle.
174+
162175
## Making Cleanup Stick
163176

164177
Process alone won't fix dashboard rot. People hoard dashboards because deletion feels risky and there's no reward for tidiness. You need to change the incentives — and have responses ready for the inevitable objections.
165178

166179
<List
167-
variant="check-icons-list"
180+
variant="timeline-list"
181+
classes={{
182+
wrapper: "sm:ml-6 sm:mr-12 mb-6",
183+
}}
168184
items={[
169185
{
170186
lead: "\"I might need it someday.\"",
@@ -199,6 +215,8 @@ The biggest obstacle to dashboard hygiene isn't technical — it's cultural. Peo
199215

200216
Dashboard hygiene isn't a one-time project. It's ongoing maintenance, like any other operational practice.
201217

218+
If you do nothing else: implement usage tracking and run one cleanup campaign. Those two actions will give you the data to make informed decisions and the momentum to build a sustainable hygiene practice.
219+
202220
<Download
203221
resource="grafana-dashboard-hygiene-pruning-actionable-metrics"
204222
title="Download the Dashboard Hygiene Guide"
@@ -219,6 +237,4 @@ Dashboard hygiene isn't a one-time project. It's ongoing maintenance, like any o
219237
]}
220238
/>
221239

222-
If you do nothing else: implement usage tracking and run one cleanup campaign. Those two actions will give you the data to make informed decisions and the momentum to build a sustainable hygiene practice.
223-
224240
The goal isn't the smallest possible dashboard count. It's ensuring that every dashboard that exists is accurate, maintained, and serves a clear purpose. When an engineer searches for "payments" during an incident, they should find 3 relevant dashboards, not 30 stale ones.

0 commit comments

Comments
 (0)