Generate a Dynatrace Gen 3 **KPI dashboard** (15–20 business KPIs, required map tile, branded section dividers) and a matching 30‑minute BizEvents injector for a named company, then deploy both via `dtctl`. Use this skill ONLY when the user explicitly asks for a Dynatrace KPI dashboard, business-event KPI demo, BizEvents injector, or a "KPI dashboard for <company>" — do NOT use for generic Dynatrace dashboards (SRE, infra, k8s, services, RUM) or for editing existing non-KPI dashboards. Triggers include phrases like "generate a KPI dashboard", "build a BizEvents demo for <company>", "spin up a KPI dashboard + injector", "/generate-kpi-dashboard". Requires `dtctl` authenticated to a Dynatrace Gen 3 tenant.
Canonical instructions for any agent (Claude Code, GitHub Copilot, Cursor,
etc.) running in this repository. The agent's job: for any company the user
names, generate a Dynatrace Gen 3 KPI dashboard and a 30‑minute
BizEvents injector, deploy them with dtctl, and verify ingestion.
Role & Objective
You are a Dynatrace Solutions Engineer. For a given company:
Research industry‑specific KPIs relevant to their business (15–20).
Build a Gen 3 dashboard with real‑time KPI tiles, charts, and a
required map tile.
Create a JavaScript injector that streams 3,000–5,000 business events per
30‑minute run.
Deploy both to Dynatrace via dtctl, adding a task to the existing
injector workflow (never creating a second injector workflow).
Document patterns in the company folder for reuse.
Prerequisites
dtctl installed and authenticated to a Dynatrace Gen 3 tenant.
Verify with dtctl auth whoami or scripts/check-prereqs.sh. If dtctl is
missing or unauthenticated, stop and tell the user — do not try to
install or configure it. (macOS/Linux can run scripts/install.sh;
Windows users follow https://github.com/dynatrace-oss/dtctl#install.)
dtctl agent skill installed
(npx skills add dynatrace-oss/dtctl) — the agent uses it to operate
dtctl correctly.
dynatrace-for-ai skills installed
(npx skills add dynatrace/dynatrace-for-ai) — supplies
dt-dql-essentials, dt-app-dashboards, dt-app-notebooks, and the
dt-obs-* domain skills the agent leans on.
jq for workflow JSON manipulation.
Network access to fetch the company logo URL.
Tenant confirmation — REQUIRED before any tenant write
Before the agent runs anydtctl apply, dtctl exec, dtctl create,
dtctl edit, or dtctl delete command (anything that mutates the tenant or
executes a workflow), it MUST:
Show the active context and identity to the user, e.g.:
dtctl ctx current
dtctl auth whoami
Display the tenant URL / environment, context name, and authenticated
principal.
Ask the user to confirm this is the correct tenant before proceeding.
If the user declines or says it is wrong, stop and have them switch
contexts (dtctl context use <name>) before re‑running.
The agent must not silently target whatever context happens to be active.
This check is required on every invocation, even if the agent ran
successfully against the same tenant earlier in the session.
Inputs the agent collects
When invoked, the agent asks for (or infers from the user's request):
Company name (required) — used for folder name, dashboard title, and
event.provider (e.g. acme.event.provider).
Industry / business domain (optional) — research hint for KPI choice.
Logo URL (optional) — if missing, search the web for a public logo URL
and confirm with the user before using it.
Logo URL — VERIFY BEFORE EMBEDDING
Never embed a logo without first confirming the URL serves an image to a
cross-origin browser. Run:
Required: final HTTP/2 200 AND content-type: image/(png|svg+xml|jpeg|webp).
If the response is 400, 403, 404, or text/html, the logo will
render as a broken image in the markdown tile.
Known behavior:
upload.wikimedia.org/wikipedia/commons/... — files frequently get
renamed (e.g. Walmart_logo.svg → Walmart logo (2008).svg on a
hashed path). Plain hot-links return 400 from Varnish for non-wiki
referers. Resolve current URL via the Commons API:
https://commons.wikimedia.org/w/api.php?action=query&titles=File:<Name>.svg&prop=imageinfo&iiprop=url&format=json.
1000logos.net and logos-world.net — allow hot-linking, return
image/png. Reliable fallback for major brands.
Corporate *.com CDNs (e.g. i5.walmartimages.com,
corporate.<brand>.com) — usually unstable; require auth or rotate.
Avoid unless verified.
If no working URL is found after 2–3 candidates, ask the user for one
instead of guessing.
Output layout
For every new company create a folder under dashboards/:
dashboards/<Company>/
<company>-dashboard-v1.json # Gen 3 dashboard JSON
<company>-injector.js # 30-min BizEvents injector
README.md # overview, dashboard ID, workflow ID
LEARNINGS.md # iteration notes (DQL patterns, pitfalls)
SALES-PITCH.md # 1-page value pitch for sales teams
File naming: lower‑case company slug, hyphen‑separated. Versioned dashboards
are *-dashboard-v2.json — never overwrite v1. In‑workflow task names
mirror the version (e.g. acme_v1, acme_v2).
Every dashboard must include the most relevant map tile, a bubbleMap,
dotMap, connectionMap, or chloropleth tile, fed by an event type
that emits geo.location.latitude and geo.location.longitude (cluster,
region, site, or store). The injector must populate these fields for at
least one event type. See tile 30 in example_dashboard.json for shape.
Place the map immediately under the header — full width (w:24,
h:8) at y:2, before the executive summary. Geographic context belongs
above the fold. When inserting, bump every following tile's y by
exactly the map height; collisions silently break the layout.
Visualization variety — required mix
A monolithic stack of donut + area charts is visually monotonous. Aim
for a deliberate mix across the dashboard:
categoricalBar — horizontal stacked time-bars; same query
requirement.
honeycomb — many small categories (6+); needs
visualizationSettings.honeycomb.dataMappings.value = "<count_field>".
lineChart / areaChart — single or multi-series timeseries.
table / dataPage — raw rows.
bubbleMap / dotMap — geo.
singleValue — KPIs. Apply a gauge feel by attaching three
threshold colorRules with colorThresholdTarget: "background" and
customColor from var(--dt-colors-charts-status-{success,warning,critical}-default, ...).
Comparator ≥ (Unicode), highest threshold first. Gen 3 has no
separate gauge viz type — this IS the gauge.
When swapping a donut/pie to bar/categoricalBar/honeycomb, strip
visualizationSettings.chartSettings.circleChartSettings. Leaving it
in makes the new chart render blank.
Phase 3 — DQL query patterns
Always filter by event.provider == "<company>.event.provider" and use the
field aliases from your event schema (snake_case).
Pattern → visualization
DQL pattern
Visualization
Use case
summarize <agg>
singleValue
Single KPI
makeTimeseries <agg>, bins:N
lineChart, areaChart
Time trends
fetch ... | filter ... | fields ...
table, dataPage
Raw data display
summarize by:{field}
donutChart, pieChart, honeycomb
Pure-category breakdown (NO time axis)
makeTimeseries by:{field}, bins:N
barChart, categoricalBar, stacked areaChart
Categorical trends over time
Critical:barChart and categoricalBar in Gen 3 ALWAYS require a
time axis. The Gen 3 chart engine demands fieldMapping.timestamp = "timeframe" and a timeframe column in the result, which only
makeTimeseries produces. Feeding a summarize by:{} result into a
barChart errors with “Time is required and there is no suitable
field.” For non-time category visuals, use donutChart, pieChart,
honeycomb, or table.
Define each variable as type: "query", multiple: true, sourced via
| dedup <field> against the company's event.provider. Filter tiles
with plain | filter in(<field>, $<Var>) — noarray_size($Var) == 0 escape clause (it breaks the filter; default-all already returns
all rows).
Rules:
Insert filters BEFORE aggregation pipes (makeTimeseries,
summarize, fields*, sort, limit). After makeTimeseries the
source field no longer exists, so a trailing
| filter in(region, $Region) silently drops every row.
Per-tile field availability matters. Compute the intersection
of filterable fields across every event.type referenced by the
tile. Only inject filters for fields shared by ALL referenced types.
Tiles whose events share no filterable dimensions (section dividers,
funnel-only events, the global map) correctly get no variable filter.
Variables are company-specific. Pick 3–5 dimensions that map to
the operating model (e.g. $Banner, $Region, $Department,
$Channel, $Store). Avoid more than ~5 — the bar gets crowded.
DQL best practices
Always filter by event.provider.
Snake_case field names matching the injector schema.
Add | limit 10 while testing.
makeTimeseries for time charts AND for barChart/categoricalBar;
summarize only for singleValue/donutChart/pieChart/honeycomb/table.
Ratio metrics = sum(num)/sum(denom)*100, never avg(percent).
Test queries in the DQL editor (or dtctl query) before adding to
the dashboard JSON. Substitute a literal array(...) for $Var to
smoke-test multi-select filters.
Use the dt-app-dashboards, dt-dql-essentials, dt-app-notebooks, and
dtctl skills when available in the agent runtime.
Phase 4 — Event injector JavaScript
Use reference/example-injector.js and the script
field in example_data_injector.workflow.json as the structural template.
Requirements
Event types: 15–20 different types
(gaming.transaction, guest.checkin, equipment.telemetry, ...).
Field schema: snake_case for all fields
(gaming_venue, occupancy_percent, ...).
Realistic values: match the business domain (currency for prices,
0–100 for percentages, plausible ranges).
Volume: 3,000–5,000 events per execution (~100+ per event type).
Geo fields: at least one event type emits
geo.location.latitude / geo.location.longitude for the map tile.
Each DQL query tested in the DQL editor with | limit 10.
Aggregation type matches visualization (makeTimeseries vs
summarize).
Field names match the injector schema exactly.
Tile creation:
Logo tile (markdown, h:2, w:6).
Title tile (markdown, h:2, w:18).
Map tile placed at y:2 (above executive summary), w:24,
h:8.
Section dividers (h:1, colored).
KPI tiles (h:2, under each section); 3–4 use singleValue +
threshold colorRules for gauge feel.
Chart tiles (h:4+, under KPIs).
Visualization mix: at least 4 distinct chart types across the
board (e.g. pieChart, barChart, categoricalBar, honeycomb,
lineChart, areaChart); avoid all-donut.
All barChart/categoricalBar queries use makeTimeseries,
not summarize by:{}; fieldMapping includes
timestamp:"timeframe", leftAxisValues, leftAxisDimensions.
Any honeycomb tile sets
visualizationSettings.honeycomb.dataMappings.value.
Any non-circular chart has chartSettings.circleChartSettings
removed.
Consistent X positions (0, 6, 12, 18).
Y gaps minimized (+1 to +2).
Styling & validation:
Section colors applied.
Chart legend.ratio 20–30.
categoryOverrides for semantic colors.
No red‑X tiles in preview.
Logo and map tile render correctly.
Phase 6 — Workflow & deployment (CRITICAL RULES)
The injector workflow is shared across all companies in a tenant. There
is exactly one injector workflow per tenant; new companies are added as
additional tasks inside it.
Submitting just the content body imports tiles but creates an
"Untitled dashboard" with name and ID detached. Re-applying with the
wrapper fixes it in place (ACTION = updated).
Search for the existing injector workflow first:
dtctl get workflows -o json --plain | \
jq '.[] | select(.title | test("BizEvents Dashboard Generator|KPI Data Injector|injector"; "i"))'
Prefer the workflow titled 1.BizEvents Dashboard Generator. If multiple
match, confirm with the user.
If a workflow exists (the normal case):
dtctl get workflow <id> -o json --plain > .tmp/workflow.json
Append a new task keyed <company>_v1 (or _v2 on iteration).
Use a uniqueposition.{x, y} — duplicates produce a 400 error.
Set predecessors: [] so tasks run in parallel.
dtctl apply -f .tmp/workflow.json
If no workflow exists (first run on a brand‑new tenant only):
Use reference/example_data_injector.workflow.json
as the template.
Replace its single task with the new company's task; rename the
workflow 1.BizEvents Dashboard Generator.
Phase 8 — Quality gate (run before declaring done)
Logo renders.
All section dividers show correct colors.
No red‑X tiles.
Every KPI tile has data.
Every chart shows legends/labels.
Map tile is populated with cluster/region/site coordinates.
Layout is compact (no excessive whitespace).
Workflow execution finished SUCCESS.
3,000+ events ingested per run.
README.md, LEARNINGS.md, SALES-PITCH.md all present.
Key principles
Markdown formatting is critical — pure markdown only, no HTML.
Logo = professional touch — every dashboard branded.
Charts need space — h:4 minimum.
Test before deploy — DQL in the editor first.
Document everything — LEARNINGS.md is the knowledge capital.
Consistency breeds quality — follow the example shape exactly.
One injector workflow per tenant — always add a task, never duplicate.
What the agent must NOT do
Do not invent dashboard IDs, workflow IDs, or URLs — always use values
returned by dtctl.
Do not create a second injector workflow when one exists.
Do not skip the map tile.
Do not push commits or open PRs unless asked.
Do not run destructive dtctl delete commands without explicit user
confirmation.
Do not attempt to install or configure dtctl.
1---2name: dynatrace-kpi-dashboard-generator3description: Generate a Dynatrace Gen 3 **KPI dashboard** (15–20 business KPIs, required map tile, branded section dividers) and a matching 30‑minute BizEvents injector for a named company, then deploy both via `dtctl`. Use this skill ONLY when the user explicitly asks for a Dynatrace KPI dashboard, business-event KPI demo, BizEvents injector, or a "KPI dashboard for <company>" — do NOT use for generic Dynatrace dashboards (SRE, infra, k8s, services, RUM) or for editing existing non-KPI dashboards. Triggers include phrases like "generate a KPI dashboard", "build a BizEvents demo for <company>", "spin up a KPI dashboard + injector", "/generate-kpi-dashboard". Requires `dtctl` authenticated to a Dynatrace Gen 3 tenant.4---56# Business Event Generator Agent78Canonical instructions for any agent (Claude Code, GitHub Copilot, Cursor,9etc.) running in this repository. The agent's job: for any company the user10names, generate a Dynatrace **Gen 3 KPI dashboard** and a **30‑minute11BizEvents injector**, deploy them with `dtctl`, and verify ingestion.1213---1415## Role & Objective1617You are a Dynatrace Solutions Engineer. For a given company:18191. Research industry‑specific KPIs relevant to their business (15–20).202. Build a **Gen 3 dashboard** with real‑time KPI tiles, charts, and a21 required map tile.223. Create a JavaScript injector that streams 3,000–5,000 business events per23 30‑minute run.244. Deploy both to Dynatrace via `dtctl`, **adding a task to the existing25 injector workflow** (never creating a second injector workflow).265. Document patterns in the company folder for reuse.2728---2930## Prerequisites31321. **`dtctl`** installed and authenticated to a Dynatrace Gen 3 tenant.33 Verify with `dtctl auth whoami` or `scripts/check-prereqs.sh`. If `dtctl` is34 missing or unauthenticated, **stop and tell the user** — do not try to35 install or configure it. (macOS/Linux can run `scripts/install.sh`;36 Windows users follow https://github.com/dynatrace-oss/dtctl#install.)372. **`dtctl` agent skill** installed38 (`npx skills add dynatrace-oss/dtctl`) — the agent uses it to operate39 `dtctl` correctly.403. **`dynatrace-for-ai` skills** installed41 (`npx skills add dynatrace/dynatrace-for-ai`) — supplies42 `dt-dql-essentials`, `dt-app-dashboards`, `dt-app-notebooks`, and the43 `dt-obs-*` domain skills the agent leans on.444. **`jq`** for workflow JSON manipulation.455. Network access to fetch the company logo URL.4647---4849## Tenant confirmation — REQUIRED before any tenant write5051Before the agent runs **any** `dtctl apply`, `dtctl exec`, `dtctl create`,52`dtctl edit`, or `dtctl delete` command (anything that mutates the tenant or53executes a workflow), it MUST:54551. Show the active context and identity to the user, e.g.:56 ```bash57 dtctl ctx current58 dtctl auth whoami59 ```60 Display the tenant URL / environment, context name, and authenticated61 principal.622. Ask the user to confirm this is the correct tenant before proceeding.633. If the user declines or says it is wrong, stop and have them switch64 contexts (`dtctl context use <name>`) before re‑running.6566The agent must not silently target whatever context happens to be active.67This check is required on every invocation, even if the agent ran68successfully against the same tenant earlier in the session.6970---7172## Inputs the agent collects7374When invoked, the agent asks for (or infers from the user's request):7576- **Company name** (required) — used for folder name, dashboard title, and77 `event.provider` (e.g. `acme.event.provider`).78- **Industry / business domain** (optional) — research hint for KPI choice.79- **Logo URL** (optional) — if missing, search the web for a public logo URL80 and confirm with the user before using it.8182### Logo URL — VERIFY BEFORE EMBEDDING8384Never embed a logo without first confirming the URL serves an image to a85cross-origin browser. Run:8687```bash88curl -sIL -A 'Mozilla/5.0' -H 'Referer: https://apps.dynatrace.com' '<URL>' \89 | grep -E '^(HTTP|content-type)'90```9192Required: final `HTTP/2 200` AND `content-type: image/(png|svg+xml|jpeg|webp)`.93If the response is `400`, `403`, `404`, or `text/html`, the logo will94render as a broken image in the markdown tile.9596Known behavior:97- `upload.wikimedia.org/wikipedia/commons/...` — files frequently get98 renamed (e.g. `Walmart_logo.svg` → `Walmart logo (2008).svg` on a99 hashed path). Plain hot-links return `400` from Varnish for non-wiki100 referers. Resolve current URL via the Commons API:101 `https://commons.wikimedia.org/w/api.php?action=query&titles=File:<Name>.svg&prop=imageinfo&iiprop=url&format=json`.102- `1000logos.net` and `logos-world.net` — allow hot-linking, return103 `image/png`. Reliable fallback for major brands.104- Corporate `*.com` CDNs (e.g. `i5.walmartimages.com`,105 `corporate.<brand>.com`) — usually unstable; require auth or rotate.106 Avoid unless verified.107108If no working URL is found after 2–3 candidates, ask the user for one109instead of guessing.110111---112113## Output layout114115For every new company create a folder under `dashboards/`:116117```118dashboards/<Company>/119 <company>-dashboard-v1.json # Gen 3 dashboard JSON120 <company>-injector.js # 30-min BizEvents injector121 README.md # overview, dashboard ID, workflow ID122 LEARNINGS.md # iteration notes (DQL patterns, pitfalls)123 SALES-PITCH.md # 1-page value pitch for sales teams124```125126File naming: lower‑case company slug, hyphen‑separated. Versioned dashboards127are `*-dashboard-v2.json` — **never overwrite v1**. In‑workflow task names128mirror the version (e.g. `acme_v1`, `acme_v2`).129130---131132## Reference assets (read these before generating)133134- `reference/example_dashboard.json` — Gen 3 dashboard135 JSON shape: tiles, layouts, variables, map tile, section dividers,136 category overrides.137- `reference/example_data_injector.workflow.json` —138 Workflow + JS task shape (schedule, ownerType, action type, position).139- `reference/example-injector.js` — Realistic injector140 JS template: event helpers, batched ingest, cluster/region weights, geo141 coords, schema conventions.142143The agent must **mirror the structure** of these examples.144145---146147## Phase 1 — Planning & Research148149Identify primary business processes (gaming, hospitality, manufacturing,150sales, operations, etc.). For each, define:151152- KPIs (revenue, utilization, satisfaction, response time, etc.).153- Event types that map to those KPIs (transactions, state changes, service154 requests, telemetry).155- Realistic per‑run volume targets (15–20 event types totaling156 3,000–5,000 events per 30‑minute run).157158---159160## Phase 2 — Dashboard design (Gen 3 only)161162### Header — required split layout163164Two side‑by‑side markdown tiles (NOT one combined tile, NOT HTML):165166```167"0": # Logo tile168 type: markdown169 content: ""170 layout: { x: 0, y: 0, w: 6, h: 2 }171172"41": # Title tile173 type: markdown174 content: "# <Company> | Operations Dashboard\n\nReal-time KPI monitoring..."175 layout: { x: 6, y: 0, w: 18, h: 2 }176```177178Markdown tiles do not reliably support `<div>`, `<img>`, or other inline179HTML. Use pure markdown image syntax (``).180181### Section dividers182183Use a `singleValue` data tile with `data record(section="...")`, height184`h:1`, full width `w:24`, distinct brand‑appropriate background color per185section.186187Suggested palette (override per company brand):188- Gaming: `#D4AF37` (Gold)189- Hospitality: `#1E90FF` (Blue)190- Dining/F&B: `#FF6347` (Tomato Red)191- Guest Experience: `#9B59B6` (Purple)192- Operations/Facilities: `#34495E` (Slate)193- Summary/KPI: `#DAA520` (Dark Gold)194195Example divider:196197```json198{199 "type": "data",200 "title": "SECTION NAME",201 "query": "data record(section=\"Name\")",202 "visualization": "singleValue",203 "visualizationSettings": {204 "singleValue": {205 "labelMode": "none",206 "isIconVisible": true,207 "prefixIcon": "GridIcon",208 "colorThresholdTarget": "background"209 },210 "thresholds": [211 { "id": 1, "field": "section", "rules": [212 { "id": 1, "color": "#D4AF37", "comparator": "!=", "value": "1" }213 ]}214 ]215 }216}217```218219### Tile height guidelines220221| Height | Use case | Examples |222|--------|----------|----------|223| `h:1` | Section dividers, sparse info | Section headers |224| `h:2` | Single‑value KPIs | Revenue totals, occupancy %, counts |225| `h:3` | Small charts | 2–3 category bar charts |226| `h:4` | **Standard charts (recommended)** | Line, bar, area, donut |227| `h:5+` | Dense tables / multi‑series | Summary tables, complex analyses |228229**Rule:** chart tiles need `h:4` minimum. `h:2`–`h:3` truncates legends and230labels.231232### Layout & spacing233234Minimize vertical gaps for a professional appearance:235236- Y‑axis increments: `+1` to `+2` units between rows (not `+3+`).237- Pattern: divider (`h:1`) → KPI row (`h:2`) → chart row (`h:4`) → next238 divider.239- Consistent X columns: `0, 6, 12, 18` (board width is 24).240- Example flow:241 ```242 y:0 header (h:2)243 y:2 divider (h:1)244 y:3 KPI row (h:2)245 y:5 chart row (h:4)246 y:9 next divider (h:1)247 ```248249### Map tile — REQUIRED, ABOVE THE FOLD250251Every dashboard must include the most relevant map tile, a `bubbleMap`,252`dotMap`, `connectionMap`, or `chloropleth` tile, fed by an event type253that emits `geo.location.latitude` and `geo.location.longitude` (cluster,254region, site, or store). The injector must populate these fields for at255least one event type. See tile `30` in `example_dashboard.json` for shape.256257**Place the map immediately under the header** — full width (`w:24`,258`h:8`) at `y:2`, before the executive summary. Geographic context belongs259above the fold. When inserting, bump every following tile's `y` by260exactly the map height; collisions silently break the layout.261262### Visualization variety — required mix263264A monolithic stack of donut + area charts is visually monotonous. Aim265for a deliberate mix across the dashboard:266267- **`pieChart`** — small categorical share (3–5 slices).268- **`donutChart`** — same, when you want a center total.269- **`barChart`** (vertical, stacked) — categorical-over-time. Requires270 `makeTimeseries ..., by:{<group>}, bins:N` (see Phase 3).271- **`categoricalBar`** — horizontal stacked time-bars; same query272 requirement.273- **`honeycomb`** — many small categories (6+); needs274 `visualizationSettings.honeycomb.dataMappings.value = "<count_field>"`.275- **`lineChart` / `areaChart`** — single or multi-series timeseries.276- **`table` / `dataPage`** — raw rows.277- **`bubbleMap` / `dotMap`** — geo.278- **`singleValue`** — KPIs. Apply a **gauge feel** by attaching three279 threshold `colorRules` with `colorThresholdTarget: "background"` and280 `customColor` from `var(--dt-colors-charts-status-{success,warning,critical}-default, ...)`.281 Comparator `≥` (Unicode), highest threshold first. Gen 3 has no282 separate `gauge` viz type — this IS the gauge.283284When swapping a donut/pie to bar/categoricalBar/honeycomb, **strip285`visualizationSettings.chartSettings.circleChartSettings`**. Leaving it286in makes the new chart render blank.287288---289290## Phase 3 — DQL query patterns291292Always filter by `event.provider == "<company>.event.provider"` and use the293field aliases from your event schema (snake_case).294295### Pattern → visualization296297| DQL pattern | Visualization | Use case |298|-------------|---------------|----------|299| `summarize <agg>` | `singleValue` | Single KPI |300| `makeTimeseries <agg>, bins:N` | `lineChart`, `areaChart` | Time trends |301| `fetch ... \| filter ... \| fields ...` | `table`, `dataPage` | Raw data display |302| `summarize by:{field}` | `donutChart`, `pieChart`, `honeycomb` | Pure-category breakdown (NO time axis) |303| `makeTimeseries by:{field}, bins:N` | `barChart`, `categoricalBar`, stacked `areaChart` | Categorical trends over time |304305**Critical:** `barChart` and `categoricalBar` in Gen 3 ALWAYS require a306time axis. The Gen 3 chart engine demands `fieldMapping.timestamp =307"timeframe"` and a `timeframe` column in the result, which only308`makeTimeseries` produces. Feeding a `summarize by:{}` result into a309`barChart` errors with “Time is required and there is no suitable310field.” For non-time category visuals, use `donutChart`, `pieChart`,311`honeycomb`, or `table`.312313`barChart` / `categoricalBar` `fieldMapping`:314```json315{ "timestamp": "timeframe",316 "leftAxisValues": ["<value_field>"],317 "leftAxisDimensions": ["<group_field>"] }318```319320### Common pitfalls321322❌ WRONG — feeding `summarize` into a `barChart`/`categoricalBar`:323```dql324fetch bizevents | summarize revenue = sum(amount), by:{venue}325| visualization: barChart // "Time is required"326```327328✅ CORRECT — use `makeTimeseries` for any bar chart:329```dql330fetch bizevents | makeTimeseries revenue = sum(amount), by:{venue}, bins:20331| visualization: barChart332```333334❌ WRONG — `avg(percentage_field)` for ratio metrics:335```dql336| summarize conversion = avg(conversion_percent)337```338339✅ CORRECT — `sum/sum` calc:340```dql341| summarize visitors = sum(visitors_count), txns = sum(transactions_count)342| fieldsAdd conversion = (toDouble(txns) / toDouble(visitors)) * 100343```344345### Multi-select variable filters346347Define each variable as `type: "query"`, `multiple: true`, sourced via348`| dedup <field>` against the company's `event.provider`. Filter tiles349with plain `| filter in(<field>, $<Var>)` — **no** `array_size($Var)350== 0` escape clause (it breaks the filter; default-all already returns351all rows).352353Rules:3541. **Insert filters BEFORE aggregation pipes** (`makeTimeseries`,355 `summarize`, `fields*`, `sort`, `limit`). After `makeTimeseries` the356 source field no longer exists, so a trailing357 `| filter in(region, $Region)` silently drops every row.3582. **Per-tile field availability matters.** Compute the **intersection**359 of filterable fields across every `event.type` referenced by the360 tile. Only inject filters for fields shared by ALL referenced types.361 Tiles whose events share no filterable dimensions (section dividers,362 funnel-only events, the global map) correctly get no variable filter.3633. Variables are **company-specific**. Pick 3–5 dimensions that map to364 the operating model (e.g. `$Banner`, `$Region`, `$Department`,365 `$Channel`, `$Store`). Avoid more than ~5 — the bar gets crowded.366367### DQL best practices3683691. Always filter by `event.provider`.3702. Snake_case field names matching the injector schema.3713. Add `| limit 10` while testing.3724. `makeTimeseries` for time charts AND for `barChart`/`categoricalBar`;373 `summarize` only for `singleValue`/`donutChart`/`pieChart`/`honeycomb`/`table`.3745. Ratio metrics = `sum(num)/sum(denom)*100`, never `avg(percent)`.3756. Test queries in the DQL editor (or `dtctl query`) before adding to376 the dashboard JSON. Substitute a literal `array(...)` for `$Var` to377 smoke-test multi-select filters.378379Use the `dt-app-dashboards`, `dt-dql-essentials`, `dt-app-notebooks`, and380`dtctl` skills when available in the agent runtime.381382---383384## Phase 4 — Event injector JavaScript385386Use `reference/example-injector.js` and the `script`387field in `example_data_injector.workflow.json` as the structural template.388389### Requirements390391- **Event types:** 15–20 different types392 (`gaming.transaction`, `guest.checkin`, `equipment.telemetry`, ...).393- **Field schema:** snake_case for all fields394 (`gaming_venue`, `occupancy_percent`, ...).395- **Realistic values:** match the business domain (currency for prices,396 0–100 for percentages, plausible ranges).397- **Volume:** 3,000–5,000 events per execution (~100+ per event type).398- **Geo fields:** at least one event type emits399 `geo.location.latitude` / `geo.location.longitude` for the map tile.400- **Ingest endpoint:** `/platform/classic/environment-api/v2/bizevents/ingest`.401- **Batching:** 500 events per POST to stay under ~5MB; throw on non‑2xx.402- **Auth:** integrated platform auth — no token; the workflow runs in the403 AutomationEngine context.404- **Provider:** `EVENT_PROVIDER = "<company>.event.provider"`.405406---407408## Phase 5 — Dashboard implementation checklist409410Pre‑implementation:411- [ ] Meaningful dashboard title (e.g. `<Company> | Operations Dashboard`).412- [ ] 15–20 KPIs researched and mapped to event types.413- [ ] Layout sketched (sections, tile positions).414- [ ] Logo URL gathered **AND verified** via `curl -sIL` (must return415 `HTTP 200` + `content-type: image/*`).416- [ ] 5–6 section colors chosen from brand/theme.417- [ ] 3–5 dashboard variables chosen (multi-select, query-driven).418419Query validation:420- [ ] Each DQL query tested in the DQL editor with `| limit 10`.421- [ ] Aggregation type matches visualization (`makeTimeseries` vs422 `summarize`).423- [ ] Field names match the injector schema exactly.424425Tile creation:426- [ ] Logo tile (markdown, `h:2`, `w:6`).427- [ ] Title tile (markdown, `h:2`, `w:18`).428- [ ] **Map tile placed at `y:2` (above executive summary), `w:24`,429 `h:8`**.430- [ ] Section dividers (`h:1`, colored).431- [ ] KPI tiles (`h:2`, under each section); 3–4 use `singleValue` +432 threshold `colorRules` for gauge feel.433- [ ] Chart tiles (`h:4+`, under KPIs).434- [ ] **Visualization mix:** at least 4 distinct chart types across the435 board (e.g. `pieChart`, `barChart`, `categoricalBar`, `honeycomb`,436 `lineChart`, `areaChart`); avoid all-donut.437- [ ] All `barChart`/`categoricalBar` queries use `makeTimeseries`,438 not `summarize by:{}`; `fieldMapping` includes439 `timestamp:"timeframe"`, `leftAxisValues`, `leftAxisDimensions`.440- [ ] Any `honeycomb` tile sets441 `visualizationSettings.honeycomb.dataMappings.value`.442- [ ] Any non-circular chart has `chartSettings.circleChartSettings`443 removed.444- [ ] Consistent X positions (`0, 6, 12, 18`).445- [ ] Y gaps minimized (`+1` to `+2`).446447Styling & validation:448- [ ] Section colors applied.449- [ ] Chart `legend.ratio` 20–30.450- [ ] `categoryOverrides` for semantic colors.451- [ ] No red‑X tiles in preview.452- [ ] Logo and map tile render correctly.453454---455456## Phase 6 — Workflow & deployment (CRITICAL RULES)457458The injector workflow is **shared across all companies** in a tenant. There459is exactly one injector workflow per tenant; new companies are added as460**additional tasks** inside it.461462### Step‑by‑step4634641. **Apply the dashboard:**465 ```bash466 dtctl apply -f "dashboards/<Company>/<company>-dashboard-v1.json"467 ```468 Capture the returned dashboard ID.469470 **Envelope shape (REQUIRED):** `dtctl apply` expects a wrapper:471 ```json472 { "id": "<uuid?>", "name": "<Company> | Operations Dashboard",473 "type": "dashboard", "isPrivate": false,474 "content": { "tiles": {...}, "layouts": {...}, "variables": [...],475 "settings": {...}, "version": 21, ... } }476 ```477 Submitting just the `content` body imports tiles but creates an478 "Untitled dashboard" with name and ID detached. Re-applying with the479 wrapper fixes it in place (`ACTION = updated`).4804812. **Search for the existing injector workflow first:**482 ```bash483 dtctl get workflows -o json --plain | \484 jq '.[] | select(.title | test("BizEvents Dashboard Generator|KPI Data Injector|injector"; "i"))'485 ```486 Prefer the workflow titled `1.BizEvents Dashboard Generator`. If multiple487 match, confirm with the user.4884893. **If a workflow exists (the normal case):**490 - `dtctl get workflow <id> -o json --plain > .tmp/workflow.json`491 - Append a new task keyed `<company>_v1` (or `_v2` on iteration).492 - Use a **unique** `position.{x, y}` — duplicates produce a 400 error.493 - Set `predecessors: []` so tasks run in parallel.494 - `dtctl apply -f .tmp/workflow.json`4954964. **If no workflow exists (first run on a brand‑new tenant only):**497 - Use `reference/example_data_injector.workflow.json`498 as the template.499 - Replace its single task with the new company's task; rename the500 workflow `1.BizEvents Dashboard Generator`.501 - `dtctl apply -f` it; capture the workflow ID.5025035. **Execute and verify:**504 ```bash505 dtctl exec workflow <id>506 dtctl describe workflow-execution <exec-id> # wait for SUCCESS507 ```508 `describe workflow-execution` returns an empty `tasks{}` dict — to509 inspect the JS task's return value (totals, batches, errors) use:510 ```bash511 dtctl get wfe-task-result <exec-id> -t <taskName>512 ```513 The task name is required via the `-t/--task` flag, NOT positional.5145156. **Verify ingestion:**516 ```dql517 fetch bizevents518 | filter event.provider == "<company>.event.provider"519 | summarize total = count()520 ```521522**Never create a second injector workflow** when one already exists.523524### Versioning525526- First iteration: `<company>-dashboard-v1.json`, task `<company>_v1`.527- Updates: `<company>-dashboard-v2.json`, task `<company>_v2`.528- Never overwrite v1 files.529530---531532## Phase 7 — Documentation deliverables533534For every project, write into the company folder:535536- **`README.md`** — Project overview, file list, dashboard ID, workflow ID,537 task name, deployment commands.538- **`LEARNINGS.md`** — DQL patterns, layout decisions, pitfalls, color539 scheme, anything reusable for the next project.540- **`SALES-PITCH.md`** — 1‑page value proposition tailored to the company541 for the sales team.542543`LEARNINGS.md` template:544545```markdown546# <Company> Dashboard Learnings547548**Date:** <date>549**Version:** v1550551## DQL Patterns Used552| Tile Type | DQL Pattern | Notes |553|-----------|-------------|-------|554555## Layout Decisions556- Header, dividers, KPI rows, chart rows...557558## Pitfalls Hit5591. ...560561## Color Scheme562- Section: `#hex`563```564565---566567## Phase 8 — Quality gate (run before declaring done)568569- [ ] Logo renders.570- [ ] All section dividers show correct colors.571- [ ] No red‑X tiles.572- [ ] Every KPI tile has data.573- [ ] Every chart shows legends/labels.574- [ ] Map tile is populated with cluster/region/site coordinates.575- [ ] Layout is compact (no excessive whitespace).576- [ ] Workflow execution finished SUCCESS.577- [ ] 3,000+ events ingested per run.578- [ ] `README.md`, `LEARNINGS.md`, `SALES-PITCH.md` all present.579580---581582## Key principles5835841. **Markdown formatting is critical** — pure markdown only, no HTML.5852. **Logo = professional touch** — every dashboard branded.5863. **Charts need space** — `h:4` minimum.5874. **Test before deploy** — DQL in the editor first.5885. **Document everything** — `LEARNINGS.md` is the knowledge capital.5896. **Consistency breeds quality** — follow the example shape exactly.5907. **One injector workflow per tenant** — always add a task, never duplicate.591592---593594## What the agent must NOT do595596- Do not invent dashboard IDs, workflow IDs, or URLs — always use values597 returned by `dtctl`.598- Do not create a second injector workflow when one exists.599- Do not skip the map tile.600- Do not push commits or open PRs unless asked.601- Do not run destructive `dtctl delete` commands without explicit user602 confirmation.603- Do not attempt to install or configure `dtctl`.
Run npx skillmds@latest add sudosmitty/dynatrace-kpi-dashboard-generator in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Generate a Dynatrace Gen 3 **KPI dashboard** (15–20 business KPIs, required map tile, branded section dividers) and a matching 30‑minute BizEvents injector for a named company, then deploy both via `dtctl`. Use this skill ONLY when the user explicitly asks for a Dynatrace KPI dashboard, business-event KPI demo, BizEvents injector, or a "KPI dashboard for <company>" — do NOT use for generic Dynatrace dashboards (SRE, infra, k8s, services, RUM) or for editing existing non-KPI dashboards. Triggers include phrases like "generate a KPI dashboard", "build a BizEvents demo for <company>", "spin up a KPI dashboard + injector", "/generate-kpi-dashboard". Requires `dtctl` authenticated to a Dynatrace Gen 3 tenant. It is listed under DevOps & Infra on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
sudosmitty (@sudosmitty) published this skill. Their other Agent Skills are listed on their SkillMD profile.