From b1f322d7f4644f98962b8a2ff2b5491b24c61584 Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Sat, 25 Jul 2026 20:09:00 -0500 Subject: [PATCH 01/19] alerting-first-rule-lj: add install-datasources milestone Adds a Pathfinder-driven QuickPizza install step so learners don't need to install the tutorial data sources manually before starting the path. Rewires business-value -> install-datasources -> navigate-to-form, bumps downstream step numbers, and trims the install bullet from the cover. --- .../build-query/website.yaml | 2 +- .../business-value/content.json | 2 +- .../business-value/manifest.json | 2 +- .../configure-handling/website.yaml | 2 +- .../configure-labels-evaluation/website.yaml | 2 +- alerting-first-rule-lj/content.json | 2 +- .../end-journey/website.yaml | 2 +- .../install-datasources/content.json | 47 +++++++++++++++++++ .../install-datasources/manifest.json | 17 +++++++ .../install-datasources/website.yaml | 12 +++++ alerting-first-rule-lj/manifest.json | 1 + .../navigate-to-form/manifest.json | 2 +- .../navigate-to-form/website.yaml | 2 +- alerting-first-rule-lj/save-rule/website.yaml | 2 +- .../select-contact-point/website.yaml | 2 +- .../set-condition/website.yaml | 2 +- .../set-evaluation-behavior/website.yaml | 2 +- 17 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 alerting-first-rule-lj/install-datasources/content.json create mode 100644 alerting-first-rule-lj/install-datasources/manifest.json create mode 100644 alerting-first-rule-lj/install-datasources/website.yaml diff --git a/alerting-first-rule-lj/build-query/website.yaml b/alerting-first-rule-lj/build-query/website.yaml index 97562062..ba4954a1 100644 --- a/alerting-first-rule-lj/build-query/website.yaml +++ b/alerting-first-rule-lj/build-query/website.yaml @@ -5,7 +5,7 @@ keywords: - alerting - query weight: 400 -step: 4 +step: 5 layout: single-journey cta: type: continue diff --git a/alerting-first-rule-lj/business-value/content.json b/alerting-first-rule-lj/business-value/content.json index a3121cb6..33af9510 100644 --- a/alerting-first-rule-lj/business-value/content.json +++ b/alerting-first-rule-lj/business-value/content.json @@ -9,7 +9,7 @@ }, { "type": "markdown", - "content": "This path uses the QuickPizza tutorial data sources, which you install as a prerequisite before you begin. In the next milestone, you open the alert rule form and give your rule a clear name." + "content": "This path uses the QuickPizza tutorial data sources. The next milestone installs them for you, then the one after opens the alert rule form so you can name your rule." } ] } diff --git a/alerting-first-rule-lj/business-value/manifest.json b/alerting-first-rule-lj/business-value/manifest.json index 66c46c52..efac4b03 100644 --- a/alerting-first-rule-lj/business-value/manifest.json +++ b/alerting-first-rule-lj/business-value/manifest.json @@ -11,7 +11,7 @@ "tier": "cloud" }, "depends": [], - "recommends": ["alerting-first-rule-navigate-to-form"], + "recommends": ["alerting-first-rule-install-datasources"], "suggests": [], "provides": [] } diff --git a/alerting-first-rule-lj/configure-handling/website.yaml b/alerting-first-rule-lj/configure-handling/website.yaml index 6e9cf01c..1ceb028b 100644 --- a/alerting-first-rule-lj/configure-handling/website.yaml +++ b/alerting-first-rule-lj/configure-handling/website.yaml @@ -5,7 +5,7 @@ keywords: - alerting - handling weight: 800 -step: 8 +step: 9 layout: single-journey cta: type: continue diff --git a/alerting-first-rule-lj/configure-labels-evaluation/website.yaml b/alerting-first-rule-lj/configure-labels-evaluation/website.yaml index 66792584..21fc06f2 100644 --- a/alerting-first-rule-lj/configure-labels-evaluation/website.yaml +++ b/alerting-first-rule-lj/configure-labels-evaluation/website.yaml @@ -5,7 +5,7 @@ keywords: - alerting - labels weight: 600 -step: 6 +step: 7 layout: single-journey cta: type: continue diff --git a/alerting-first-rule-lj/content.json b/alerting-first-rule-lj/content.json index c9d0106a..633edd56 100644 --- a/alerting-first-rule-lj/content.json +++ b/alerting-first-rule-lj/content.json @@ -13,7 +13,7 @@ }, { "type": "markdown", - "content": "## Before you begin\n\nBefore you build your alert rule, ensure you have:\n\n- A Grafana Cloud account. To create one, refer to [Grafana Cloud](https://grafana.com/signup/cloud/connect-account).\n- Admin permissions to create and manage alert rules.\n- The QuickPizza tutorial data sources installed on your stack. To install them, refer to [Work with demo data in Grafana Cloud](https://grafana.com/docs/grafana-cloud/get-started/connect-send/explore-demo-data/). This path uses `grafanacloud-play-prom`, and querying it does not count toward your Grafana Cloud usage." + "content": "## Before you begin\n\nBefore you build your alert rule, ensure you have:\n\n- A Grafana Cloud account. To create one, refer to [Grafana Cloud](https://grafana.com/signup/cloud/connect-account).\n- Admin permissions to create and manage alert rules.\n\nThe path installs the QuickPizza tutorial data sources for you in the next milestone. This path uses `grafanacloud-play-prom`, and querying it does not count toward your Grafana Cloud usage." }, { "type": "markdown", diff --git a/alerting-first-rule-lj/end-journey/website.yaml b/alerting-first-rule-lj/end-journey/website.yaml index b0b6f036..548772fb 100644 --- a/alerting-first-rule-lj/end-journey/website.yaml +++ b/alerting-first-rule-lj/end-journey/website.yaml @@ -4,7 +4,7 @@ description: You created a complete alert rule from query to notification. keywords: - alerting weight: 1100 -step: 11 +step: 12 layout: single-journey cta: type: conclusion diff --git a/alerting-first-rule-lj/install-datasources/content.json b/alerting-first-rule-lj/install-datasources/content.json new file mode 100644 index 00000000..7a788152 --- /dev/null +++ b/alerting-first-rule-lj/install-datasources/content.json @@ -0,0 +1,47 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-first-rule-install-datasources", + "title": "Install the tutorial data sources", + "blocks": [ + { + "type": "markdown", + "content": "This path uses the QuickPizza tutorial data sources, which include a Prometheus data source named **grafanacloud-play-prom** that carries real metrics from a running demo service. Querying these data sources does not count toward your Grafana Cloud usage.\n\nThe install runs from the **Demo Data Dashboards** app in the left nav. After the install finishes, Grafana refreshes the page and the alerting flow picks up in the next milestone." + }, + { + "type": "section", + "id": "install-tutorial-datasources", + "title": "Install QuickPizza", + "objectives": [ + "has-datasource:grafanacloud-play-prom" + ], + "blocks": [ + { + "type": "interactive", + "action": "highlight", + "reftarget": "a[data-testid='data-testid Nav menu item'][href='/a/grafana-demodashboards-app']", + "content": "In the left nav, click **Demo Data Dashboards**.", + "tooltip": "Opens the app that installs the tutorial data sources.", + "requirements": [ + "navmenu-open", + "exists-reftarget" + ] + }, + { + "type": "interactive", + "action": "highlight", + "reftarget": "button[data-testid='install-quickpizza']", + "content": "Click **Install the Quick Pizza SRE demo**. This deploys the tutorial data sources and refreshes the page.", + "tooltip": "Installs grafanacloud-play-prom and the other tutorial data sources.", + "requirements": [ + "exists-reftarget" + ], + "completeEarly": true + } + ] + }, + { + "type": "markdown", + "content": "The `grafanacloud-play-prom` data source is now available. In the next milestone, you open the alert rule form and give your rule a clear name." + } + ] +} diff --git a/alerting-first-rule-lj/install-datasources/manifest.json b/alerting-first-rule-lj/install-datasources/manifest.json new file mode 100644 index 00000000..7210b3b4 --- /dev/null +++ b/alerting-first-rule-lj/install-datasources/manifest.json @@ -0,0 +1,17 @@ +{ + "id": "alerting-first-rule-install-datasources", + "type": "guide", + "description": "Install the QuickPizza tutorial data sources so the alert rule has real data to query.", + "category": "take-action", + "author": { + "name": "JohnnyK-Grafana", + "team": "Grafana Documentation" + }, + "testEnvironment": { + "tier": "cloud" + }, + "depends": ["alerting-first-rule-business-value"], + "recommends": ["alerting-first-rule-navigate-to-form"], + "suggests": [], + "provides": [] +} diff --git a/alerting-first-rule-lj/install-datasources/website.yaml b/alerting-first-rule-lj/install-datasources/website.yaml new file mode 100644 index 00000000..90ad9c11 --- /dev/null +++ b/alerting-first-rule-lj/install-datasources/website.yaml @@ -0,0 +1,12 @@ +menuTitle: Install data sources +title: Install the tutorial data sources +description: Install the QuickPizza tutorial data sources so the alert rule has real data to query. +keywords: + - alerting + - datasource + - quickpizza +weight: 200 +step: 3 +layout: single-journey +cta: + type: continue diff --git a/alerting-first-rule-lj/manifest.json b/alerting-first-rule-lj/manifest.json index 1f5de15e..de0be272 100644 --- a/alerting-first-rule-lj/manifest.json +++ b/alerting-first-rule-lj/manifest.json @@ -9,6 +9,7 @@ "testEnvironment": { "tier": "cloud" }, "milestones": [ "alerting-first-rule-business-value", + "alerting-first-rule-install-datasources", "alerting-first-rule-navigate-to-form", "alerting-first-rule-build-query", "alerting-first-rule-set-condition", diff --git a/alerting-first-rule-lj/navigate-to-form/manifest.json b/alerting-first-rule-lj/navigate-to-form/manifest.json index 3b8b9adb..f22be143 100644 --- a/alerting-first-rule-lj/navigate-to-form/manifest.json +++ b/alerting-first-rule-lj/navigate-to-form/manifest.json @@ -10,7 +10,7 @@ "testEnvironment": { "tier": "cloud" }, - "depends": ["alerting-first-rule-business-value"], + "depends": ["alerting-first-rule-install-datasources"], "recommends": ["alerting-first-rule-build-query"], "suggests": [], "provides": [] diff --git a/alerting-first-rule-lj/navigate-to-form/website.yaml b/alerting-first-rule-lj/navigate-to-form/website.yaml index d5bb9165..a93e52a2 100644 --- a/alerting-first-rule-lj/navigate-to-form/website.yaml +++ b/alerting-first-rule-lj/navigate-to-form/website.yaml @@ -5,7 +5,7 @@ keywords: - alerting - alert rule weight: 300 -step: 3 +step: 4 layout: single-journey cta: type: continue diff --git a/alerting-first-rule-lj/save-rule/website.yaml b/alerting-first-rule-lj/save-rule/website.yaml index 840b960b..72380bd2 100644 --- a/alerting-first-rule-lj/save-rule/website.yaml +++ b/alerting-first-rule-lj/save-rule/website.yaml @@ -5,7 +5,7 @@ keywords: - alerting - save weight: 1000 -step: 10 +step: 11 layout: single-journey cta: type: success diff --git a/alerting-first-rule-lj/select-contact-point/website.yaml b/alerting-first-rule-lj/select-contact-point/website.yaml index 0c0540ef..f97f7810 100644 --- a/alerting-first-rule-lj/select-contact-point/website.yaml +++ b/alerting-first-rule-lj/select-contact-point/website.yaml @@ -5,7 +5,7 @@ keywords: - alerting - notifications weight: 900 -step: 9 +step: 10 layout: single-journey cta: type: continue diff --git a/alerting-first-rule-lj/set-condition/website.yaml b/alerting-first-rule-lj/set-condition/website.yaml index f231820d..48eeb51f 100644 --- a/alerting-first-rule-lj/set-condition/website.yaml +++ b/alerting-first-rule-lj/set-condition/website.yaml @@ -5,7 +5,7 @@ keywords: - alerting - condition weight: 500 -step: 5 +step: 6 layout: single-journey cta: type: continue diff --git a/alerting-first-rule-lj/set-evaluation-behavior/website.yaml b/alerting-first-rule-lj/set-evaluation-behavior/website.yaml index ad70c031..099e6c20 100644 --- a/alerting-first-rule-lj/set-evaluation-behavior/website.yaml +++ b/alerting-first-rule-lj/set-evaluation-behavior/website.yaml @@ -5,7 +5,7 @@ keywords: - alerting - evaluation weight: 700 -step: 7 +step: 8 layout: single-journey cta: type: continue From 9b3a886c9624915e82b3bf30f6f61644e5fde2fc Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Sat, 25 Jul 2026 20:31:27 -0500 Subject: [PATCH 02/19] alerting-first-rule-lj: replace "form" with "new alert rule page" Aligns UI-element wording with current Grafana alerting docs (v11.1+), which describe this UI by the button label (+ New alert rule) and by named page sections rather than as a form. Wire-level IDs, folder names, section IDs, and dependency tokens are unchanged. --- alerting-first-rule-lj/business-value/content.json | 2 +- alerting-first-rule-lj/content.json | 2 +- .../install-datasources/content.json | 2 +- alerting-first-rule-lj/navigate-to-form/content.json | 10 +++++----- alerting-first-rule-lj/navigate-to-form/manifest.json | 2 +- alerting-first-rule-lj/navigate-to-form/website.yaml | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/alerting-first-rule-lj/business-value/content.json b/alerting-first-rule-lj/business-value/content.json index 33af9510..137c9fab 100644 --- a/alerting-first-rule-lj/business-value/content.json +++ b/alerting-first-rule-lj/business-value/content.json @@ -9,7 +9,7 @@ }, { "type": "markdown", - "content": "This path uses the QuickPizza tutorial data sources. The next milestone installs them for you, then the one after opens the alert rule form so you can name your rule." + "content": "This path uses the QuickPizza tutorial data sources. The next milestone installs them for you, then the one after opens the new alert rule page so you can name your rule." } ] } diff --git a/alerting-first-rule-lj/content.json b/alerting-first-rule-lj/content.json index 633edd56..5c6f1841 100644 --- a/alerting-first-rule-lj/content.json +++ b/alerting-first-rule-lj/content.json @@ -9,7 +9,7 @@ }, { "type": "markdown", - "content": "## Here's what to expect\n\nWhen you complete this path, you'll be able to:\n\n- Navigate to the alert rule form and give your rule a clear name\n- Build a Prometheus query using the visual query builder\n- Set a comparison operator and threshold for the alert condition\n- Select a folder and add labels that control routing\n- Configure the evaluation group and pending period\n- Configure no-data and error handling\n- Select a contact point and add a summary annotation\n- Save and activate your alert rule" + "content": "## Here's what to expect\n\nWhen you complete this path, you'll be able to:\n\n- Open the new alert rule page and give your rule a clear name\n- Build a Prometheus query using the visual query builder\n- Set a comparison operator and threshold for the alert condition\n- Select a folder and add labels that control routing\n- Configure the evaluation group and pending period\n- Configure no-data and error handling\n- Select a contact point and add a summary annotation\n- Save and activate your alert rule" }, { "type": "markdown", diff --git a/alerting-first-rule-lj/install-datasources/content.json b/alerting-first-rule-lj/install-datasources/content.json index 7a788152..3dad4abb 100644 --- a/alerting-first-rule-lj/install-datasources/content.json +++ b/alerting-first-rule-lj/install-datasources/content.json @@ -41,7 +41,7 @@ }, { "type": "markdown", - "content": "The `grafanacloud-play-prom` data source is now available. In the next milestone, you open the alert rule form and give your rule a clear name." + "content": "The `grafanacloud-play-prom` data source is now available. In the next milestone, you open the new alert rule page and give your rule a clear name." } ] } diff --git a/alerting-first-rule-lj/navigate-to-form/content.json b/alerting-first-rule-lj/navigate-to-form/content.json index 19126f47..ef11d9a9 100644 --- a/alerting-first-rule-lj/navigate-to-form/content.json +++ b/alerting-first-rule-lj/navigate-to-form/content.json @@ -1,16 +1,16 @@ { "schemaVersion": "1.1.0", "id": "alerting-first-rule-navigate-to-form", - "title": "Navigate to the alert rule form", + "title": "Open the new alert rule page", "blocks": [ { "type": "markdown", - "content": "A clear, descriptive name is the first thing responders see when an alert fires. A good name tells them immediately what's wrong without opening the alert details.\n\nIn this milestone, you'll open the alert rule creation form and give your rule a name." + "content": "A clear, descriptive name is the first thing responders see when an alert fires. A good name tells them immediately what's wrong without opening the alert details.\n\nIn this milestone, you'll open the new alert rule page and give your rule a name." }, { "type": "section", "id": "open-alert-form", - "title": "Open the alert rule form", + "title": "Open the new alert rule page", "blocks": [ { "type": "interactive", @@ -23,13 +23,13 @@ "type": "interactive", "action": "highlight", "reftarget": "a[href*='alerting/new/alerting']", - "content": "Click **+ New alert rule** to open the alert rule creation form.", + "content": "Click **+ New alert rule** to open the new alert rule page.", "requirements": ["on-page:/alerting/list"], "verify": "on-page:/alerting/new/alerting" }, { "type": "markdown", - "content": "You're now on the alert rule creation form." + "content": "You're now on the new alert rule page." } ] }, diff --git a/alerting-first-rule-lj/navigate-to-form/manifest.json b/alerting-first-rule-lj/navigate-to-form/manifest.json index f22be143..2beba10f 100644 --- a/alerting-first-rule-lj/navigate-to-form/manifest.json +++ b/alerting-first-rule-lj/navigate-to-form/manifest.json @@ -1,7 +1,7 @@ { "id": "alerting-first-rule-navigate-to-form", "type": "guide", - "description": "Open the alert rule creation form and give your rule a clear name.", + "description": "Open the new alert rule page and give your rule a clear name.", "category": "take-action", "author": { "name": "JohnnyK-Grafana", diff --git a/alerting-first-rule-lj/navigate-to-form/website.yaml b/alerting-first-rule-lj/navigate-to-form/website.yaml index a93e52a2..81d08193 100644 --- a/alerting-first-rule-lj/navigate-to-form/website.yaml +++ b/alerting-first-rule-lj/navigate-to-form/website.yaml @@ -1,6 +1,6 @@ menuTitle: Name your rule -title: Navigate to the alert rule form -description: Open the alert rule creation form and give your rule a clear name. +title: Open the new alert rule page +description: Open the new alert rule page and give your rule a clear name. keywords: - alerting - alert rule From 3bba6b1d4f02070d2ba29bbefb4fc71ae2b67fc1 Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Mon, 27 Jul 2026 11:40:05 -0500 Subject: [PATCH 03/19] alerting-first-rule-lj: rework query to watch QuickPizza 5xx errors Rewrites the build-query milestone to build sum(increase(quickpizza_server_http_requests_total{status=~"5.."}[5m])) in Builder mode, with values pre-filled for the learner where the picker allows. Adds optional Sum and Explain sections. Updates set-condition threshold from 1 (req/s) to 0 (any errors), removing the 'traffic surge' framing. Adjusts configure-labels-evaluation and save-rule copy so it no longer references the removed service_name=quickpizza-public-api filter; the save-rule summary annotation now uses {{ $value }} since Sum aggregates labels away. --- .../build-query/content.json | 130 ++++++++++++++---- .../configure-labels-evaluation/content.json | 6 +- alerting-first-rule-lj/save-rule/content.json | 2 +- .../set-condition/content.json | 17 ++- 4 files changed, 113 insertions(+), 42 deletions(-) diff --git a/alerting-first-rule-lj/build-query/content.json b/alerting-first-rule-lj/build-query/content.json index 88ff5e45..082ec2d0 100644 --- a/alerting-first-rule-lj/build-query/content.json +++ b/alerting-first-rule-lj/build-query/content.json @@ -5,7 +5,7 @@ "blocks": [ { "type": "markdown", - "content": "The query defines what data Grafana monitors. You point the rule at a data source, choose a metric, filter it down to what matters, and apply operations that transform the raw data into something useful.\n\nThis milestone uses the **grafanacloud-play-prom** Prometheus data source from the QuickPizza tutorial data sources. You build the query in **Builder** mode, targeting a real HTTP request counter from the QuickPizza demo service." + "content": "This alert watches the QuickPizza app for server errors and notifies you as soon as any occur.\n\nYou build the query in **Builder** mode against the **grafanacloud-play-prom** Prometheus data source. The finished query counts how many QuickPizza HTTP requests returned a `5xx` status code in the last 5 minutes, sums them across all services, and sets you up to fire the moment that total goes above zero — so you find out the instant QuickPizza starts failing requests, no matter which service is responsible." }, { "type": "section", @@ -17,19 +17,18 @@ "blocks": [ { "type": "markdown", - "content": "You'll point the alert rule at the Prometheus data source that came with the QuickPizza tutorial data sources." + "content": "First, point the alert rule at the Prometheus data source from the QuickPizza tutorial data sources." }, { "type": "interactive", "action": "formfill", "reftarget": "input[data-testid='data-testid Select a data source']", "targetvalue": "grafanacloud-play-prom", - "content": "In the **Define query and alert condition** section, open the data source picker and select **grafanacloud-play-prom**.", - "tooltip": "Pick the Prometheus data source from the QuickPizza tutorial data sources.", + "content": "In the **Define query and alert condition** section, the data source picker is filled with **grafanacloud-play-prom** for you.", + "tooltip": "Prometheus data source from the QuickPizza tutorial data sources.", "requirements": [ "on-page:/alerting/new/alerting" - ], - "doIt": false + ] } ] }, @@ -43,7 +42,7 @@ "blocks": [ { "type": "markdown", - "content": "The Prometheus query editor has two modes: **Builder** (visual, point-and-click) and **Code** (write PromQL by hand). Builder is the fastest way in when you're new to Prometheus, and it's the default for new queries." + "content": "**Builder** mode lets you construct the query by clicking dropdowns and menus instead of typing PromQL. It's the default for new queries." }, { "type": "interactive", @@ -68,74 +67,147 @@ "blocks": [ { "type": "markdown", - "content": "The **Metric** field is where you tell Prometheus which time series to query. QuickPizza exposes an HTTP request counter you can alert on." + "content": "The **Metric** field tells Prometheus which time series to query. QuickPizza exposes a counter for every HTTP request its services have handled, broken down by status code — that's the raw material this alert is built from." }, { "type": "interactive", - "action": "highlight", - "reftarget": "button[aria-label='Open metrics explorer']", - "content": "Click the **Metric** drop-down and select `quickpizza_server_http_request_duration_seconds_count`.\n\nIf you can't find the Metric from the dropdown, Click the **Open metric explorer** button and search for the metric.\n\nThis is a counter that increments every time QuickPizza handles an HTTP request. It's exactly the kind of counter you'd alert on in production.", + "action": "formfill", + "reftarget": "[data-testid='data-testid metric select'] input", + "targetvalue": "quickpizza_server_http_requests_total", + "content": "In the **Metric** drop-down, the value is set to `quickpizza_server_http_requests_total` for you.\n\nThis counter increments every time one of QuickPizza's services — catalog, config, copy, public-api, recommendation — handles an HTTP request. Each series is labeled with the response `status`, which the next step filters on.", + "tooltip": "Counter of QuickPizza HTTP requests, broken down by status code.", "requirements": [ "on-page:/alerting/new/alerting" - ], - "doIt": false + ] } ] }, { "type": "section", "id": "add-label-filter", - "title": "Narrow down with a label filter", + "title": "Filter to server errors", "requirements": [ "section-completed:choose-metric" ], "blocks": [ { "type": "markdown", - "content": "QuickPizza has multiple services. A label filter narrows the query to just the one you care about — the public API, which handles customer traffic." + "content": "The raw metric covers *all* requests — 200s, 400s, 500s. This filter narrows the query to server errors only, using a regex match on the `status` label so it catches every 5xx code at once (500, 502, 503, and so on)." }, { "type": "interactive", - "action": "highlight", - "reftarget": "[data-testid='data-testid Select label']", - "content": "Below the metric picker, in the **Label filters** row, open the **Label** drop-down and select `service_name`.", + "action": "formfill", + "reftarget": "[data-testid='data-testid Select label'] input", + "targetvalue": "status", + "content": "In the **Label filters** row, the **Label** field is set to `status` for you.", "requirements": [ "on-page:/alerting/new/alerting" - ], - "doIt": false + ] }, { "type": "interactive", "action": "highlight", - "reftarget": "[data-testid='data-testid Select value']", - "content": "In the value picker next to `service_name`, select `quickpizza-public-api`.\n\nYour query now returns only the HTTP request counter for QuickPizza's public API.", + "reftarget": "[data-testid='data-testid Select operator']", + "content": "Change the operator from `=` to `=~`. The `=~` operator does a regex match, which is how you catch every 5xx code at once instead of one specific status.", + "tooltip": "=~ is Prometheus regex-match; = is exact-match.", "requirements": [ "on-page:/alerting/new/alerting" ], + "skippable": true, "doIt": false + }, + { + "type": "interactive", + "action": "formfill", + "reftarget": "[data-testid='data-testid Select value'] input", + "targetvalue": "5..", + "content": "In the value field, the value is set to `5..` for you.\n\nThat regex matches any three-digit status starting with 5 (500, 502, 503, 504, and so on). Your query now returns only server errors.", + "requirements": [ + "on-page:/alerting/new/alerting" + ] } ] }, { "type": "section", - "id": "add-rate-operation", - "title": "Convert the counter to a rate", + "id": "add-increase-operation", + "title": "Add Increase (5m)", "requirements": [ "section-completed:add-label-filter" ], "blocks": [ { "type": "markdown", - "content": "Counters only ever go up, so the raw value isn't useful for alerting. Applying **Rate** turns the counter into a per-second rate — how many requests QuickPizza is currently serving. That's what you actually want to compare against a threshold." + "content": "The raw metric is a *counter* — it only ever goes up and never resets, so its current value (like 48,213) tells you nothing about whether errors just happened. `increase()` converts that into \"how many new errors occurred in the last 5 minutes\" — a number you can actually alert on." }, { "type": "interactive", "action": "button", "reftarget": "Operations", - "content": "Click **+ Operations** to add a function to your query. In the picker, select **Range > Rate** and `$__rate_interval`.\n\nGrafana wraps your query with `rate(...[$__rate_interval])` under the hood — but you never touched PromQL.", + "content": "Click **+ Operations** to open the operations picker.", + "requirements": [ + "on-page:/alerting/new/alerting" + ] + }, + { + "type": "interactive", + "action": "noop", + "content": "In the picker, select **Range functions**, then **Increase**. Grafana adds an `Increase` step to your query.\n\nIn the step's range field, set the range to `5m` so the query counts errors over the last 5 minutes." + } + ] + }, + { + "type": "section", + "id": "add-sum-operation", + "title": "Sum across services (optional)", + "requirements": [ + "section-completed:add-increase-operation" + ], + "skippable": true, + "blocks": [ + { + "type": "markdown", + "content": "Without `Sum`, you get one separate number per QuickPizza service, since each runs on its own instance. Summing collapses them into a single total, so the alert answers \"is QuickPizza having errors *anywhere*?\" instead of firing separately per service — quieter and easier to read." + }, + { + "type": "interactive", + "action": "button", + "reftarget": "Operations", + "content": "Click **+ Operations** again to open the picker.", "requirements": [ "on-page:/alerting/new/alerting" ], + "skippable": true + }, + { + "type": "interactive", + "action": "noop", + "content": "Select **Aggregations**, then **Sum**. Grafana adds a `Sum` step that wraps your `increase()` result in a single total across all services." + } + ] + }, + { + "type": "section", + "id": "toggle-explain", + "title": "Turn on Explain (optional)", + "requirements": [ + "section-completed:add-sum-operation" + ], + "skippable": true, + "blocks": [ + { + "type": "markdown", + "content": "**Explain** makes Grafana narrate what each part of your query does in plain English as you build it — a fast way to learn what you're clicking without having to know PromQL syntax upfront." + }, + { + "type": "interactive", + "action": "highlight", + "reftarget": "label[for='option-explain-radiogroup']", + "content": "Toggle **Explain** on. Grafana adds inline descriptions to each step of your query.", + "requirements": [ + "on-page:/alerting/new/alerting" + ], + "skippable": true, "doIt": false } ] @@ -145,21 +217,21 @@ "id": "preview-query", "title": "Preview the query", "requirements": [ - "section-completed:add-rate-operation" + "section-completed:toggle-explain" ], "blocks": [ { "type": "interactive", "action": "button", "reftarget": "Run queries", - "content": "Click **Run queries** to preview the data your query returns.\n\nYou should see a line that fluctuates in the low single digits — the real-time HTTP request rate for QuickPizza's public API. If the preview is empty, double-check that you selected `grafanacloud-play-prom` and the metric name is correct.", + "content": "Click **Run queries** to preview the data.\n\nYou should see a value that stays at `0` while QuickPizza is healthy and jumps above zero when server errors occur in the last 5-minute window. If the preview is empty, double-check the data source, metric name, and label filter.", "requirements": [ "on-page:/alerting/new/alerting" ] }, { "type": "markdown", - "content": "Your query now returns a real per-second request rate from QuickPizza. In the next milestone, you set the condition that decides when this rate is high enough to fire an alert." + "content": "Your query now counts QuickPizza's 5xx errors over the last 5 minutes. In the next milestone, you set the condition that fires the alert the moment that count goes above zero." } ] } diff --git a/alerting-first-rule-lj/configure-labels-evaluation/content.json b/alerting-first-rule-lj/configure-labels-evaluation/content.json index be1466c0..f9bcbdaf 100644 --- a/alerting-first-rule-lj/configure-labels-evaluation/content.json +++ b/alerting-first-rule-lj/configure-labels-evaluation/content.json @@ -30,7 +30,7 @@ "blocks": [ { "type": "markdown", - "content": "Labels can come from two places: the query itself (your QuickPizza query already carries `service_name`, `service_namespace`, and other labels straight from the Prometheus metric) or the rule-level **Add labels** section. Use rule-level labels for higher-level routing metadata that doesn't belong on the data, like team, severity, or environment." + "content": "Labels can come from two places: the query itself (the raw QuickPizza metric carries `service_name`, `service_namespace`, and `status` labels straight from Prometheus, though the `Sum` step aggregates them away) or the rule-level **Add labels** section. Use rule-level labels for higher-level routing metadata that doesn't belong on the data, like team, severity, or environment." }, { "type": "interactive", @@ -42,12 +42,12 @@ { "type": "interactive", "action": "noop", - "content": "Your query already carries `service_name=\"quickpizza-public-api\"` and `service_namespace=\"quickpizza\"` from the metric. Add higher-level labels here to control routing without touching the query.\n\n| Key | Example values | Purpose |\n|-----|----------------|--------|\n| `severity` | `critical`, `warning`, `info` | Controls urgency and routing |\n| `team` | `platform`, `database`, `frontend` | Routes to the right responders |\n| `environment` | `production`, `staging` | Distinguishes alert context |\n| `runbook` | `payments-runbook` | Points on-call to a runbook |" + "content": "Rule-level labels give you routing metadata that lives on the alert regardless of what the query returns. Add them here to control how notifications are dispatched without touching the query.\n\n| Key | Example values | Purpose |\n|-----|----------------|--------|\n| `severity` | `critical`, `warning`, `info` | Controls urgency and routing |\n| `team` | `platform`, `database`, `frontend` | Routes to the right responders |\n| `environment` | `production`, `staging` | Distinguishes alert context |\n| `runbook` | `payments-runbook` | Points on-call to a runbook |" }, { "type": "interactive", "action": "noop", - "content": "Labels drive notification routing. For example, notification policies can:\n\n- Route `severity=\"critical\"` to PagerDuty for immediate response\n- Route `severity=\"warning\"` to a Slack channel for awareness\n- Route `team=\"platform\"` to the platform team's on-call\n- Route by `service_name` (already on the query) to service owners\n\nChoose labels that match your notification policy matchers." + "content": "Labels drive notification routing. For example, notification policies can:\n\n- Route `severity=\"critical\"` to PagerDuty for immediate response\n- Route `severity=\"warning\"` to a Slack channel for awareness\n- Route `team=\"platform\"` to the platform team's on-call\n- Route by service or environment labels to service owners\n\nChoose labels that match your notification policy matchers." }, { "type": "markdown", diff --git a/alerting-first-rule-lj/save-rule/content.json b/alerting-first-rule-lj/save-rule/content.json index c1d7640b..81d3f118 100644 --- a/alerting-first-rule-lj/save-rule/content.json +++ b/alerting-first-rule-lj/save-rule/content.json @@ -17,7 +17,7 @@ "type": "interactive", "action": "highlight", "reftarget": "[data-testid='annotation-value-0']", - "content": "In the **Configure notification message** section, locate the **Summary** field. The summary is the first line responders see in Slack, PagerDuty, email, and other channels. Keep it brief and use template variables to reference labels from your query.\n\nFor the QuickPizza alert you built, use:\n\n```\nHigh request rate on {{ $labels.service_name }}\n```\n\nOther examples for real alerts:\n\n| If alerting on | Example summary |\n|----------------|------------------|\n| CPU usage | `CPU usage is high on {{ $labels.instance }}` |\n| Error rate | `Error rate elevated for {{ $labels.job }}` |\n| Disk space | `Disk space low on {{ $labels.instance }}` |", + "content": "In the **Configure notification message** section, locate the **Summary** field. The summary is the first line responders see in Slack, PagerDuty, email, and other channels. Keep it brief and use template variables to reference labels or values from your query.\n\nFor the QuickPizza alert you built, use:\n\n```\nQuickPizza returned {{ $value }} server errors in the last 5 minutes\n```\n\nOther examples for real alerts:\n\n| If alerting on | Example summary |\n|----------------|------------------|\n| CPU usage | `CPU usage is high on {{ $labels.instance }}` |\n| Error rate | `Error rate elevated for {{ $labels.job }}` |\n| Disk space | `Disk space low on {{ $labels.instance }}` |", "doIt": false, "requirements": ["on-page:/alerting/new/alerting"] } diff --git a/alerting-first-rule-lj/set-condition/content.json b/alerting-first-rule-lj/set-condition/content.json index eb3d8c6f..096c04b6 100644 --- a/alerting-first-rule-lj/set-condition/content.json +++ b/alerting-first-rule-lj/set-condition/content.json @@ -5,7 +5,7 @@ "blocks": [ { "type": "markdown", - "content": "You'll configure the condition that determines when your alert fires using the simplified alert condition editor.\n\nIn this milestone, you set a comparison operator and threshold that fire when QuickPizza's request rate exceeds a target value, then preview the result." + "content": "You'll configure the condition that determines when your alert fires using the simplified alert condition editor.\n\nIn this milestone, you set a comparison operator and threshold that fire the moment QuickPizza returns a server error, then preview the result." }, { "type": "section", @@ -17,13 +17,13 @@ "type": "interactive", "action": "highlight", "reftarget": "[data-testid='data-testid alert-rule step-2'] button:contains('Is above')", - "content": "Below the query editor, locate the **Alert condition** area. It reads \"WHEN QUERY\" followed by a condition dropdown.\n\nLeave the operator on **Is above**. You want the alert to fire when QuickPizza's request rate climbs above your target — the classic \"traffic surge\" pattern.", + "content": "Below the query editor, locate the **Alert condition** area. It reads \"WHEN QUERY\" followed by a condition dropdown.\n\nLeave the operator on **Is above**. Your query returns the number of QuickPizza 5xx errors in the last 5 minutes, and you want to fire the moment that count climbs above zero.", "doIt": false, "requirements": ["on-page:/alerting/new/alerting"] }, { "type": "markdown", - "content": "> **Other operators.** For real alerts, you might pick a different operator: `Is below` for low disk space or memory, `Is equal to` for exact matches, or `Is within range` and `Is outside range` for band checks. For this course, `Is above` matches the traffic-surge pattern." + "content": "> **Other operators.** For real alerts, you might pick a different operator: `Is below` for low disk space or memory, `Is equal to` for exact matches, or `Is within range` and `Is outside range` for band checks. For this alert, `Is above` paired with a threshold of `0` is the simplest \"anything broken?\" trigger." } ] }, @@ -37,26 +37,25 @@ "type": "interactive", "action": "formfill", "reftarget": "[data-testid='data-testid alert-rule step-2'] input[type='number']", - "targetvalue": "1", - "content": "In the number field next to the condition operator, enter `1`.\n\nThat threshold represents one request per second. QuickPizza's public API typically serves a low, fluctuating rate of demo traffic, so a threshold around `1` will fire during busy periods and resolve during quiet ones — a useful pattern to watch in real time.", - "doIt": false, + "targetvalue": "0", + "content": "In the number field next to the condition operator, the threshold is set to `0` for you.\n\nAfter the `increase()` step, your query returns the number of QuickPizza 5xx errors in the last 5 minutes. Any value above zero means at least one error happened — the simplest, most direct trigger for \"something broke.\"", "requirements": ["on-page:/alerting/new/alerting"] }, { "type": "interactive", "action": "button", "reftarget": "Preview alert rule condition", - "content": "Click **Preview alert rule condition** to test the threshold against your query.\n\nThe preview shows a firing state during traffic spikes above `1` req/s and a normal state during quieter periods.", + "content": "Click **Preview alert rule condition** to test the threshold against your query.\n\nThe preview shows a normal state while QuickPizza is healthy and a firing state as soon as any 5xx error appears in the 5-minute window.", "requirements": ["on-page:/alerting/new/alerting"] }, { "type": "interactive", "action": "noop", - "content": "For real-world alerts, pick a threshold that gives you time to respond before things break — for example, `80` for CPU percentage, `10` for errors per minute, or a p95 latency ceiling for a user-facing endpoint.\n\nFor more control over how the condition is evaluated, enable **Advanced options** at the top of this section to access the full Reduce and Threshold expression editors." + "content": "For real-world alerts, pick a threshold that gives you time to respond before things break — for example, `80` for CPU percentage, `10` for errors per minute, or a p95 latency ceiling for a user-facing endpoint. Raising this alert's threshold above `0` is one way to only page on sustained or repeated failures rather than a single blip.\n\nFor more control over how the condition is evaluated, enable **Advanced options** at the top of this section to access the full Reduce and Threshold expression editors." }, { "type": "markdown", - "content": "Your alert condition is configured. Grafana evaluates the threshold at every interval and fires the alert when QuickPizza's request rate exceeds `1` req/s.\n\nIn the next milestone, you organize your rule with labels and control evaluation timing." + "content": "Your alert condition is configured. Grafana evaluates the query at every interval and fires the alert as soon as QuickPizza returns any 5xx error.\n\nIn the next milestone, you organize your rule with labels and control evaluation timing." } ] } From 2feb7c26121bd8fed86b46605ac7cbb4a41760a1 Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Mon, 27 Jul 2026 11:44:12 -0500 Subject: [PATCH 04/19] alerting-first-rule-lj: simplify build-query intro Breaks the dense opening paragraph into a short lede, a plain-language bullet list of what the learner will do, and a one-sentence payoff. --- alerting-first-rule-lj/build-query/content.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alerting-first-rule-lj/build-query/content.json b/alerting-first-rule-lj/build-query/content.json index 082ec2d0..f4c404da 100644 --- a/alerting-first-rule-lj/build-query/content.json +++ b/alerting-first-rule-lj/build-query/content.json @@ -5,7 +5,7 @@ "blocks": [ { "type": "markdown", - "content": "This alert watches the QuickPizza app for server errors and notifies you as soon as any occur.\n\nYou build the query in **Builder** mode against the **grafanacloud-play-prom** Prometheus data source. The finished query counts how many QuickPizza HTTP requests returned a `5xx` status code in the last 5 minutes, sums them across all services, and sets you up to fire the moment that total goes above zero — so you find out the instant QuickPizza starts failing requests, no matter which service is responsible." + "content": "This alert watches the QuickPizza app for server errors and pings you the moment any happen.\n\nYou'll build it in **Builder** mode — clicking dropdowns instead of typing PromQL. In a few steps you'll:\n\n- Pick the request-count metric from QuickPizza\n- Filter it down to `5xx` (server error) responses\n- Ask \"how many happened in the last 5 minutes?\"\n- Sum across QuickPizza's services so one alert covers everything\n\nThe finished query fires the moment a server error shows up — no matter which QuickPizza service is responsible." }, { "type": "section", From 60672a11f33c4e87e581454aaa27bbe221fdeb87 Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Mon, 27 Jul 2026 11:55:18 -0500 Subject: [PATCH 05/19] alerting-first-rule-lj: collapse section intros and pre-fill alert name build-query: folds each section's lead markdown block into the interactive step's content so every numbered step has an action attached, preventing lonely numbered sentences when requirements gate the interactive block. navigate-to-form: pre-fills the alert rule name with 'QuickPizza server errors' via formfill, matching the query built in the next milestone. --- .../build-query/content.json | 54 +++++-------------- .../navigate-to-form/content.json | 6 +-- 2 files changed, 16 insertions(+), 44 deletions(-) diff --git a/alerting-first-rule-lj/build-query/content.json b/alerting-first-rule-lj/build-query/content.json index f4c404da..985b67ef 100644 --- a/alerting-first-rule-lj/build-query/content.json +++ b/alerting-first-rule-lj/build-query/content.json @@ -5,7 +5,7 @@ "blocks": [ { "type": "markdown", - "content": "This alert watches the QuickPizza app for server errors and pings you the moment any happen.\n\nYou'll build it in **Builder** mode — clicking dropdowns instead of typing PromQL. In a few steps you'll:\n\n- Pick the request-count metric from QuickPizza\n- Filter it down to `5xx` (server error) responses\n- Ask \"how many happened in the last 5 minutes?\"\n- Sum across QuickPizza's services so one alert covers everything\n\nThe finished query fires the moment a server error shows up — no matter which QuickPizza service is responsible." + "content": "You'll build an alert that watches the QuickPizza app for server errors. You'll use Builder mode instead of using a query language." }, { "type": "section", @@ -15,16 +15,12 @@ "has-datasource:grafanacloud-play-prom" ], "blocks": [ - { - "type": "markdown", - "content": "First, point the alert rule at the Prometheus data source from the QuickPizza tutorial data sources." - }, { "type": "interactive", "action": "formfill", "reftarget": "input[data-testid='data-testid Select a data source']", "targetvalue": "grafanacloud-play-prom", - "content": "In the **Define query and alert condition** section, the data source picker is filled with **grafanacloud-play-prom** for you.", + "content": "Point the alert rule at the **grafanacloud-play-prom** data source from the QuickPizza tutorial data sources. It's filled in for you.", "tooltip": "Prometheus data source from the QuickPizza tutorial data sources.", "requirements": [ "on-page:/alerting/new/alerting" @@ -40,15 +36,11 @@ "section-completed:select-data-source" ], "blocks": [ - { - "type": "markdown", - "content": "**Builder** mode lets you construct the query by clicking dropdowns and menus instead of typing PromQL. It's the default for new queries." - }, { "type": "interactive", "action": "highlight", "reftarget": "div[data-testid='data-testid QueryEditorModeToggle']", - "content": "Above the query field, confirm **Builder** is selected. If you're on **Code**, click **Builder** to switch.", + "content": "**Builder** mode lets you construct the query by clicking dropdowns instead of typing PromQL. It's the default for new queries — if **Code** is selected, click **Builder** to switch.", "requirements": [ "on-page:/alerting/new/alerting" ], @@ -65,16 +57,12 @@ "section-completed:use-builder-mode" ], "blocks": [ - { - "type": "markdown", - "content": "The **Metric** field tells Prometheus which time series to query. QuickPizza exposes a counter for every HTTP request its services have handled, broken down by status code — that's the raw material this alert is built from." - }, { "type": "interactive", "action": "formfill", "reftarget": "[data-testid='data-testid metric select'] input", "targetvalue": "quickpizza_server_http_requests_total", - "content": "In the **Metric** drop-down, the value is set to `quickpizza_server_http_requests_total` for you.\n\nThis counter increments every time one of QuickPizza's services — catalog, config, copy, public-api, recommendation — handles an HTTP request. Each series is labeled with the response `status`, which the next step filters on.", + "content": "In the **Metric** drop-down, `quickpizza_server_http_requests_total` is filled in for you. This counter tracks every HTTP request QuickPizza's services handle, labeled with the response `status` — the raw material for the alert.", "tooltip": "Counter of QuickPizza HTTP requests, broken down by status code.", "requirements": [ "on-page:/alerting/new/alerting" @@ -90,16 +78,12 @@ "section-completed:choose-metric" ], "blocks": [ - { - "type": "markdown", - "content": "The raw metric covers *all* requests — 200s, 400s, 500s. This filter narrows the query to server errors only, using a regex match on the `status` label so it catches every 5xx code at once (500, 502, 503, and so on)." - }, { "type": "interactive", "action": "formfill", "reftarget": "[data-testid='data-testid Select label'] input", "targetvalue": "status", - "content": "In the **Label filters** row, the **Label** field is set to `status` for you.", + "content": "The raw metric covers *all* requests. To keep only server errors, add a **Label filter** on `status` — it's filled in for you.", "requirements": [ "on-page:/alerting/new/alerting" ] @@ -108,7 +92,7 @@ "type": "interactive", "action": "highlight", "reftarget": "[data-testid='data-testid Select operator']", - "content": "Change the operator from `=` to `=~`. The `=~` operator does a regex match, which is how you catch every 5xx code at once instead of one specific status.", + "content": "Change the operator from `=` to `=~`. The `=~` operator does a regex match, so one filter catches every 5xx code at once (500, 502, 503, and so on).", "tooltip": "=~ is Prometheus regex-match; = is exact-match.", "requirements": [ "on-page:/alerting/new/alerting" @@ -121,7 +105,7 @@ "action": "formfill", "reftarget": "[data-testid='data-testid Select value'] input", "targetvalue": "5..", - "content": "In the value field, the value is set to `5..` for you.\n\nThat regex matches any three-digit status starting with 5 (500, 502, 503, 504, and so on). Your query now returns only server errors.", + "content": "In the value field, `5..` is filled in for you. That regex matches any three-digit status starting with 5 — so the query now returns only server errors.", "requirements": [ "on-page:/alerting/new/alerting" ] @@ -136,15 +120,11 @@ "section-completed:add-label-filter" ], "blocks": [ - { - "type": "markdown", - "content": "The raw metric is a *counter* — it only ever goes up and never resets, so its current value (like 48,213) tells you nothing about whether errors just happened. `increase()` converts that into \"how many new errors occurred in the last 5 minutes\" — a number you can actually alert on." - }, { "type": "interactive", "action": "button", "reftarget": "Operations", - "content": "Click **+ Operations** to open the operations picker.", + "content": "The metric is a *counter* that only goes up, so its raw value doesn't tell you whether errors just happened. `increase()` fixes that. Click **+ Operations** to open the picker.", "requirements": [ "on-page:/alerting/new/alerting" ] @@ -152,7 +132,7 @@ { "type": "interactive", "action": "noop", - "content": "In the picker, select **Range functions**, then **Increase**. Grafana adds an `Increase` step to your query.\n\nIn the step's range field, set the range to `5m` so the query counts errors over the last 5 minutes." + "content": "Select **Range functions**, then **Increase**. Set the range to `5m` — the query now counts new errors in the last 5 minutes." } ] }, @@ -165,15 +145,11 @@ ], "skippable": true, "blocks": [ - { - "type": "markdown", - "content": "Without `Sum`, you get one separate number per QuickPizza service, since each runs on its own instance. Summing collapses them into a single total, so the alert answers \"is QuickPizza having errors *anywhere*?\" instead of firing separately per service — quieter and easier to read." - }, { "type": "interactive", "action": "button", "reftarget": "Operations", - "content": "Click **+ Operations** again to open the picker.", + "content": "Without `Sum`, you get one number per QuickPizza service — noisier, harder to read. Summing gives one total so the alert answers \"is QuickPizza having errors *anywhere*?\" Click **+ Operations** again to open the picker.", "requirements": [ "on-page:/alerting/new/alerting" ], @@ -182,7 +158,7 @@ { "type": "interactive", "action": "noop", - "content": "Select **Aggregations**, then **Sum**. Grafana adds a `Sum` step that wraps your `increase()` result in a single total across all services." + "content": "Select **Aggregations**, then **Sum**. Grafana wraps your `increase()` result in a single total across all services." } ] }, @@ -195,15 +171,11 @@ ], "skippable": true, "blocks": [ - { - "type": "markdown", - "content": "**Explain** makes Grafana narrate what each part of your query does in plain English as you build it — a fast way to learn what you're clicking without having to know PromQL syntax upfront." - }, { "type": "interactive", "action": "highlight", "reftarget": "label[for='option-explain-radiogroup']", - "content": "Toggle **Explain** on. Grafana adds inline descriptions to each step of your query.", + "content": "Toggle **Explain** on. Grafana narrates what each part of your query does in plain English — a fast way to learn what you're clicking without knowing PromQL syntax.", "requirements": [ "on-page:/alerting/new/alerting" ], @@ -224,7 +196,7 @@ "type": "interactive", "action": "button", "reftarget": "Run queries", - "content": "Click **Run queries** to preview the data.\n\nYou should see a value that stays at `0` while QuickPizza is healthy and jumps above zero when server errors occur in the last 5-minute window. If the preview is empty, double-check the data source, metric name, and label filter.", + "content": "Click **Run queries** to preview the data. You should see `0` while QuickPizza is healthy, jumping above zero when 5xx errors happen in the last 5-minute window. If the preview is empty, double-check the data source, metric name, and label filter.", "requirements": [ "on-page:/alerting/new/alerting" ] diff --git a/alerting-first-rule-lj/navigate-to-form/content.json b/alerting-first-rule-lj/navigate-to-form/content.json index ef11d9a9..80bfd6c7 100644 --- a/alerting-first-rule-lj/navigate-to-form/content.json +++ b/alerting-first-rule-lj/navigate-to-form/content.json @@ -45,11 +45,11 @@ }, { "type": "interactive", - "action": "highlight", + "action": "formfill", "reftarget": "input#name", - "content": "Enter a descriptive name in the **Alert rule name** field.\n\nA good alert name clearly identifies what's being monitored and what condition triggers the alert.", + "targetvalue": "QuickPizza server errors", + "content": "In the **Alert rule name** field, `QuickPizza server errors` is filled in for you. Responders see this name first when the alert fires — it clearly identifies what's being monitored and what triggered it.", "tooltip": "Responders see this name first when the alert fires.", - "doIt": false, "requirements": ["on-page:/alerting/new/alerting"] }, { From 9cffeeb506a2a7e97f4e5df504ba7155857d06bc Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Mon, 27 Jul 2026 14:55:42 -0500 Subject: [PATCH 06/19] alerting-first-rule-lj: tighten alert-name step copy Combines the intro markdown, why-it-matters sentence, and formfill instruction into a single interactive step, and removes the trailing summary markdown block. The section now renders as one numbered do-it step plus a next-milestone line. --- .../navigate-to-form/content.json | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/alerting-first-rule-lj/navigate-to-form/content.json b/alerting-first-rule-lj/navigate-to-form/content.json index 80bfd6c7..7ec67dc6 100644 --- a/alerting-first-rule-lj/navigate-to-form/content.json +++ b/alerting-first-rule-lj/navigate-to-form/content.json @@ -39,27 +39,18 @@ "title": "Set the alert rule name", "requirements": ["section-completed:open-alert-form"], "blocks": [ - { - "type": "markdown", - "content": "You'll enter a descriptive name for your alert rule that tells responders exactly what triggered it." - }, { "type": "interactive", "action": "formfill", "reftarget": "input#name", "targetvalue": "QuickPizza server errors", - "content": "In the **Alert rule name** field, `QuickPizza server errors` is filled in for you. Responders see this name first when the alert fires — it clearly identifies what's being monitored and what triggered it.", + "content": "Enter a descriptive name for your alert rule that tells responders triage alerts quickly, especially when multiple alerts fire at once. In the **Alert rule name** field, `QuickPizza server errors` is filled in for you.", "tooltip": "Responders see this name first when the alert fires.", "requirements": ["on-page:/alerting/new/alerting"] }, - { - "type": "interactive", - "action": "noop", - "content": "Use a naming pattern that includes the signal, the scope, and the condition:\n\n| Pattern | Example |\n|---------|--------|\n| Signal + scope | `High CPU usage - Linux servers` |\n| Signal + service | `Error rate spike - API gateway` |\n| Signal + threshold | `Disk space critical - Database servers` |\n| Log pattern + scope | `OOM errors - Production pods` |" - }, { "type": "markdown", - "content": "A descriptive name helps responders triage alerts quickly, especially when multiple alerts fire at once.\n\nIn the next milestone, you learn how to build the query that defines what your alert monitors." + "content": "In the next milestone, you learn how to build the query that defines what your alert monitors." } ] } From 5c85ce6c24c876089c2ec873ed60bff77da0f5a5 Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Mon, 27 Jul 2026 16:07:37 -0500 Subject: [PATCH 07/19] alerting-first-rule-lj: fix build-query selectors and step wording - operator: use [data-testid='data-testid Select match operator'] and switch to Show me only (no skip) - explain toggle: use [data-testid='data-testid prometheus explain switch wrapper'] and move section to right after data source - value: reword to 'In the Select value box, enter 5..' --- .../build-query/content.json | 99 ++++++++----------- 1 file changed, 39 insertions(+), 60 deletions(-) diff --git a/alerting-first-rule-lj/build-query/content.json b/alerting-first-rule-lj/build-query/content.json index 985b67ef..88575435 100644 --- a/alerting-first-rule-lj/build-query/content.json +++ b/alerting-first-rule-lj/build-query/content.json @@ -20,7 +20,7 @@ "action": "formfill", "reftarget": "input[data-testid='data-testid Select a data source']", "targetvalue": "grafanacloud-play-prom", - "content": "Point the alert rule at the **grafanacloud-play-prom** data source from the QuickPizza tutorial data sources. It's filled in for you.", + "content": "Choose the data source that holds the metric you want to alert on. For this tutorial, `grafanacloud-play-prom` is filled in for you — it's the Prometheus data source from the QuickPizza tutorial data sources.", "tooltip": "Prometheus data source from the QuickPizza tutorial data sources.", "requirements": [ "on-page:/alerting/new/alerting" @@ -28,19 +28,35 @@ } ] }, + { + "type": "section", + "id": "toggle-explain", + "title": "Turn on Explain (optional)", + "skippable": true, + "blocks": [ + { + "type": "interactive", + "action": "highlight", + "reftarget": "[data-testid='data-testid prometheus explain switch wrapper']", + "content": "Toggle **Explain** on. Grafana narrates what each part of your query does in plain English — a fast way to learn what you're clicking without knowing PromQL syntax.", + "requirements": [ + "on-page:/alerting/new/alerting" + ], + "skippable": true, + "doIt": false + } + ] + }, { "type": "section", "id": "use-builder-mode", "title": "Confirm Builder mode", - "requirements": [ - "section-completed:select-data-source" - ], "blocks": [ { "type": "interactive", "action": "highlight", "reftarget": "div[data-testid='data-testid QueryEditorModeToggle']", - "content": "**Builder** mode lets you construct the query by clicking dropdowns instead of typing PromQL. It's the default for new queries — if **Code** is selected, click **Builder** to switch.", + "content": "Make sure **Builder** mode is selected. This mode lets you build the query without using PromQL.", "requirements": [ "on-page:/alerting/new/alerting" ], @@ -53,17 +69,14 @@ "type": "section", "id": "choose-metric", "title": "Choose the metric", - "requirements": [ - "section-completed:use-builder-mode" - ], "blocks": [ { "type": "interactive", - "action": "formfill", - "reftarget": "[data-testid='data-testid metric select'] input", - "targetvalue": "quickpizza_server_http_requests_total", - "content": "In the **Metric** drop-down, `quickpizza_server_http_requests_total` is filled in for you. This counter tracks every HTTP request QuickPizza's services handle, labeled with the response `status` — the raw material for the alert.", - "tooltip": "Counter of QuickPizza HTTP requests, broken down by status code.", + "action": "highlight", + "reftarget": "[data-testid='data-testid metric select']", + "content": "Copy `quickpizza_server_http_requests_total` from this step, paste it into the **Metric** drop-down, then select it from the list. This counter tracks every HTTP request QuickPizza's services handle, labeled with the response `status`. It's the raw material for the alert.", + "tooltip": "Paste the metric name here, then pick it from the list.", + "doIt": false, "requirements": [ "on-page:/alerting/new/alerting" ] @@ -74,16 +87,13 @@ "type": "section", "id": "add-label-filter", "title": "Filter to server errors", - "requirements": [ - "section-completed:choose-metric" - ], "blocks": [ { "type": "interactive", - "action": "formfill", - "reftarget": "[data-testid='data-testid Select label'] input", - "targetvalue": "status", - "content": "The raw metric covers *all* requests. To keep only server errors, add a **Label filter** on `status` — it's filled in for you.", + "action": "highlight", + "reftarget": "[data-testid='data-testid Select label']", + "content": "In the **Label filters** row, open the **Label** drop-down and select `status`. The raw metric covers *all* requests — the `status` label lets you narrow it down.", + "tooltip": "Pick status from the list.", "requirements": [ "on-page:/alerting/new/alerting" ] @@ -91,21 +101,21 @@ { "type": "interactive", "action": "highlight", - "reftarget": "[data-testid='data-testid Select operator']", - "content": "Change the operator from `=` to `=~`. The `=~` operator does a regex match, so one filter catches every 5xx code at once (500, 502, 503, and so on).", + "reftarget": "[data-testid='data-testid Select match operator']", + "content": "Open the operator drop-down and change `=` to `=~`. The `=~` operator does a regex match, so one filter catches every 5xx code at once (500, 502, 503, and so on).", "tooltip": "=~ is Prometheus regex-match; = is exact-match.", + "doIt": false, "requirements": [ "on-page:/alerting/new/alerting" - ], - "skippable": true, - "doIt": false + ] }, { "type": "interactive", - "action": "formfill", - "reftarget": "[data-testid='data-testid Select value'] input", - "targetvalue": "5..", - "content": "In the value field, `5..` is filled in for you. That regex matches any three-digit status starting with 5 — so the query now returns only server errors.", + "action": "highlight", + "reftarget": "[aria-label='Value']", + "content": "In the **Select value** box, enter `5..`, then press Enter. That regex matches any three-digit status starting with 5, so the query now returns only server errors.", + "tooltip": "Enter 5.. and press Enter.", + "doIt": false, "requirements": [ "on-page:/alerting/new/alerting" ] @@ -116,9 +126,6 @@ "type": "section", "id": "add-increase-operation", "title": "Add Increase (5m)", - "requirements": [ - "section-completed:add-label-filter" - ], "blocks": [ { "type": "interactive", @@ -140,9 +147,6 @@ "type": "section", "id": "add-sum-operation", "title": "Sum across services (optional)", - "requirements": [ - "section-completed:add-increase-operation" - ], "skippable": true, "blocks": [ { @@ -162,35 +166,10 @@ } ] }, - { - "type": "section", - "id": "toggle-explain", - "title": "Turn on Explain (optional)", - "requirements": [ - "section-completed:add-sum-operation" - ], - "skippable": true, - "blocks": [ - { - "type": "interactive", - "action": "highlight", - "reftarget": "label[for='option-explain-radiogroup']", - "content": "Toggle **Explain** on. Grafana narrates what each part of your query does in plain English — a fast way to learn what you're clicking without knowing PromQL syntax.", - "requirements": [ - "on-page:/alerting/new/alerting" - ], - "skippable": true, - "doIt": false - } - ] - }, { "type": "section", "id": "preview-query", "title": "Preview the query", - "requirements": [ - "section-completed:toggle-explain" - ], "blocks": [ { "type": "interactive", From 82609fdc2358d455303c6035f8ccff02dc0fc885 Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Mon, 27 Jul 2026 17:57:05 -0500 Subject: [PATCH 08/19] alerting-first-rule-lj: use stable Select value testid in build-query --- alerting-first-rule-lj/build-query/content.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alerting-first-rule-lj/build-query/content.json b/alerting-first-rule-lj/build-query/content.json index 88575435..7c4dca8a 100644 --- a/alerting-first-rule-lj/build-query/content.json +++ b/alerting-first-rule-lj/build-query/content.json @@ -112,7 +112,7 @@ { "type": "interactive", "action": "highlight", - "reftarget": "[aria-label='Value']", + "reftarget": "[data-testid='data-testid Select value']", "content": "In the **Select value** box, enter `5..`, then press Enter. That regex matches any three-digit status starting with 5, so the query now returns only server errors.", "tooltip": "Enter 5.. and press Enter.", "doIt": false, From 2b70e75d35faf535c9f3fb7dd44bf2097411f16f Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Mon, 27 Jul 2026 17:57:05 -0500 Subject: [PATCH 09/19] alerting-first-rule-lj: rework set-condition milestone - Retitle sections to 'Choose when the alert fires' and 'Preview rule' - Highlight the Alert condition area via WHEN QUERY label - Highlight the Is above button scoped under alert-rule step-2 - Drop jargon (comparison operator, threshold), use plain language - Remove redundant markdown blocks and repetition - Fold wrap-up into the final noop --- .../set-condition/content.json | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/alerting-first-rule-lj/set-condition/content.json b/alerting-first-rule-lj/set-condition/content.json index 096c04b6..9391cbc4 100644 --- a/alerting-first-rule-lj/set-condition/content.json +++ b/alerting-first-rule-lj/set-condition/content.json @@ -5,57 +5,49 @@ "blocks": [ { "type": "markdown", - "content": "You'll configure the condition that determines when your alert fires using the simplified alert condition editor.\n\nIn this milestone, you set a comparison operator and threshold that fire the moment QuickPizza returns a server error, then preview the result." + "content": "You'll set the rule that decides when your alert fires: which comparison to make, and what value to compare against.\n\nYour query counts QuickPizza's 5xx errors in the last 5 minutes. In this milestone, you tell Grafana to fire the moment that count goes above zero, then preview the result." }, { "type": "section", "id": "choose-operator", - "title": "Choose the comparison operator", + "title": "Choose when the alert fires", "requirements": ["has-datasource:grafanacloud-play-prom"], "blocks": [ { "type": "interactive", "action": "highlight", - "reftarget": "[data-testid='data-testid alert-rule step-2'] button:contains('Is above')", - "content": "Below the query editor, locate the **Alert condition** area. It reads \"WHEN QUERY\" followed by a condition dropdown.\n\nLeave the operator on **Is above**. Your query returns the number of QuickPizza 5xx errors in the last 5 minutes, and you want to fire the moment that count climbs above zero.", + "reftarget": "label:contains('WHEN QUERY')", + "content": "Notice the **Alert condition** section. This is where you tell Grafana when to fire the alert.", "doIt": false, "requirements": ["on-page:/alerting/new/alerting"] }, { - "type": "markdown", - "content": "> **Other operators.** For real alerts, you might pick a different operator: `Is below` for low disk space or memory, `Is equal to` for exact matches, or `Is within range` and `Is outside range` for band checks. For this alert, `Is above` paired with a threshold of `0` is the simplest \"anything broken?\" trigger." + "type": "interactive", + "action": "highlight", + "reftarget": "[data-testid='data-testid alert-rule step-2'] button:contains('Is above')", + "content": "**Is above** is already selected. Leave it — any 5xx error in the last 5 minutes puts your query result above zero, which fires the alert.", + "doIt": false, + "requirements": ["on-page:/alerting/new/alerting"] } ] }, { "type": "section", "id": "set-threshold", - "title": "Set the threshold value", + "title": "Preview rule", "requirements": ["section-completed:choose-operator"], "blocks": [ - { - "type": "interactive", - "action": "formfill", - "reftarget": "[data-testid='data-testid alert-rule step-2'] input[type='number']", - "targetvalue": "0", - "content": "In the number field next to the condition operator, the threshold is set to `0` for you.\n\nAfter the `increase()` step, your query returns the number of QuickPizza 5xx errors in the last 5 minutes. Any value above zero means at least one error happened — the simplest, most direct trigger for \"something broke.\"", - "requirements": ["on-page:/alerting/new/alerting"] - }, { "type": "interactive", "action": "button", "reftarget": "Preview alert rule condition", - "content": "Click **Preview alert rule condition** to test the threshold against your query.\n\nThe preview shows a normal state while QuickPizza is healthy and a firing state as soon as any 5xx error appears in the 5-minute window.", + "content": "Click **Preview alert rule condition** to test the value against your query.\n\nThe preview shows a normal state while QuickPizza is healthy and a firing state as soon as any 5xx error appears in the 5-minute window.", "requirements": ["on-page:/alerting/new/alerting"] }, { "type": "interactive", "action": "noop", - "content": "For real-world alerts, pick a threshold that gives you time to respond before things break — for example, `80` for CPU percentage, `10` for errors per minute, or a p95 latency ceiling for a user-facing endpoint. Raising this alert's threshold above `0` is one way to only page on sustained or repeated failures rather than a single blip.\n\nFor more control over how the condition is evaluated, enable **Advanced options** at the top of this section to access the full Reduce and Threshold expression editors." - }, - { - "type": "markdown", - "content": "Your alert condition is configured. Grafana evaluates the query at every interval and fires the alert as soon as QuickPizza returns any 5xx error.\n\nIn the next milestone, you organize your rule with labels and control evaluation timing." + "content": "Your alert condition is set. Next, you organize the rule with labels and control how often Grafana evaluates it." } ] } From fee718226a69b199b1c4784ab90af44f6bd70fb5 Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Mon, 27 Jul 2026 18:49:29 -0500 Subject: [PATCH 10/19] alerting-first-rule-lj: rework configure-labels-evaluation milestone - Retitle to 'Create a folder and add labels'; fix section reference to 'Add folder and labels' - Replace jargon-heavy intro (service_name, Sum step) with plain routing summary - Section 1: single create-folder path (New folder button + noop for name+Create) - Section 2: Add labels button, concrete severity=critical example, brief transition - Drop reference-dump markdown table and orphan wrap-up --- .../configure-labels-evaluation/content.json | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/alerting-first-rule-lj/configure-labels-evaluation/content.json b/alerting-first-rule-lj/configure-labels-evaluation/content.json index f9bcbdaf..37c06168 100644 --- a/alerting-first-rule-lj/configure-labels-evaluation/content.json +++ b/alerting-first-rule-lj/configure-labels-evaluation/content.json @@ -1,24 +1,29 @@ { "schemaVersion": "1.1.0", "id": "alerting-first-rule-configure-labels-evaluation", - "title": "Select a folder and add labels", + "title": "Create a folder and add labels", "blocks": [ { "type": "markdown", - "content": "You'll organize your alert into a folder and add labels that control routing.\n\nIn this milestone, you select a folder, then add rule-level labels." + "content": "You'll organize your alert into a folder and add labels that control how notifications get routed.\n\nFolders organize alerts by team, service, or purpose, and control who can manage them.\n\nIn this milestone, you create a folder, then add rule-level labels." }, { "type": "section", "id": "select-folder", - "title": "Select a folder", + "title": "Create a folder", "blocks": [ { "type": "interactive", "action": "button", - "reftarget": "Select folder", - "content": "In the **Set evaluation behavior** section, click **Select folder** to choose or create a folder for your alert rule.\n\nFolders organize alert rules by team, service, or purpose, and they determine who has permission to manage the rules inside them.", + "reftarget": "New folder", + "content": "In the **Add folder and labels** section, click **New folder** to open the create dialog.", "doIt": false, "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "interactive", + "action": "noop", + "content": "Give your folder a name \u2014 **Alerts** works for this tutorial, or pick something meaningful to your team \u2014 then click **Create**. Grafana saves the folder and selects it for your rule." } ] }, @@ -28,30 +33,22 @@ "title": "Add labels", "requirements": ["section-completed:select-folder"], "blocks": [ - { - "type": "markdown", - "content": "Labels can come from two places: the query itself (the raw QuickPizza metric carries `service_name`, `service_namespace`, and `status` labels straight from Prometheus, though the `Sum` step aggregates them away) or the rule-level **Add labels** section. Use rule-level labels for higher-level routing metadata that doesn't belong on the data, like team, severity, or environment." - }, { "type": "interactive", "action": "button", "reftarget": "Add labels", - "content": "Scroll to the **Add labels** section and click **Add labels** to add a rule-level label.", + "content": "Click **Add labels** to open the labels editor. Labels are simple key-value tags you attach to the alert. Grafana uses them to decide who to notify.", "requirements": ["on-page:/alerting/new/alerting"] }, { "type": "interactive", "action": "noop", - "content": "Rule-level labels give you routing metadata that lives on the alert regardless of what the query returns. Add them here to control how notifications are dispatched without touching the query.\n\n| Key | Example values | Purpose |\n|-----|----------------|--------|\n| `severity` | `critical`, `warning`, `info` | Controls urgency and routing |\n| `team` | `platform`, `database`, `frontend` | Routes to the right responders |\n| `environment` | `production`, `staging` | Distinguishes alert context |\n| `runbook` | `payments-runbook` | Points on-call to a runbook |" + "content": "Add a label with the key **severity** and the value **critical**, then click **Save**.\n\nA server returning 5xx errors is significant, so this alert deserves the critical tag." }, { "type": "interactive", "action": "noop", - "content": "Labels drive notification routing. For example, notification policies can:\n\n- Route `severity=\"critical\"` to PagerDuty for immediate response\n- Route `severity=\"warning\"` to a Slack channel for awareness\n- Route `team=\"platform\"` to the platform team's on-call\n- Route by service or environment labels to service owners\n\nChoose labels that match your notification policy matchers." - }, - { - "type": "markdown", - "content": "Your alert now has a folder and routing labels.\n\nNext, you set how often Grafana evaluates the rule and how long a problem must persist before it fires." + "content": "Your alert now has a folder and a label. Next, you'll tell Grafana how often to check the query and how long the problem must last before the alert fires." } ] } From fb29d4222c669fe8bde17565f03c6bce219581d2 Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Mon, 27 Jul 2026 18:59:35 -0500 Subject: [PATCH 11/19] alerting-first-rule-lj: rework set-evaluation-behavior milestone - Drop urgency/interval and alert-type reference tables - Retitle section 1 to 'Create an evaluation group' (single path) - Replace highlight steps with button click + noop for modal contents - Concrete 1m interval and 1m pending period recommendations - Convert trailing markdown wrap-up to noop transition --- .../set-evaluation-behavior/content.json | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/alerting-first-rule-lj/set-evaluation-behavior/content.json b/alerting-first-rule-lj/set-evaluation-behavior/content.json index 3ebc904b..5c058eed 100644 --- a/alerting-first-rule-lj/set-evaluation-behavior/content.json +++ b/alerting-first-rule-lj/set-evaluation-behavior/content.json @@ -5,31 +5,26 @@ "blocks": [ { "type": "markdown", - "content": "Evaluation behavior controls how often Grafana runs your rule and how long a problem must persist before the alert fires.\n\nIn this milestone, you configure the evaluation group and set the pending period." + "content": "Evaluation behavior sets how often Grafana runs your query and how long a problem must last before the alert fires.\n\nIn this milestone, you create an evaluation group and set a pending period." }, { "type": "section", "id": "configure-evaluation-group", - "title": "Configure the evaluation group", + "title": "Create an evaluation group", "requirements": ["on-page:/alerting/new/alerting"], "blocks": [ { "type": "interactive", - "action": "highlight", - "reftarget": "[data-testid='group-picker']", - "content": "Locate the evaluation group picker. Evaluation groups batch rules that share the same evaluation interval.\n\nIf the picker is disabled, select a folder first.", + "action": "button", + "reftarget": "New evaluation group", + "content": "In the **Set evaluation behavior** section, click **New evaluation group** to open the dialog.", "doIt": false, - "requirements": ["on-page:/alerting/new/alerting"], - "skippable": true + "requirements": ["on-page:/alerting/new/alerting"] }, { "type": "interactive", - "action": "highlight", - "reftarget": "[data-testid='data-testid alert-rule new-evaluation-group-button']", - "content": "If you don't have an existing group, click **New evaluation group** to create one. Choose a name and set the **Evaluation interval** — how often Grafana runs your query.\n\n| Urgency | Interval | Use case |\n|---------|----------|----------|\n| Critical | `1m` | Host down, service unavailable |\n| Standard | `5m` | CPU high, error rate elevated |\n| Low priority | `10m` | Disk trending full, memory creeping up |\n\nShorter intervals detect issues faster but increase query load on your data sources.", - "doIt": false, - "requirements": ["on-page:/alerting/new/alerting"], - "skippable": true + "action": "noop", + "content": "Give the group a name, such as **Alerts group**, set the **Evaluation interval** to **1m**, then click **Create**." } ] }, @@ -43,13 +38,14 @@ "type": "interactive", "action": "highlight", "reftarget": "#eval-for-input", - "content": "Locate the **Pending period** field. This controls how long the condition must be true before the alert fires, which prevents brief spikes from triggering notifications.\n\n| Alert type | Pending period | Why |\n|------------|----------------|-----|\n| CPU spike | `5m` | Filters brief spikes during deployments |\n| Error burst | `2m` | Catches sustained patterns, not single errors |\n| Disk full | `10m` | Disk usage changes slowly, longer window is fine |\n| Service down | `1m` | Outages need fast detection |", + "content": "Find the **Pending period** field. This is how long the condition must stay true before the alert fires. Set it to **1m** \u2014 brief spikes get filtered out, but a real problem fires quickly.", "doIt": false, "requirements": ["on-page:/alerting/new/alerting"] }, { - "type": "markdown", - "content": "Your alert now has a folder, routing labels, an evaluation schedule, and a pending period.\n\nIn the next milestone, you configure no-data handling, select a contact point, and save your rule." + "type": "interactive", + "action": "noop", + "content": "Your alert now has a folder, a label, and an evaluation schedule. Next, you'll pick who gets notified when the alert fires." } ] } From 1090f178c39e19696b506d4bc605e052dd956579 Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Tue, 28 Jul 2026 08:33:37 -0500 Subject: [PATCH 12/19] alerting-first-rule-lj: rework configure-handling, select-contact-point, save-rule; add configure-notification milestone - Rework configure-handling: plain-language intro, auto-expand collapse, keep-defaults rationale, transition noop - Rework select-contact-point: single highlight step on the picker, concrete grafana-default-email recommendation, transition noop - Add configure-notification milestone: formfill Summary and Description with QuickPizza-specific values - Slim save-rule to just save-and-activate with doIt: false so the learner presses Save - Update manifest to insert configure-notification between select-contact-point and save-rule - Split combined contact-point + annotation bullet in journey outcomes and end-journey recap --- .../configure-handling/content.json | 20 ++++++-- .../configure-notification/content.json | 48 +++++++++++++++++++ alerting-first-rule-lj/content.json | 2 +- .../end-journey/content.json | 2 +- alerting-first-rule-lj/manifest.json | 1 + alerting-first-rule-lj/save-rule/content.json | 29 +++-------- .../select-contact-point/content.json | 12 ++--- 7 files changed, 77 insertions(+), 37 deletions(-) create mode 100644 alerting-first-rule-lj/configure-notification/content.json diff --git a/alerting-first-rule-lj/configure-handling/content.json b/alerting-first-rule-lj/configure-handling/content.json index 8abf2b3b..7850bc35 100644 --- a/alerting-first-rule-lj/configure-handling/content.json +++ b/alerting-first-rule-lj/configure-handling/content.json @@ -5,7 +5,7 @@ "blocks": [ { "type": "markdown", - "content": "No-data and error handling controls what your rule does when a query returns no data or fails.\n\nIn this milestone, you configure how the rule handles missing data and query errors." + "content": "No-data and error handling controls what your rule does when a query returns no data or fails. In this milestone, you'll set these." }, { "type": "section", @@ -15,11 +15,21 @@ "blocks": [ { "type": "interactive", - "action": "highlight", - "reftarget": "button:contains('Configure no data and error handling')", - "content": "In the evaluation behavior area, expand **Configure no data and error handling** to control what happens when a query returns no data or errors.\n\nThe defaults are usually fine, but change the behavior for queries that legitimately return no data at times — for example, set **No data** to **Normal** so a quiet log stream doesn't fire an alert.", - "doIt": false, + "action": "button", + "reftarget": "Configure no data and error handling", + "content": "In the **Set evaluation behavior** section, click **Configure no data and error handling** to expand the options.", + "doIt": true, "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "interactive", + "action": "noop", + "content": "Leave both dropdown menus at their defaults. **Alert state if no data or all values are null** stays on **No Data**. **Alert state if execution error or timeout** stays on **Error**. That way, empty results and query failures each surface as their own alert instead of going unnoticed." + }, + { + "type": "interactive", + "action": "noop", + "content": "Your rule now handles empty results and query errors safely. Next, you'll choose the contact point that receives the notification." } ] } diff --git a/alerting-first-rule-lj/configure-notification/content.json b/alerting-first-rule-lj/configure-notification/content.json new file mode 100644 index 00000000..196fb5ae --- /dev/null +++ b/alerting-first-rule-lj/configure-notification/content.json @@ -0,0 +1,48 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-first-rule-configure-notification", + "title": "Configure the notification message", + "blocks": [ + { + "type": "markdown", + "content": "When your alert fires, the notification includes a **Summary** and a **Description**. In this milestone, you complete both so anyone who gets the alert can tell what happened and what to check." + }, + { + "type": "section", + "id": "add-summary", + "title": "Add a summary", + "requirements": ["on-page:/alerting/new/alerting"], + "blocks": [ + { + "type": "interactive", + "action": "formfill", + "reftarget": "[data-testid='annotation-value-0']", + "targetvalue": "QuickPizza returned {{ $value }} server errors in the last 5 minutes", + "content": "The **Summary** is the first line people see in the email or Slack message, so keep it short. The `{{ $value }}` piece gets replaced with the live number from your query when the alert fires.", + "requirements": ["on-page:/alerting/new/alerting"] + } + ] + }, + { + "type": "section", + "id": "add-description", + "title": "Add a description", + "requirements": ["section-completed:add-summary"], + "blocks": [ + { + "type": "interactive", + "action": "formfill", + "reftarget": "[data-testid='annotation-value-1']", + "targetvalue": "Fires when the 5-minute rate of QuickPizza HTTP 5xx responses stays above the threshold. Check the app dashboard and recent deploys first.", + "content": "The **Description** gives more detail than the Summary. Use it to explain what the alert is watching and where to start looking if it goes off.", + "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "interactive", + "action": "noop", + "content": "Your notification now says what happened and what to check. Next, you'll save and activate the rule." + } + ] + } + ] +} diff --git a/alerting-first-rule-lj/content.json b/alerting-first-rule-lj/content.json index 5c6f1841..9d7b25e9 100644 --- a/alerting-first-rule-lj/content.json +++ b/alerting-first-rule-lj/content.json @@ -9,7 +9,7 @@ }, { "type": "markdown", - "content": "## Here's what to expect\n\nWhen you complete this path, you'll be able to:\n\n- Open the new alert rule page and give your rule a clear name\n- Build a Prometheus query using the visual query builder\n- Set a comparison operator and threshold for the alert condition\n- Select a folder and add labels that control routing\n- Configure the evaluation group and pending period\n- Configure no-data and error handling\n- Select a contact point and add a summary annotation\n- Save and activate your alert rule" + "content": "## Here's what to expect\n\nWhen you complete this path, you'll be able to:\n\n- Open the new alert rule page and give your rule a clear name\n- Build a Prometheus query using the visual query builder\n- Set a comparison operator and threshold for the alert condition\n- Select a folder and add labels that control routing\n- Configure the evaluation group and pending period\n- Configure no-data and error handling\n- Select a contact point\n- Add a summary and description to your notification\n- Save and activate your alert rule" }, { "type": "markdown", diff --git a/alerting-first-rule-lj/end-journey/content.json b/alerting-first-rule-lj/end-journey/content.json index 26e0247c..a3f465c4 100644 --- a/alerting-first-rule-lj/end-journey/content.json +++ b/alerting-first-rule-lj/end-journey/content.json @@ -5,7 +5,7 @@ "blocks": [ { "type": "markdown", - "content": "Congratulations on completing this path! Job well done!\n\nWe hope you've enjoyed traveling with us as you:\n\n- Opened and named your alert rule\n- Built a Prometheus query with the visual query builder\n- Set the alert condition and threshold\n- Selected a folder and added routing labels\n- Configured the evaluation group and pending period\n- Configured no-data and error handling\n- Selected a contact point and added a summary annotation\n- Saved and activated your alert rule" + "content": "Congratulations on completing this path! Job well done!\n\nWe hope you've enjoyed traveling with us as you:\n\n- Opened and named your alert rule\n- Built a Prometheus query with the visual query builder\n- Set the alert condition and threshold\n- Selected a folder and added routing labels\n- Configured the evaluation group and pending period\n- Configured no-data and error handling\n- Selected a contact point\n- Added a summary and description to your notification\n- Saved and activated your alert rule" }, { "type": "markdown", diff --git a/alerting-first-rule-lj/manifest.json b/alerting-first-rule-lj/manifest.json index de0be272..61b945bc 100644 --- a/alerting-first-rule-lj/manifest.json +++ b/alerting-first-rule-lj/manifest.json @@ -17,6 +17,7 @@ "alerting-first-rule-set-evaluation-behavior", "alerting-first-rule-configure-handling", "alerting-first-rule-select-contact-point", + "alerting-first-rule-configure-notification", "alerting-first-rule-save-rule", "alerting-first-rule-end-journey" ], diff --git a/alerting-first-rule-lj/save-rule/content.json b/alerting-first-rule-lj/save-rule/content.json index 81d3f118..ca228364 100644 --- a/alerting-first-rule-lj/save-rule/content.json +++ b/alerting-first-rule-lj/save-rule/content.json @@ -5,43 +5,28 @@ "blocks": [ { "type": "markdown", - "content": "The last steps are the notification message and saving.\n\nIn this milestone, you add a summary annotation, then save and activate your rule." - }, - { - "type": "section", - "id": "add-annotation", - "title": "Add a summary annotation", - "requirements": ["on-page:/alerting/new/alerting"], - "blocks": [ - { - "type": "interactive", - "action": "highlight", - "reftarget": "[data-testid='annotation-value-0']", - "content": "In the **Configure notification message** section, locate the **Summary** field. The summary is the first line responders see in Slack, PagerDuty, email, and other channels. Keep it brief and use template variables to reference labels or values from your query.\n\nFor the QuickPizza alert you built, use:\n\n```\nQuickPizza returned {{ $value }} server errors in the last 5 minutes\n```\n\nOther examples for real alerts:\n\n| If alerting on | Example summary |\n|----------------|------------------|\n| CPU usage | `CPU usage is high on {{ $labels.instance }}` |\n| Error rate | `Error rate elevated for {{ $labels.job }}` |\n| Disk space | `Disk space low on {{ $labels.instance }}` |", - "doIt": false, - "requirements": ["on-page:/alerting/new/alerting"] - } - ] + "content": "The rule is ready. In this milestone, you save it and Grafana starts evaluating it on the schedule you set." }, { "type": "section", "id": "save-rule", "title": "Save and activate the rule", - "requirements": ["section-completed:add-annotation"], + "requirements": ["on-page:/alerting/new/alerting"], "blocks": [ { "type": "interactive", - "action": "highlight", - "reftarget": "button[data-testid='save-rule']", - "content": "Click **Save** to save and activate your alert rule.\n\nYour alert rule is now saved and active. Grafana immediately begins evaluating it at your specified interval.", + "action": "button", + "reftarget": "Save rule", + "content": "Click **Save rule** to save and activate your alert rule. Grafana immediately begins evaluating it at the interval you set.", "doIt": false, "requirements": ["on-page:/alerting/new/alerting"] }, { "type": "markdown", - "content": "**Your alert rule is saved and actively monitoring.** When the condition is met and persists past your pending period, the alert fires and notifications are sent to your contact point.\n\nYou've created a complete alert rule from query to notification." + "content": "**Your alert rule is saved and running.** When the condition holds past your pending period, the alert fires and notifications go to your contact point.\n\nYou've created a complete alert rule from query to notification." } ] } ] } + diff --git a/alerting-first-rule-lj/select-contact-point/content.json b/alerting-first-rule-lj/select-contact-point/content.json index 26b86873..e0ffd840 100644 --- a/alerting-first-rule-lj/select-contact-point/content.json +++ b/alerting-first-rule-lj/select-contact-point/content.json @@ -5,7 +5,7 @@ "blocks": [ { "type": "markdown", - "content": "A contact point is where notifications go, like email, Slack, or PagerDuty.\n\nIn this milestone, you select the contact point that receives notifications for this rule." + "content": "A contact point tells Grafana where to send notifications, like email, Slack, or PagerDuty. In this milestone, you pick the contact point that receives notifications when this rule fires." }, { "type": "section", @@ -17,18 +17,14 @@ "type": "interactive", "action": "highlight", "reftarget": "[data-testid='contact-point-picker']", - "content": "In the **Configure notifications** section, locate the **Contact point** picker. Select the contact point where you want alert notifications delivered.\n\nNotifications for firing alerts are routed to the selected contact point.", + "content": "In the **Configure notifications** section, open the **Contact point** picker and choose **grafana-default-email**. Every Grafana Cloud stack includes it, and it delivers notifications to the email on your account.", "doIt": false, "requirements": ["on-page:/alerting/new/alerting"] }, { "type": "interactive", - "action": "highlight", - "reftarget": "a[aria-label='View or create contact points']", - "content": "If you don't have a contact point yet, click **View or create contact points** to create one.\n\nChoose the integration that matches your team's workflow:\n\n| Integration | Setup |\n|-------------|-------|\n| **Email** | Enter your email address — simplest option |\n| **Slack** | Provide a webhook URL for your channel |\n| **PagerDuty** | Add your routing key for on-call escalation |\n| **Microsoft Teams** | Provide a webhook URL for your team channel |", - "doIt": false, - "requirements": ["on-page:/alerting/new/alerting"], - "skippable": true + "action": "noop", + "content": "Your rule now knows where to send notifications. Next, you'll write the notification message and save the rule." } ] } From 6728fa58f44d6c9e862ed42e5381a7a74ebb2880 Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Tue, 28 Jul 2026 08:56:27 -0500 Subject: [PATCH 13/19] alerting-first-rule-lj: fix save-rule selector and tighten business-value - save-rule: switch to highlight action with [data-testid='save-rule']; the actual Grafana button text is 'Save', so text-based match won't work - save-rule: convert trailing wrap-up markdown to a noop transition step - business-value: split the dense hook + list paragraph into a one-sentence hook and a scannable bullet list of what you build --- alerting-first-rule-lj/business-value/content.json | 8 ++++++-- alerting-first-rule-lj/save-rule/content.json | 11 ++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/alerting-first-rule-lj/business-value/content.json b/alerting-first-rule-lj/business-value/content.json index 137c9fab..33eaa484 100644 --- a/alerting-first-rule-lj/business-value/content.json +++ b/alerting-first-rule-lj/business-value/content.json @@ -5,11 +5,15 @@ "blocks": [ { "type": "markdown", - "content": "An alert rule turns your telemetry into action. It watches a query, decides when something is wrong, and notifies the right people through the right channel — so you find out about problems before your users do.\n\nEvery alert rule is built from the same pieces: a query that defines what to watch, a condition that decides when to fire, labels and a folder that organize and route it, evaluation settings that control timing, and notification settings that decide who hears about it. In this path, you build all of them in order." + "content": "An alert rule watches your data, fires when something looks wrong, and notifies the right people. You hear about problems before your users do." }, { "type": "markdown", - "content": "This path uses the QuickPizza tutorial data sources. The next milestone installs them for you, then the one after opens the new alert rule page so you can name your rule." + "content": "In this path, you build every part of an alert rule in order:\n\n- A query that picks what to watch\n- A condition that decides when to fire\n- A folder and labels that organize and route it\n- Evaluation settings that control how often it runs and how long it must be wrong before firing\n- A contact point and message that decide who hears about it and what they see" + }, + { + "type": "markdown", + "content": "You'll use the QuickPizza tutorial data sources. The next milestone installs them, then the one after opens the new alert rule page so you can name your rule." } ] } diff --git a/alerting-first-rule-lj/save-rule/content.json b/alerting-first-rule-lj/save-rule/content.json index ca228364..13b181de 100644 --- a/alerting-first-rule-lj/save-rule/content.json +++ b/alerting-first-rule-lj/save-rule/content.json @@ -15,15 +15,16 @@ "blocks": [ { "type": "interactive", - "action": "button", - "reftarget": "Save rule", - "content": "Click **Save rule** to save and activate your alert rule. Grafana immediately begins evaluating it at the interval you set.", + "action": "highlight", + "reftarget": "[data-testid='save-rule']", + "content": "Click **Save** in the action bar at the bottom of the page to save and activate your alert rule. Grafana immediately begins evaluating it at the interval you set.", "doIt": false, "requirements": ["on-page:/alerting/new/alerting"] }, { - "type": "markdown", - "content": "**Your alert rule is saved and running.** When the condition holds past your pending period, the alert fires and notifications go to your contact point.\n\nYou've created a complete alert rule from query to notification." + "type": "interactive", + "action": "noop", + "content": "Your alert rule is saved and running. When the condition holds past your pending period, the alert fires and notifications go to your contact point." } ] } From 0ce3ce8e2e45d698024f3c4131d5eee90c66db82 Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Tue, 28 Jul 2026 11:51:28 -0500 Subject: [PATCH 14/19] alerting-first-rule-lj: add Keep firing for step to set-evaluation-behavior New section highlights #keep-firing-for-input, explains the setting as post-condition dampening to prevent flapping, and recommends leaving it at None for this rule. Intro markdown updated to name all three evaluation-behavior settings. --- .../set-evaluation-behavior/content.json | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/alerting-first-rule-lj/set-evaluation-behavior/content.json b/alerting-first-rule-lj/set-evaluation-behavior/content.json index 5c058eed..01d8aaa1 100644 --- a/alerting-first-rule-lj/set-evaluation-behavior/content.json +++ b/alerting-first-rule-lj/set-evaluation-behavior/content.json @@ -5,7 +5,7 @@ "blocks": [ { "type": "markdown", - "content": "Evaluation behavior sets how often Grafana runs your query and how long a problem must last before the alert fires.\n\nIn this milestone, you create an evaluation group and set a pending period." + "content": "Evaluation behavior sets how often Grafana runs your query, how long a problem must last before the alert fires, and how long the alert keeps firing after the condition resolves.\n\nIn this milestone, you create an evaluation group, set a pending period, and set keep firing for." }, { "type": "section", @@ -41,6 +41,22 @@ "content": "Find the **Pending period** field. This is how long the condition must stay true before the alert fires. Set it to **1m** \u2014 brief spikes get filtered out, but a real problem fires quickly.", "doIt": false, "requirements": ["on-page:/alerting/new/alerting"] + } + ] + }, + { + "type": "section", + "id": "set-keep-firing-for", + "title": "Set keep firing for", + "requirements": ["section-completed:set-pending-period"], + "blocks": [ + { + "type": "interactive", + "action": "highlight", + "reftarget": "#keep-firing-for-input", + "content": "Find the **Keep firing for** field. This is how long the alert keeps firing after the condition resolves, so a metric bouncing across the threshold doesn't flap. Leave it at **None** for this rule \u2014 you can raise it later if you see the alert flip on and off.", + "doIt": false, + "requirements": ["on-page:/alerting/new/alerting"] }, { "type": "interactive", From 1e472aa8cde46ed67de6bbee6f571937c55c12cc Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Tue, 28 Jul 2026 11:51:38 -0500 Subject: [PATCH 15/19] alerting-query-language-lj: initial learning path New learning path teaching how to write an alert rule with query language using PromQL. Mirrors alerting-first-rule-lj structure with 12 milestones: install-datasources, navigate-to-form, build-query, set-condition, configure-labels-evaluation, set-evaluation-behavior, configure-handling (with recovery threshold), select-contact-point, configure-notification (formatted percentage annotations), save-rule, business-value, end-journey. --- .../build-query/content.json | 94 +++++++++++++++++++ .../business-value/content.json | 23 +++++ .../configure-handling/content.json | 55 +++++++++++ .../configure-labels-evaluation/content.json | 57 +++++++++++ .../configure-notification/content.json | 48 ++++++++++ alerting-query-language-lj/content.json | 27 ++++++ .../end-journey/content.json | 23 +++++ .../install-datasources/content.json | 43 +++++++++ alerting-query-language-lj/manifest.json | 28 ++++++ .../navigate-to-form/content.json | 58 ++++++++++++ .../save-rule/content.json | 32 +++++++ .../select-contact-point/content.json | 32 +++++++ .../set-condition/content.json | 72 ++++++++++++++ .../set-evaluation-behavior/content.json | 69 ++++++++++++++ alerting-query-language-lj/website.yaml | 40 ++++++++ 15 files changed, 701 insertions(+) create mode 100644 alerting-query-language-lj/build-query/content.json create mode 100644 alerting-query-language-lj/business-value/content.json create mode 100644 alerting-query-language-lj/configure-handling/content.json create mode 100644 alerting-query-language-lj/configure-labels-evaluation/content.json create mode 100644 alerting-query-language-lj/configure-notification/content.json create mode 100644 alerting-query-language-lj/content.json create mode 100644 alerting-query-language-lj/end-journey/content.json create mode 100644 alerting-query-language-lj/install-datasources/content.json create mode 100644 alerting-query-language-lj/manifest.json create mode 100644 alerting-query-language-lj/navigate-to-form/content.json create mode 100644 alerting-query-language-lj/save-rule/content.json create mode 100644 alerting-query-language-lj/select-contact-point/content.json create mode 100644 alerting-query-language-lj/set-condition/content.json create mode 100644 alerting-query-language-lj/set-evaluation-behavior/content.json create mode 100644 alerting-query-language-lj/website.yaml diff --git a/alerting-query-language-lj/build-query/content.json b/alerting-query-language-lj/build-query/content.json new file mode 100644 index 00000000..4fffc02e --- /dev/null +++ b/alerting-query-language-lj/build-query/content.json @@ -0,0 +1,94 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-query-language-build-query", + "title": "Write your query in Code mode", + "blocks": [ + { + "type": "markdown", + "content": "Your alert watches how often QuickPizza's server fails. Every request either works or doesn't. You want the alert to fire when **too many** fail, say, more than 5 in every 100.\n\nTo express \"5 in every 100,\" your query has to divide the number of failed requests by the number of *all* requests. Builder mode can't divide two numbers in one query, so you switch to Code mode and write it in PromQL." + }, + { + "type": "section", + "id": "select-data-source", + "title": "Select the QuickPizza Prometheus data source", + "requirements": [ + "has-datasource:grafanacloud-play-prom" + ], + "blocks": [ + { + "type": "interactive", + "action": "formfill", + "reftarget": "input[data-testid='data-testid Select a data source']", + "targetvalue": "grafanacloud-play-prom", + "content": "Choose **grafanacloud-play-prom** as the data source. That's the Prometheus data source with QuickPizza's metrics.", + "tooltip": "QuickPizza's Prometheus data source.", + "requirements": [ + "on-page:/alerting/new/alerting" + ] + } + ] + }, + { + "type": "section", + "id": "switch-to-code-mode", + "title": "Switch to Code mode", + "requirements": ["section-completed:select-data-source"], + "blocks": [ + { + "type": "interactive", + "action": "highlight", + "reftarget": "div[data-testid='data-testid QueryEditorModeToggle']", + "content": "Click **Code** on the mode toggle to switch away from Builder mode. The dropdowns collapse, and a text field appears where you type PromQL.", + "tooltip": "Switches the query editor from Builder to Code.", + "doIt": false, + "requirements": [ + "on-page:/alerting/new/alerting" + ] + } + ] + }, + { + "type": "section", + "id": "write-promql", + "title": "Write the PromQL error-rate query", + "requirements": ["section-completed:switch-to-code-mode"], + "blocks": [ + { + "type": "code-block", + "reftarget": "div[data-testid='data-testid Query field']", + "language": "promql", + "code": "100 * sum(rate(quickpizza_server_http_requests_total{status=~\"5..\"}[5m])) / sum(rate(quickpizza_server_http_requests_total[5m]))", + "content": "Add this query to the editor. Click **Insert** to drop it in.", + "requirements": [ + "on-page:/alerting/new/alerting" + ] + }, + { + "type": "markdown", + "content": "Here's what the query does, one piece at a time:\n\n- `quickpizza_server_http_requests_total` counts every request QuickPizza has ever handled.\n- `rate(...[5m])` turns that ever-growing count into a per-second speed, smoothed over the last 5 minutes.\n- `{status=~\"5..\"}` narrows to just the failed requests (anything with a status code starting with `5`, like `500` or `503`).\n- Dividing failed requests by all requests, then multiplying by 100, gives you a percentage." + } + ] + }, + { + "type": "section", + "id": "preview-query", + "title": "Preview the query", + "requirements": ["section-completed:write-promql"], + "blocks": [ + { + "type": "interactive", + "action": "button", + "reftarget": "Run queries", + "content": "Click **Run queries** to see what the query returns. You'll see a low percentage while QuickPizza is healthy, and spikes when errors happen. If the preview is empty, double-check that **grafanacloud-play-prom** is selected as the data source.", + "requirements": [ + "on-page:/alerting/new/alerting" + ] + }, + { + "type": "markdown", + "content": "The query now reports QuickPizza's error rate as a percentage. Next, you decide how high that percentage has to go before the alert fires." + } + ] + } + ] +} diff --git a/alerting-query-language-lj/business-value/content.json b/alerting-query-language-lj/business-value/content.json new file mode 100644 index 00000000..f72d2212 --- /dev/null +++ b/alerting-query-language-lj/business-value/content.json @@ -0,0 +1,23 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-query-language-business-value", + "title": "Why write your alert in query language?", + "blocks": [ + { + "type": "markdown", + "content": "**Builder mode** lets you build a query by picking things from menus: one metric, a filter or two, done. **Code mode** lets you type the query yourself, and that opens up patterns the menus can't build.\n\nIn this path, you use Code mode to alert on QuickPizza's **error rate**: the percentage of requests that fail. To get a percentage, the query has to divide one number by another, something Builder mode can't do in a single query." + }, + { + "type": "markdown", + "content": "## When to reach for Code mode\n\nCode mode is worth it when your alert needs:\n\n- **A percentage or ratio.** For example, failed requests as a share of all requests.\n- **To detect silence.** Alert when a metric stops reporting. The menus don't offer this.\n- **To compare two metrics.** For example, current usage against a capacity limit on a different series.\n- **A query you already trust.** Paste one in from a dashboard or a teammate." + }, + { + "type": "markdown", + "content": "## What you'll build\n\nStep by step, you build every part of the rule:\n\n- The PromQL query that measures error rate\n- The threshold that decides when to fire\n- A folder and labels so the alert is easy to find and route\n- How often Grafana checks the rule, and how long the problem must last before firing\n- Rules for handling missing data\n- The contact point and message that tell someone what's wrong" + }, + { + "type": "markdown", + "content": "You'll use **QuickPizza**, a demo pizza shop that ships with Grafana Cloud. The next milestone installs it." + } + ] +} diff --git a/alerting-query-language-lj/configure-handling/content.json b/alerting-query-language-lj/configure-handling/content.json new file mode 100644 index 00000000..9a07ec1e --- /dev/null +++ b/alerting-query-language-lj/configure-handling/content.json @@ -0,0 +1,55 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-query-language-configure-handling", + "title": "Configure no-data, error handling, and recovery", + "blocks": [ + { + "type": "markdown", + "content": "No-data and error handling controls what your rule does when a query returns no data or fails.\n\nA **recovery threshold** is a second boundary that stops the alert from flapping. It fires above your threshold, but only returns to normal when the value drops below the recovery boundary. Ratio queries can hover near the trigger point, so a recovery threshold matters here.\n\nIn this milestone, you configure both." + }, + { + "type": "section", + "id": "configure-handling", + "title": "Configure no-data and error handling", + "requirements": ["on-page:/alerting/new/alerting"], + "blocks": [ + { + "type": "interactive", + "action": "button", + "reftarget": "Configure no data and error handling", + "content": "In the **Set evaluation behavior** section, click **Configure no data and error handling** to expand the options.", + "doIt": true, + "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "interactive", + "action": "noop", + "content": "Leave both dropdowns at their defaults. **Alert state if no data or all values are null** stays on **No Data**. **Alert state if execution error or timeout** stays on **Error**. That way, empty results and query failures each surface as their own alert instead of going unnoticed." + } + ] + }, + { + "type": "section", + "id": "set-recovery-threshold", + "title": "Set a recovery threshold", + "requirements": ["section-completed:configure-handling"], + "skippable": true, + "blocks": [ + { + "type": "interactive", + "action": "highlight", + "reftarget": "[data-testid='data-testid alert-rule step-2']", + "content": "Scroll back to the **Set alert condition** section and turn on the **Custom recovery threshold** toggle. Set the recovery value to **3**. The alert now fires when the error rate crosses 5%, and only returns to normal when it drops below 3%. That gap prevents the alert from flapping when the rate hovers around 5%.", + "doIt": false, + "skippable": true, + "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "interactive", + "action": "noop", + "content": "Your rule now handles empty results, query errors, and recovery. Next, you choose the contact point that receives the notification." + } + ] + } + ] +} diff --git a/alerting-query-language-lj/configure-labels-evaluation/content.json b/alerting-query-language-lj/configure-labels-evaluation/content.json new file mode 100644 index 00000000..f4813fd6 --- /dev/null +++ b/alerting-query-language-lj/configure-labels-evaluation/content.json @@ -0,0 +1,57 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-query-language-configure-labels-evaluation", + "title": "Create a folder and add labels", + "blocks": [ + { + "type": "markdown", + "content": "You'll organize your alert into a folder and add labels that control how notifications get routed. Folders group alerts by team, service, or purpose, and control who can manage them.\n\nIn this milestone, you create a folder, then add rule-level labels." + }, + { + "type": "section", + "id": "select-folder", + "title": "Create a folder", + "blocks": [ + { + "type": "interactive", + "action": "button", + "reftarget": "New folder", + "content": "In the **Add folder and labels** section, click **New folder** to open the create dialog.", + "doIt": false, + "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "interactive", + "action": "noop", + "content": "Name the folder **Alerts** (or something meaningful to your team), then click **Create**. Grafana saves the folder and selects it for your rule." + } + ] + }, + { + "type": "section", + "id": "add-labels", + "title": "Add labels", + "requirements": ["section-completed:select-folder"], + "blocks": [ + { + "type": "interactive", + "action": "button", + "reftarget": "Add labels", + "content": "Click **Add labels** to open the labels editor. Labels are key-value tags that route this alert. A sustained 5% error rate deserves a `severity=critical` tag.", + "doIt": false, + "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "interactive", + "action": "noop", + "content": "In the **Choose key** dropdown, type or select **severity**. In **Choose value**, type **critical**. Click **Save**." + }, + { + "type": "interactive", + "action": "noop", + "content": "Your alert now has a folder and a label. Next, you tell Grafana how often to check the query and how long the problem must last before the alert fires." + } + ] + } + ] +} diff --git a/alerting-query-language-lj/configure-notification/content.json b/alerting-query-language-lj/configure-notification/content.json new file mode 100644 index 00000000..edfbce36 --- /dev/null +++ b/alerting-query-language-lj/configure-notification/content.json @@ -0,0 +1,48 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-query-language-configure-notification", + "title": "Configure the notification message", + "blocks": [ + { + "type": "markdown", + "content": "When your alert fires, the notification includes a **Summary** and a **Description**. In this milestone, you complete both so anyone who gets the alert can tell what happened and what to check.\n\nYour first alert reported a raw count of 5xx responses. This alert reports a **percentage**, because the query returns a ratio. The Summary uses `printf \"%.1f\"` to format `$value` as a one-decimal number, and the Description names the ratio explicitly so the on-call reader knows what 5% means." + }, + { + "type": "section", + "id": "add-summary", + "title": "Add a summary", + "requirements": ["on-page:/alerting/new/alerting"], + "blocks": [ + { + "type": "interactive", + "action": "formfill", + "reftarget": "[data-testid='annotation-value-0']", + "targetvalue": "QuickPizza error rate at {{ $value | printf \"%.1f\" }}% over the last 5 minutes", + "content": "The **Summary** is the first line people see. The `{{ $value }}` template renders the live percentage when the alert fires.", + "requirements": ["on-page:/alerting/new/alerting"] + } + ] + }, + { + "type": "section", + "id": "add-description", + "title": "Add a description", + "requirements": ["section-completed:add-summary"], + "blocks": [ + { + "type": "interactive", + "action": "formfill", + "reftarget": "[data-testid='annotation-value-1']", + "targetvalue": "Fires when the ratio of QuickPizza 5xx responses to total requests exceeds 5% over 5 minutes. Check recent deploys and the app dashboard first.", + "content": "The **Description** gives more detail than the Summary. Use it to explain what the alert is watching and where to start looking if it goes off.", + "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "interactive", + "action": "noop", + "content": "Your notification now says what happened and what to check. Next, you save and activate the rule." + } + ] + } + ] +} diff --git a/alerting-query-language-lj/content.json b/alerting-query-language-lj/content.json new file mode 100644 index 00000000..182c93c5 --- /dev/null +++ b/alerting-query-language-lj/content.json @@ -0,0 +1,27 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-query-language-lj", + "title": "Create an alert rule with query language", + "blocks": [ + { + "type": "markdown", + "content": "Welcome! In this learning path, you build a Grafana alert rule by writing the query yourself in **Code mode**, instead of picking it from menus.\n\nYou'll open a new alert rule, write a PromQL query that measures QuickPizza's error rate, set a threshold, add labels and a folder, choose a contact point, and save." + }, + { + "type": "markdown", + "content": "## What you'll learn\n\nBy the end of this path, you can:\n\n- Tell when Code mode is a better fit than Builder mode\n- Write a PromQL query that calculates a percentage\n- Set a threshold that decides when the alert fires\n- Organize an alert with a folder and labels\n- Control how often Grafana checks the rule\n- Handle no-data and error cases so the alert stays useful\n- Send the alert to a contact point with a helpful message" + }, + { + "type": "markdown", + "content": "## Before you begin\n\nYou need:\n\n- A Grafana Cloud account. To sign up, refer to [Grafana Cloud](https://grafana.com/signup/cloud/connect-account).\n- Permission to create alert rules.\n- Some familiarity with PromQL. If PromQL is new to you, try [Create your first alert rule](https://grafana.com/docs/learning-paths/alerting-first-rule/) first. It uses Builder mode, so you don't need to write any query.\n\nThe next milestone installs **QuickPizza**, a demo pizza shop that generates real metrics. Querying it doesn't count toward your Grafana Cloud usage." + }, + { + "type": "markdown", + "content": "## Troubleshooting\n\nIf you get stuck, we've got your back! Where appropriate, troubleshooting information is just a click away." + }, + { + "type": "markdown", + "content": "## More to explore\n\nAfter you save your first rule, explore silences to suppress notifications during planned maintenance, and learn how to configure contact points and notification policies in depth." + } + ] +} diff --git a/alerting-query-language-lj/end-journey/content.json b/alerting-query-language-lj/end-journey/content.json new file mode 100644 index 00000000..c31de5dc --- /dev/null +++ b/alerting-query-language-lj/end-journey/content.json @@ -0,0 +1,23 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-query-language-end-journey", + "title": "Destination reached!", + "blocks": [ + { + "type": "markdown", + "content": "Congratulations on completing this path! Job well done!\n\nWe hope you've enjoyed traveling with us as you:\n\n- Opened and named your alert rule\n- Switched the query editor to Code mode\n- Wrote a PromQL error-rate query using two `rate()` calls and a ratio\n- Set the alert condition and threshold\n- Created a folder and added a routing label\n- Configured the evaluation group and pending period\n- Configured no-data and error handling, and set a recovery threshold\n- Selected a contact point\n- Added a summary and description to your notification\n- Saved and activated your alert rule" + }, + { + "type": "markdown", + "content": "You now have a working alert rule that reaches for a pattern the visual builder can't compose in one query. That's the real power of Code mode." + }, + { + "type": "markdown", + "content": "---\n\n### More to explore (optional)\n\nRead more about how you can:\n\n- [Build alert queries and conditions](https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/queries-conditions/)\n- [Write PromQL expressions](https://grafana.com/docs/grafana/latest/fundamentals/intro-to-promql/)\n- [Configure contact points](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/)\n- [Write effective alert annotations](https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/annotation-label/)" + }, + { + "type": "markdown", + "content": "---\n\n### Related paths\n\nConsider taking the following path next:\n\n- [Create a silence](https://grafana.com/docs/learning-paths/alerting-create-silence/)" + } + ] +} diff --git a/alerting-query-language-lj/install-datasources/content.json b/alerting-query-language-lj/install-datasources/content.json new file mode 100644 index 00000000..3202f809 --- /dev/null +++ b/alerting-query-language-lj/install-datasources/content.json @@ -0,0 +1,43 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-query-language-install-datasources", + "title": "Install the tutorial data sources", + "blocks": [ + { + "type": "markdown", + "content": "**QuickPizza** is a demo pizza-shop app that runs inside Grafana Cloud. It generates real metrics, so your queries have something interesting to see. Querying it doesn't count toward your Grafana Cloud usage.\n\nYou install QuickPizza from the **Demo Data Dashboards** app in the left nav. When it's done, Grafana refreshes the page and you're ready to build the alert." + }, + { + "type": "section", + "id": "install-tutorial-datasources", + "title": "Install QuickPizza", + "objectives": [ + "has-datasource:grafanacloud-play-prom" + ], + "blocks": [ + { + "type": "interactive", + "action": "highlight", + "reftarget": "a[data-testid='data-testid Nav menu item'][href='/a/grafana-demodashboards-app']", + "content": "In the left nav, click **Demo Data Dashboards**.", + "tooltip": "Opens the app that installs QuickPizza.", + "requirements": [ + "navmenu-open" + ] + }, + { + "type": "interactive", + "action": "highlight", + "reftarget": "button[data-testid='install-quickpizza']", + "content": "Click **Install the Quick Pizza SRE demo**. Grafana adds QuickPizza's data sources and refreshes the page.", + "tooltip": "Adds QuickPizza's metrics data source.", + "completeEarly": true + } + ] + }, + { + "type": "markdown", + "content": "QuickPizza is installed. Next, you open the alert rule form and give your rule a name." + } + ] +} diff --git a/alerting-query-language-lj/manifest.json b/alerting-query-language-lj/manifest.json new file mode 100644 index 00000000..cd7eb25a --- /dev/null +++ b/alerting-query-language-lj/manifest.json @@ -0,0 +1,28 @@ +{ + "id": "alerting-query-language-lj", + "type": "path", + "description": "Step-by-step guide: open and name an alert rule, switch to Code mode and write a PromQL error-rate query, set the condition, add a folder and labels, configure evaluation and handling, select a contact point, and save.", + "category": "take-action", + "author": { "name": "JohnnyK-Grafana", "team": "Grafana Documentation" }, + "startingLocation": "/alerting", + "targeting": { "match": { "and": [ { "urlPrefix": "/alerting" }, { "targetPlatform": "cloud" } ] } }, + "testEnvironment": { "tier": "cloud" }, + "milestones": [ + "alerting-query-language-business-value", + "alerting-query-language-install-datasources", + "alerting-query-language-navigate-to-form", + "alerting-query-language-build-query", + "alerting-query-language-set-condition", + "alerting-query-language-configure-labels-evaluation", + "alerting-query-language-set-evaluation-behavior", + "alerting-query-language-configure-handling", + "alerting-query-language-select-contact-point", + "alerting-query-language-configure-notification", + "alerting-query-language-save-rule", + "alerting-query-language-end-journey" + ], + "depends": [], + "recommends": [], + "suggests": ["alerting-create-silence-lj"], + "provides": [] +} diff --git a/alerting-query-language-lj/navigate-to-form/content.json b/alerting-query-language-lj/navigate-to-form/content.json new file mode 100644 index 00000000..51904cec --- /dev/null +++ b/alerting-query-language-lj/navigate-to-form/content.json @@ -0,0 +1,58 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-query-language-navigate-to-form", + "title": "Open the new alert rule page", + "blocks": [ + { + "type": "markdown", + "content": "When an alert fires, the name is the first thing responders see, so give the rule a name that says exactly what's wrong.\n\nIn this milestone, you open a fresh alert rule form and name the rule." + }, + { + "type": "section", + "id": "open-alert-form", + "title": "Open the new alert rule page", + "blocks": [ + { + "type": "interactive", + "action": "navigate", + "reftarget": "/alerting/list", + "content": "Open the **Alert rules** page. It's in the main menu under **Alerting > Alert rules**.", + "verify": "on-page:/alerting/list" + }, + { + "type": "interactive", + "action": "highlight", + "reftarget": "a[href*='alerting/new/alerting']", + "content": "Click **+ New alert rule** to start a fresh rule.", + "requirements": ["on-page:/alerting/list"], + "verify": "on-page:/alerting/new/alerting" + }, + { + "type": "markdown", + "content": "You're on the new alert rule page. Any draft you had open before is now cleared." + } + ] + }, + { + "type": "section", + "id": "set-alert-name", + "title": "Name the rule", + "requirements": ["section-completed:open-alert-form"], + "blocks": [ + { + "type": "interactive", + "action": "formfill", + "reftarget": "input#name", + "targetvalue": "QuickPizza high error rate", + "content": "In the **Alert rule name** field, use **QuickPizza high error rate**. It says exactly what the rule watches.", + "tooltip": "This name shows up first when the alert fires.", + "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "markdown", + "content": "Next, you write the query that measures the error rate." + } + ] + } + ] +} diff --git a/alerting-query-language-lj/save-rule/content.json b/alerting-query-language-lj/save-rule/content.json new file mode 100644 index 00000000..02726e2e --- /dev/null +++ b/alerting-query-language-lj/save-rule/content.json @@ -0,0 +1,32 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-query-language-save-rule", + "title": "Save your alert rule", + "blocks": [ + { + "type": "markdown", + "content": "The rule is ready. In this milestone, you save it and Grafana starts evaluating it on the schedule you set." + }, + { + "type": "section", + "id": "save-rule", + "title": "Save and activate the rule", + "requirements": ["on-page:/alerting/new/alerting"], + "blocks": [ + { + "type": "interactive", + "action": "highlight", + "reftarget": "[data-testid='save-rule']", + "content": "Click **Save** in the action bar at the bottom of the page to save and activate your alert rule. Grafana immediately begins evaluating it at the interval you set.", + "doIt": false, + "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "interactive", + "action": "noop", + "content": "Your alert rule is saved and running. When the error rate holds above 5% past the pending period, the alert fires and notifications go to your contact point." + } + ] + } + ] +} diff --git a/alerting-query-language-lj/select-contact-point/content.json b/alerting-query-language-lj/select-contact-point/content.json new file mode 100644 index 00000000..cb3b0b8e --- /dev/null +++ b/alerting-query-language-lj/select-contact-point/content.json @@ -0,0 +1,32 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-query-language-select-contact-point", + "title": "Select a contact point", + "blocks": [ + { + "type": "markdown", + "content": "A contact point tells Grafana where to send notifications, like email, Slack, or PagerDuty.\n\nIn this milestone, you pick the contact point that receives notifications when this rule fires." + }, + { + "type": "section", + "id": "select-contact-point", + "title": "Select a contact point", + "requirements": ["on-page:/alerting/new/alerting"], + "blocks": [ + { + "type": "interactive", + "action": "highlight", + "reftarget": "[data-testid='contact-point-picker']", + "content": "In the **Configure notifications** section, open the **Contact point** picker and choose **grafana-default-email**. Every Grafana Cloud stack includes it, and it delivers notifications to the email on your account.", + "doIt": false, + "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "interactive", + "action": "noop", + "content": "Your rule now knows where to send notifications. Next, you write the notification message and save the rule." + } + ] + } + ] +} diff --git a/alerting-query-language-lj/set-condition/content.json b/alerting-query-language-lj/set-condition/content.json new file mode 100644 index 00000000..0f7ffd5d --- /dev/null +++ b/alerting-query-language-lj/set-condition/content.json @@ -0,0 +1,72 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-query-language-set-condition", + "title": "Set the alert condition", + "blocks": [ + { + "type": "markdown", + "content": "Your query returns a percentage. Now you tell Grafana *when* that percentage counts as \"too high\" and the alert should fire.\n\nIn this milestone, you set the threshold to **5**, meaning the alert fires when the error rate goes above 5%." + }, + { + "type": "section", + "id": "choose-operator", + "title": "Check when the alert fires", + "requirements": ["has-datasource:grafanacloud-play-prom"], + "blocks": [ + { + "type": "interactive", + "action": "highlight", + "reftarget": "label:contains('WHEN QUERY')", + "content": "This is the **Alert condition** section. It's where you say: \"Fire the alert when the query result goes above this number.\"", + "doIt": false, + "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "interactive", + "action": "highlight", + "reftarget": "[data-testid='data-testid alert-rule step-2'] button:contains('Is above')", + "content": "**Is above** is already selected. Leave it. The alert fires when the error rate goes above your threshold.", + "doIt": false, + "requirements": ["on-page:/alerting/new/alerting"] + } + ] + }, + { + "type": "section", + "id": "set-threshold", + "title": "Set the threshold", + "requirements": ["section-completed:choose-operator"], + "blocks": [ + { + "type": "interactive", + "action": "formfill", + "reftarget": "[data-testid='data-testid alert-rule step-2'] input[type='number']", + "targetvalue": "5", + "content": "Set the threshold to **5**. Because the query returns a percentage, `5` means 5%.", + "tooltip": "The query returns a percentage, so 5 means 5%.", + "requirements": ["on-page:/alerting/new/alerting"] + } + ] + }, + { + "type": "section", + "id": "preview-condition", + "title": "Preview the rule", + "requirements": ["section-completed:set-threshold"], + "blocks": [ + { + "type": "interactive", + "action": "button", + "reftarget": "Preview alert rule condition", + "content": "Click **Preview alert rule condition** to test it. You'll see a **Normal** result when the error rate is under 5%, and **Firing** when it goes over.", + "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "interactive", + "action": "noop", + "content": "The condition is set. Next, you pick a folder and add labels so the alert is easy to find and route." + } + ] + } + ] +} diff --git a/alerting-query-language-lj/set-evaluation-behavior/content.json b/alerting-query-language-lj/set-evaluation-behavior/content.json new file mode 100644 index 00000000..afb5f006 --- /dev/null +++ b/alerting-query-language-lj/set-evaluation-behavior/content.json @@ -0,0 +1,69 @@ +{ + "schemaVersion": "1.1.0", + "id": "alerting-query-language-set-evaluation-behavior", + "title": "Set evaluation behavior", + "blocks": [ + { + "type": "markdown", + "content": "Evaluation behavior sets how often Grafana runs your query, how long a problem must last before the alert fires, and how long the alert keeps firing after the condition resolves.\n\nYou saw these settings in the first alert rule. This milestone walks through them again for the ratio query." + }, + { + "type": "section", + "id": "configure-evaluation-group", + "title": "Create an evaluation group", + "requirements": ["on-page:/alerting/new/alerting"], + "blocks": [ + { + "type": "interactive", + "action": "button", + "reftarget": "New evaluation group", + "content": "In the **Set evaluation behavior** section, click **New evaluation group** to open the create dialog.", + "doIt": false, + "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "interactive", + "action": "noop", + "content": "Name the group **QuickPizza alerts** (groups share an evaluation interval, so the name should describe what's grouped). Set the **Evaluation interval** to **1m**, then click **Create**." + } + ] + }, + { + "type": "section", + "id": "set-pending-period", + "title": "Set the pending period", + "requirements": ["section-completed:configure-evaluation-group"], + "blocks": [ + { + "type": "interactive", + "action": "highlight", + "reftarget": "#eval-for-input", + "content": "Find the **Pending period** field. This is how long the condition must stay true before the alert fires. Set it to **1m**. Brief spikes get filtered out, but a real problem fires quickly.", + "doIt": false, + "requirements": ["on-page:/alerting/new/alerting"] + } + ] + }, + { + "type": "section", + "id": "set-keep-firing-for", + "title": "Set keep firing for", + "requirements": ["section-completed:set-pending-period"], + "blocks": [ + { + "type": "interactive", + "action": "highlight", + "reftarget": "#keep-firing-for-input", + "content": "Find the **Keep firing for** field. This is how long the alert keeps firing after the condition resolves, so a metric bouncing across the threshold doesn't flap. Leave it at **None** \u2014 the recovery threshold you set in the next milestone handles flapping for this ratio query.", + "doIt": false, + "requirements": ["on-page:/alerting/new/alerting"] + }, + { + "type": "interactive", + "action": "noop", + "content": "Your alert now has a folder, a label, and an evaluation schedule. Next, you handle missing data and query errors, then set a recovery threshold to stop flapping." + } + ] + } + ] +} diff --git a/alerting-query-language-lj/website.yaml b/alerting-query-language-lj/website.yaml new file mode 100644 index 00000000..1bb622b2 --- /dev/null +++ b/alerting-query-language-lj/website.yaml @@ -0,0 +1,40 @@ +menuTitle: First alert rule with query language +title: Create an alert rule with query language +description: Build a Grafana alert rule in Code mode — write a PromQL error-rate query, set a condition, add labels, configure evaluation and notifications, and save. +weight: 750 +journey: + group: take-action + skill: Intermediate + source: Docs & blog posts + logo: + src: /static/img/menu/grafana2.svg + background: '#0068FF' + width: 46 + height: 55 +step: 1 +layout: single-journey +cascade: + layout: single-journey +cta: + type: start + title: Are you ready? + cta_text: Let's go! +keywords: + - alerting + - alert rule + - PromQL + - code mode + - query language + - notifications + - save +related_journeys: + title: Related paths + heading: Consider taking the following paths before you start this path. + items: + - title: Create your first alert rule + url: /docs/learning-paths/alerting-first-rule/ + description: Learn the alerting workflow using Builder mode. Recommended before this path if you're new to PromQL. +source_docs: + - /docs/grafana/latest/alerting/fundamentals/alert-rules/ + - /docs/grafana/latest/alerting/fundamentals/alert-rules/queries-conditions/ + - /docs/grafana/latest/alerting/fundamentals/notifications/contact-points/ From 80d579694589677d1b55c841de20310e3acf83f4 Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Tue, 28 Jul 2026 12:05:34 -0500 Subject: [PATCH 16/19] PR #482: add reviewer rationale notes Per-commit rationale for the entire changeset stacked on docs/lp-basic-alerting, covering install-datasources, query rework, UI-wording alignment with Grafana docs, jargon removal, selector fixes, notification milestone, Keep firing for gap-fill, and the new alerting-query-language-lj. Includes alignment matrix against the learning-hub docs in grafana/website PR #30466. --- PR-482-REVIEW-NOTES.md | 163 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 PR-482-REVIEW-NOTES.md diff --git a/PR-482-REVIEW-NOTES.md b/PR-482-REVIEW-NOTES.md new file mode 100644 index 00000000..329669fe --- /dev/null +++ b/PR-482-REVIEW-NOTES.md @@ -0,0 +1,163 @@ +# PR #482 review notes + +Rationale for every commit stacked on top of `docs/lp-basic-alerting` (PR #377) for reviewers of PR #482. Read this before the file diff — it explains **why** each interaction was added, reworded, or reordered. + +## Stack shape + +- PR #377 (JohnnyK-Grafana): `main` ← `docs/lp-basic-alerting` — original first-rule learning path packages. +- PR #482 (Beverly): `docs/lp-basic-alerting` ← `docs/lp-basic-alerting-install-datasources` — everything below. + +Merging PR #482 into `docs/lp-basic-alerting` folds all these commits into PR #377's changeset so they land on `main` together. + +## Guiding principles applied across the branch + +These principles drove most of the reworks. When you see a commit that changes an interaction, one or more of these is the reason. + +1. **Concrete over generic.** Every step targets QuickPizza with real metric names, real threshold values, and real annotation copy — not `` or "your metric here." +2. **Concept-first, plain language.** Section intros teach a concept in one paragraph. Jargon like "responders," "template variables," "rule-level labels ride along," "comparison operator," "signal," "annotations," "service_name" was removed and replaced with what the reader is doing and why. +3. **One numbered step per action.** Every `interactive` block has an action attached. Standalone `markdown` blocks between interactive steps caused lonely numbered lines when requirements gated the next block, so intros were folded into the interactive step's `content` field. +4. **Single path, no branching prose.** Sections that used to say "if the picker is enabled do X, otherwise do Y" were reduced to the one path the learner will actually see. Reference tables (Urgency/Interval, Alert type/Pending period) were dropped in favor of a single concrete recommendation. +5. **UI wording matches current Grafana docs (v11.1+).** The alerting UI is called the "new alert rule page," not "form" or "creation form" — this matches the Grafana docs and the button label (**+ New alert rule**). +6. **Stable selectors.** Where text-based `reftarget` was ambiguous or duplicated (e.g. multiple **Save** buttons), we switched to `[data-testid=...]` selectors verified against a live Grafana instance. +7. **Modal-aware copy.** Steps that open a Grafana modal put the instructional detail inside a `noop` after the button click, because the modal covers the sidebar. Short, dropdown-name-first wording lets the learner read the step even with the modal open. +8. **`doIt: false` where the learner should own the outcome.** Save, contact-point selection, and any step that opens a destructive dialog leave the click to the user. `doIt: true` is used only for expand/collapse toggles and modal openers. + +## Commit-by-commit rationale (oldest → newest) + +### `b1f322d` alerting-first-rule-lj: add install-datasources milestone + +**Why.** The path assumed the QuickPizza data source was already installed. Learners hitting the LJ fresh had no way to get it. Added a Pathfinder-driven install step using the shared `tutorial-datasources.json` template so the LJ is self-sufficient. + +**Interaction added.** `install-datasources` milestone using the shared install pattern; manifest rewired to run `business-value → install-datasources → navigate-to-form`. + +### `9b3a886` alerting-first-rule-lj: replace "form" with "new alert rule page" + +**Why.** Grafana's alerting docs (v11.1+) describe this UI by page name, not as a "form." Learner-facing copy that says "the form" doesn't match what the docs, the button label, or the browser tab title say. Wire-level IDs, folder names, section IDs, and requirement tokens were not touched — only the human-visible copy. + +### `3bba6b1` alerting-first-rule-lj: rework query to watch QuickPizza 5xx errors + +**Why.** The prior query was generic and abstracted. Learners built a rule without ever seeing what it detects. Rewritten to build `sum(increase(quickpizza_server_http_requests_total{status=~"5.."}[5m]))` in Builder mode, pre-filling values where the Grafana combobox allows. + +**Downstream ripples.** +- `set-condition`: threshold changed from `1` (req/s) to `0` (any errors); "traffic surge" framing dropped. +- `configure-labels-evaluation`, `save-rule`: `service_name=quickpizza-public-api` references removed because the aggregation drops labels. +- `save-rule` summary annotation now uses `{{ $value }}` since `sum()` aggregates labels away. + +### `2feb7c2` alerting-first-rule-lj: simplify build-query intro + +**Why.** The opening paragraph was dense and mixed concept + steps. Split into a one-line lede, a plain-language bullet list of what the learner will do, and a one-sentence payoff. + +### `60672a1` alerting-first-rule-lj: collapse section intros and pre-fill alert name + +**Why.** Sections had a markdown intro block followed by an interactive step gated on `on-page:...`. When the requirement wasn't met, the intro rendered as a lonely numbered sentence with no action next to it. Folded every section's lede markdown into the immediately-following interactive step's `content` field. + +Also pre-filled the alert rule name (`QuickPizza server errors`) via `formfill` on `navigate-to-form` so it matches the query the learner builds in the next milestone. + +### `9cffeeb` alerting-first-rule-lj: tighten alert-name step copy + +**Why.** The alert-name step still had a three-block intro + formfill + trailing summary. Collapsed into one interactive step (intro sentence, why-it-matters sentence, formfill instruction) with the next-milestone line as the transition noop. + +### `5c85ce6` alerting-first-rule-lj: fix build-query selectors and step wording + +**Why.** Preview against live Grafana revealed the selectors: +- Operator picker: needs `[data-testid='data-testid Select match operator']`; step reworded to "Show me only" with no skip option. +- Explain toggle: needs `[data-testid='data-testid prometheus explain switch wrapper']`; section moved to run immediately after the data source pick, where it's actually useful. +- Value: reworded to "In the **Select value** box, enter 5.." to match the visible field label. + +### `82609fd` alerting-first-rule-lj: use stable Select value testid in build-query + +**Why.** Text-based `reftarget` on "Select value" matched multiple pickers on the page. Switched to a stable data-testid to disambiguate. + +### `2b70e75` alerting-first-rule-lj: rework set-condition milestone + +**Why.** Sections were titled with jargon ("Configure the threshold condition"), the intros repeated the same idea twice, and there were redundant markdown blocks. + +- Sections retitled to "Choose when the alert fires" and "Preview rule" (learner outcomes, not UI vocabulary). +- Alert-condition area highlighted via the **WHEN QUERY** label instead of a fragile container selector. +- Is-above button scoped under `alert-rule step-2` so it doesn't match the same button elsewhere. +- Dropped "comparison operator" and "threshold" jargon; used plain language. +- Removed the trailing markdown wrap-up; folded into the final noop. + +### `fee7182` alerting-first-rule-lj: rework configure-labels-evaluation milestone + +**Why.** The intro leaned on `service_name` and "Sum step" terms that no longer applied after the query rework in `3bba6b1`. The section for creating a folder branched on whether the picker was disabled. + +- Retitled to "Create a folder and add labels." +- Intro rewritten as a plain routing summary (no `annotations`, no `template variables` mentions). +- Section 1 reduced to a single create-folder path: click **New folder**, then a `noop` for name + Create inside the modal. +- Section 2: click **Add labels**, then a modal-aware `noop` with a concrete `severity=critical` example, then a brief transition. +- Dropped the reference-dump markdown table and the orphan wrap-up. + +### `fb29d42` alerting-first-rule-lj: rework set-evaluation-behavior milestone + +**Why.** The milestone had two reference tables (urgency vs interval, and alert-type vs pending period) that made the reader decide something they don't need to decide in a first-rule tutorial. Removed both. + +- Section 1 retitled to "Create an evaluation group" (single path — no more "if you have an existing group, use it" branch). +- Replaced `highlight` on modal fields with a `button` click for the modal opener + `noop` for the modal's contents (which cover the sidebar). +- Concrete recommendations: 1m interval, 1m pending period. No decision tree. +- Trailing wrap-up markdown converted to a noop transition. + +### `1090f17` alerting-first-rule-lj: rework configure-handling, select-contact-point, save-rule; add configure-notification milestone + +**Why.** The tail end of the LJ had four issues: +1. `configure-handling` explained no-data / error handling in dense reference-doc language. +2. `select-contact-point` mixed picker interaction, a table of options, and a decision paragraph. +3. `save-rule` bundled the notification-message setup with the actual save. +4. Notification-message setup wasn't its own milestone, so it competed with save-and-activate for attention. + +- `configure-handling`: plain-language intro; auto-expand the collapse; keep-defaults rationale explaining why defaults are safest for a first rule; noop transition. +- `select-contact-point`: one `highlight` step on the picker with a concrete recommendation of `grafana-default-email`; transition noop. +- `configure-notification` (new milestone): two `formfill` steps for Summary and Description with QuickPizza-specific values. +- `save-rule` slimmed to just save-and-activate with `doIt: false` — the learner presses **Save**. +- Manifest updated to insert `configure-notification` between `select-contact-point` and `save-rule`. +- Business-value and end-journey recap bullets split so contact point and annotation aren't glued together. + +### `6728fa5` alerting-first-rule-lj: fix save-rule selector and tighten business-value + +**Why.** +- Save button text is just "Save" and matches multiple buttons on the page. Switched `save-rule` to a `highlight` action with `[data-testid='save-rule']`. +- Converted the trailing wrap-up markdown to a `noop` transition so it renders as a numbered step, not orphan text. +- Business-value copy had a dense hook + list paragraph. Split into a one-sentence hook and a scannable bullet list of what the learner will build. + +### `0ce3ce8` alerting-first-rule-lj: add Keep firing for step to set-evaluation-behavior + +**Why.** The learning-hub conceptual milestone `content/docs/learning-hub/basic-alerting/01-first-alert-no-query-language/04-builder-evaluation/index.md` teaches three evaluation-behavior settings: evaluation group, pending period, and **Keep firing for**. The LJ had the first two but not the third. This commit closes the gap so the LJ matches what the doc says the learner will encounter. + +- New section `set-keep-firing-for` after `set-pending-period`. +- Highlights `#keep-firing-for-input` with `doIt: false` and recommends leaving the setting at **None** for this rule (the query is a raw count, not a hovering ratio — no flapping to dampen). +- Intro markdown updated to name all three evaluation-behavior settings. + +### `1e472aa` alerting-query-language-lj: initial learning path + +**Why.** The learning-hub milestone `content/docs/learning-hub/basic-alerting/02-alert-with-query-language/` teaches the same first-rule flow with a PromQL/ratio query and adds one new concept (recovery threshold). There was no corresponding interactive LJ. + +**Structure.** Mirrors `alerting-first-rule-lj` with 12 milestones so the learner recognizes every step from the first path. + +**Where it deliberately differs from first-rule-lj:** +- `build-query`: writes PromQL directly instead of using Builder mode. Query is a ratio (`sum(rate(5xx)) / sum(rate(total))`). +- `set-condition`: threshold is 5 (percent). Section framing acknowledges the ratio's semantics (not "any errors"). +- `configure-labels-evaluation`: intro reframed as review, because the learner met these settings in the first rule. `severity=critical` rationale is on the parent button step; modal-covered instruction is short and dropdown-first. +- `set-evaluation-behavior`: intro is review-framed. Includes the same three settings including **Keep firing for**, but recommends leaving Keep firing for at **None** because the recovery-threshold set two milestones later handles flapping for a ratio query. +- `configure-handling`: keeps the existing no-data / error defaults. Adds a **skippable** `set-recovery-threshold` section that turns on Custom recovery threshold and sets the recovery value to 3. Intro markdown teaches recovery threshold as value-based dampening (fire above 5, recover below 3). +- `configure-notification`: **Summary** uses `{{ $value | printf "%.1f" }}%` to format the ratio as a percentage; **Description** names the ratio explicitly. Intro markdown calls out the difference vs first-rule (count → percentage) so the reader understands why the templates differ. + +## Alignment with learning-hub docs + +The two LJs align 1:1 with the conceptual content in `grafana/website` PR #30466: + +| Concept | learning-hub location | alerting-first-rule-lj | alerting-query-language-lj | +|---|---|---|---| +| Evaluation group | milestone 01 `04-builder-evaluation` | ✅ `set-evaluation-behavior` | ✅ `set-evaluation-behavior` | +| Pending period | milestone 01 `04-builder-evaluation` | ✅ `set-evaluation-behavior` | ✅ `set-evaluation-behavior` | +| Keep firing for | milestone 01 `04-builder-evaluation` | ✅ `set-evaluation-behavior` (added in `0ce3ce8`) | ✅ `set-evaluation-behavior` | +| No-data / error handling | milestone 01 `04-builder-evaluation` | ✅ `configure-handling` | ✅ `configure-handling` | +| Recovery threshold | milestone 02 `04-code-complete-rule` | n/a (not taught in milestone 01) | ✅ `configure-handling / set-recovery-threshold` | + +## Debugging note captured during the branch + +Grafana's **New evaluation group** button in section 4 of the alert rule page is disabled (native 🚫 cursor) until a folder is selected in section 3. This looks like a Pathfinder overlay is blocking clicks but it's Grafana's own form validation. Do not add Pathfinder workarounds — the click succeeds once the learner completes the folder step in `configure-labels-evaluation`. + +## Reviewer requests + +- Merge strategy: squash-merge preferred so authorship consolidates on the merger. `Co-authored-by:` trailer is not required. +- These changes are intended to land as part of PR #377. If PR #482 merges into `docs/lp-basic-alerting` first, PR #377 carries everything to `main` when it merges. From a5920aeddc103ff326d4bfce6290eaf6aa121c6b Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Tue, 28 Jul 2026 14:49:21 -0500 Subject: [PATCH 17/19] alerting-create-silence-lj: rework create-silence milestone - Replace "silence form" wording with "Silences page" throughout - Retitle section 1 to "Start a new silence" - Split Silence start and end and Duration steps so Show me follows the ask - Spell out valid duration units to avoid single-char copy chips - Switch matcher example to severity=critical to match earlier LJ sections - Split matcher-value step so Show me appears immediately after the ask - Concrete team=platform + severity=critical example on Add matcher - Rewrite Comment step to replace auto-populated timestamp text - Frame Affected alert instances preview around earlier-section alerts - Use label:contains('Silence start and end') for the time-range field --- .../create-silence/content.json | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/alerting-create-silence-lj/create-silence/content.json b/alerting-create-silence-lj/create-silence/content.json index 55dedadc..d11e3842 100644 --- a/alerting-create-silence-lj/create-silence/content.json +++ b/alerting-create-silence-lj/create-silence/content.json @@ -5,12 +5,12 @@ "blocks": [ { "type": "markdown", - "content": "You'll open the silence form, set a time window, add label matchers that select which alerts to suppress, verify the affected instances, and submit.\n\nIn this milestone, you create a silence from start to finish." + "content": "You'll open the **Silences** page, set a time window, add label matchers that select which alerts to suppress, verify the affected instances, and submit.\n\nIn this milestone, you create a silence from start to finish." }, { "type": "section", "id": "open-silence-form", - "title": "Open the silence form", + "title": "Start a new silence", "blocks": [ { "type": "interactive", @@ -22,8 +22,8 @@ { "type": "interactive", "action": "highlight", - "reftarget": "a[href*='/alerting/silence/new']", - "content": "Click **Add silence** to open the silence creation form.", + "reftarget": "a[href*='alerting/silence/new']", + "content": "Click **Create silence** to start a new silence.", "requirements": ["on-page:/alerting/silences"], "verify": "on-page:/alerting/silence/new" } @@ -35,18 +35,31 @@ "title": "Set the silence time window", "requirements": ["section-completed:open-silence-form"], "blocks": [ + { + "type": "interactive", + "action": "highlight", + "reftarget": "label:contains('Silence start and end')", + "content": "The **Silence start and end** field shows two timestamps: when the silence starts and when it ends. By default, the start is now and the end is two hours from now, so the silence lasts two hours.", + "doIt": false, + "requirements": ["on-page:/alerting/silence/new"] + }, { "type": "interactive", "action": "noop", - "content": "In the **Silence start and end** picker, set when the silence begins and ends. For a maintenance window, set the start to now and the end to when the work finishes." + "content": "If you need custom times, click the field to open an **Absolute time range** menu, set **From** and **To**, then click **Apply time range**. Otherwise, skip to the **Duration** field." }, { "type": "interactive", "action": "highlight", "reftarget": "#duration", - "content": "Alternatively, set the **Duration** field and Grafana calculates the end time for you.\n\nUse a duration that matches the work, for example, `2h` for a short maintenance window or `1d` for a longer migration. Valid units: `y`, `M`, `w`, `d`, `h`, `m`, `s`.", + "content": "Set the **Duration** field to how long you want the silence to last, and Grafana calculates the end time for you.", "doIt": false, "requirements": ["on-page:/alerting/silence/new"] + }, + { + "type": "interactive", + "action": "noop", + "content": "The default `2h` works for a short deployment. Use `1d` for a longer migration. Valid units are years (y), months (M), weeks (w), days (d), hours (h), minutes (m), and seconds (s)." } ] }, @@ -60,7 +73,7 @@ "type": "interactive", "action": "highlight", "reftarget": "#matcher-0-label", - "content": "Under **Refine affected alerts**, enter the label name to match in the **Label** field, for example, `severity` or `team`.", + "content": "Under **Refine affected alerts**, enter a label that's on one of your alerts. If you followed the first-rule journey, copy `severity` and paste it into the **Label** field.", "doIt": false, "requirements": ["on-page:/alerting/silence/new"] }, @@ -68,15 +81,20 @@ "type": "interactive", "action": "highlight", "reftarget": "#matcher-0-value", - "content": "Enter the matching value in the **Value** field, for example, `warning` or `database`. The operator defaults to equals (`=`); change it if you need a regex or not-equals match.\n\nOnly alerts whose labels match every matcher you add are silenced. The more matchers you add, the narrower the silence.", + "content": "Copy `critical` and paste it into the **Value** field so the matcher becomes `severity=critical`. The operator defaults to equals (`=`). Change it if you need a regex or not-equals match.", "doIt": false, "requirements": ["on-page:/alerting/silence/new"] }, + { + "type": "interactive", + "action": "noop", + "content": "Only alerts whose labels match every matcher you add are silenced. The more matchers you add, the narrower the silence." + }, { "type": "interactive", "action": "highlight", "reftarget": "button:contains('Add matcher')", - "content": "Click **Add matcher** to add another label matcher if you need to narrow the silence further.", + "content": "Click **Add matcher** if you want to narrow the silence further, for example, adding `team=platform` alongside `severity=critical` silences only critical alerts owned by the platform team.", "doIt": false, "requirements": ["on-page:/alerting/silence/new"], "skippable": true @@ -92,13 +110,13 @@ { "type": "interactive", "action": "noop", - "content": "Review the **Affected alert instances** preview below the matchers. It lists the alert instances your matchers currently select.\n\nIf the preview is empty or includes alerts you didn't intend to silence, adjust your matchers before submitting." + "content": "Review the **Affected alert instances** preview below the matchers. It lists any alert instances currently firing that match your matchers.\n\nWith `severity=critical`, you should see the alerts you created in the previous sections. If the preview is empty, the silence still catches alerts that start firing during the window." }, { "type": "interactive", "action": "highlight", "reftarget": "#comment", - "content": "Enter a **Comment** describing why you're creating the silence, for example, `Database upgrade maintenance window`. This helps your team understand the silence later.", + "content": "Replace the auto-populated text in the **Comment** field with a short reason, for example, `Database upgrade in progress`. Grafana records the creation time separately, so the timestamp isn't needed here.", "doIt": false, "requirements": ["on-page:/alerting/silence/new"] }, From b3e10f221ecab86133fb8321e5a3df37364309fc Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Tue, 28 Jul 2026 15:45:32 -0500 Subject: [PATCH 18/19] PR #482: restructure and expand reviewer notes - Reorganize by Added (new content) vs Changed (reworked content) - Retag principles as learner-experience / Pathfinder workaround / intentional-feature - Add before/after examples to all 9 guiding principles - Explain query-language-lj rationale: practice code mode + build a second rule - Expand install-datasources: pre-branch told users to install as prerequisite - Rewrite merge instructions for JohnnyK as two clear steps - Verify all concrete claims (selectors, values, titles) against files --- PR-482-REVIEW-NOTES.md | 255 +++++++++++++++++++++++++---------------- 1 file changed, 158 insertions(+), 97 deletions(-) diff --git a/PR-482-REVIEW-NOTES.md b/PR-482-REVIEW-NOTES.md index 329669fe..998b5bd7 100644 --- a/PR-482-REVIEW-NOTES.md +++ b/PR-482-REVIEW-NOTES.md @@ -1,151 +1,205 @@ # PR #482 review notes -Rationale for every commit stacked on top of `docs/lp-basic-alerting` (PR #377) for reviewers of PR #482. Read this before the file diff — it explains **why** each interaction was added, reworded, or reordered. +Rationale for the 16 commits stacked on top of `docs/lp-basic-alerting` (PR #377). Organized by what was **added** (net-new content) vs what was **changed** (existing content reworked). + +## What's in this PR + +**Added:** + +- New learning path: **`alerting-query-language-lj`** (12 milestones). +- New milestone in `alerting-first-rule-lj`: **`install-datasources`**. +- New milestone in `alerting-first-rule-lj`: **`configure-notification`**. +- New section in `alerting-first-rule-lj/set-evaluation-behavior`: **`set-keep-firing-for`**. + +**Changed:** + +- **`alerting-first-rule-lj`** — 10 existing milestones reworked end to end for concrete QuickPizza copy, plain language, and stable selectors. +- **`alerting-create-silence-lj/create-silence`** — reworked for updated UI vocabulary, step splitting so **Show me** follows the ask, and a matcher example that aligns with the earlier LJs. ## Stack shape - PR #377 (JohnnyK-Grafana): `main` ← `docs/lp-basic-alerting` — original first-rule learning path packages. -- PR #482 (Beverly): `docs/lp-basic-alerting` ← `docs/lp-basic-alerting-install-datasources` — everything below. +- PR #482 (Beverly): `docs/lp-basic-alerting` ← `docs/lp-basic-alerting-install-datasources` — everything documented here. -Merging PR #482 into `docs/lp-basic-alerting` folds all these commits into PR #377's changeset so they land on `main` together. +PR #482 targets the branch PR #377 is landing, not `main` directly. See [Reviewer requests](#reviewer-requests) for the merge order. -## Guiding principles applied across the branch +## Guiding principles -These principles drove most of the reworks. When you see a commit that changes an interaction, one or more of these is the reason. +Every rework in this PR follows the same rules. Each is tagged as a **learner-experience** improvement (a choice about wording, structure, or specificity that makes the tutorial easier for a person to follow), a **Pathfinder workaround** (compensating for a constraint in how the Pathfinder sidebar renders steps), or a **Pathfinder feature used intentionally**. -1. **Concrete over generic.** Every step targets QuickPizza with real metric names, real threshold values, and real annotation copy — not `` or "your metric here." -2. **Concept-first, plain language.** Section intros teach a concept in one paragraph. Jargon like "responders," "template variables," "rule-level labels ride along," "comparison operator," "signal," "annotations," "service_name" was removed and replaced with what the reader is doing and why. -3. **One numbered step per action.** Every `interactive` block has an action attached. Standalone `markdown` blocks between interactive steps caused lonely numbered lines when requirements gated the next block, so intros were folded into the interactive step's `content` field. -4. **Single path, no branching prose.** Sections that used to say "if the picker is enabled do X, otherwise do Y" were reduced to the one path the learner will actually see. Reference tables (Urgency/Interval, Alert type/Pending period) were dropped in favor of a single concrete recommendation. -5. **UI wording matches current Grafana docs (v11.1+).** The alerting UI is called the "new alert rule page," not "form" or "creation form" — this matches the Grafana docs and the button label (**+ New alert rule**). -6. **Stable selectors.** Where text-based `reftarget` was ambiguous or duplicated (e.g. multiple **Save** buttons), we switched to `[data-testid=...]` selectors verified against a live Grafana instance. -7. **Modal-aware copy.** Steps that open a Grafana modal put the instructional detail inside a `noop` after the button click, because the modal covers the sidebar. Short, dropdown-name-first wording lets the learner read the step even with the modal open. -8. **`doIt: false` where the learner should own the outcome.** Save, contact-point selection, and any step that opens a destructive dialog leave the click to the user. `doIt: true` is used only for expand/collapse toggles and modal openers. +1. **Concrete over generic.** _(Learner experience.)_ Every step targets QuickPizza with real metric names, threshold values, and annotation copy — not `` or "your metric here." Dictated examples keep the learner focused on the flow (build a rule end to end) instead of stalling to compare options. "What threshold should I pick?" or "what label should I add?" is a distraction on a first pass — the goal is learning how alert rules are structured, not choosing the perfect values. A specific recommendation (`severity=critical`, `1m` interval, threshold `0`) lets the learner move through the whole flow once, then explore variations on their own afterward. + - **Before:** "enter your metric name in the query field." + - **After:** "In the query field, enter `quickpizza_server_http_requests_total` and filter `status=~\"5..\"` to catch server errors." +2. **Concept-first, plain language.** _(Learner experience.)_ Jargon like "responders," "template variables," "rule-level labels ride along," "comparison operator," "signal," "annotations," and "service_name" was removed and replaced with what the reader is doing and why. + - **Before:** "Configure the comparison operator and threshold for the alert condition to define the signal." + - **After:** "Choose when the alert fires. Set it to fire whenever the count of 5xx errors is above `0`." +3. **One numbered step per action.** _(Pathfinder workaround.)_ The Pathfinder sidebar auto-numbers every block as a step in the checklist, whether the block has an action button attached or not. Standalone `markdown` blocks between interactive steps render as their own numbered step with no button — a floating sentence with nothing for the learner to click. Section intros were folded into the following interactive step's `content` field, so every numbered step in the sidebar has a matching **Do it** or **Show me** button next to it. + - **Before:** a section starts with a `markdown` block ("Now you'll configure the alert condition.") followed by an `interactive` block that highlights **Is above**. The sidebar shows step 1 with the intro sentence and no button, and step 2 with the button. Step 1 looks broken — there's nothing to click. + - **After:** the intro sentence is prepended to the `interactive` block's `content` field. The sidebar shows one step: "Now you'll configure the alert condition. Click **Is above**." — with a button attached. No orphan sentences. +4. **Single path, no branching prose.** _(Learner experience.)_ "If the picker is enabled do X, otherwise do Y" was reduced to the path the learner will actually see. Reference tables (Urgency/Interval, Alert type/Pending period) were dropped in favor of one concrete recommendation. + - **Before:** a table listing five urgency levels mapped to five evaluation intervals, asking the learner to choose. + - **After:** "Set the evaluation interval to `1m`. Short intervals catch issues quickly; longer intervals reduce query load." +5. **UI wording matches current Grafana docs (v11.1+).** _(Learner experience.)_ The alerting UI is called "the **new alert rule** page" (not "form"); the silence UI is called "the **Silences** page." Wire-level IDs, folder names, section IDs, and requirement tokens were not touched. + - **Before:** "In the silence form, enter the labels you want to match." + - **After:** "On the **Silences** page, click **Create silence** to open the new silence page." +6. **Stable selectors.** _(Pathfinder workaround.)_ Ambiguous text-based `reftarget` (e.g. multiple **Save** buttons on the page) was replaced with `[data-testid=...]` verified against a live Grafana instance. Pathfinder resolves `reftarget` by first DOM match, so ambiguous text sends **Show me** to the wrong element. + - **Before:** `"reftarget": "Save"` — matched the header **Save** button, the folder-modal **Save** button, and the rule-page **Save** button. + - **After:** `"reftarget": "[data-testid='save-rule']"` — matches only the alert-rule save button. +7. **Modal-aware copy.** _(Pathfinder workaround.)_ Steps that open a Grafana modal put instructional detail in a `noop` after the button click, because the modal covers the sidebar. Short, dropdown-name-first wording lets the learner read the step even with the modal open. + - **Before:** one step that highlights **New folder**, tells the learner what to type in the modal's **Name** field, and to click **Create** — with the modal covering the sidebar the moment step 1 executes. + - **After:** step 1 is a `button` click on **New folder**; step 2 is a `noop` that reads "In the **Name** field, enter a folder name, then click **Create**" — visible next to the open modal. +8. **`doIt: false` where the learner should own the outcome.** _(Pathfinder feature used intentionally.)_ Save, contact-point selection, and any destructive step leave the click to the user. `doIt: true` is used only for expand/collapse toggles and modal openers. + - **`doIt: false`:** `save-rule` (creating an active alert rule), `select-contact-point` (picking which team gets paged). + - **`doIt: true`:** `configure-handling` (expand the collapsed section so the learner can see the fields), `configure-labels-evaluation` (open the labels modal). +9. **Show me follows the ask.** _(Pathfinder workaround.)_ The **Show me** button always anchors to the bottom of a step's `content` field. Long reference material moves to a follow-up `noop` so **Show me** lands directly under the actionable sentence, not under a paragraph of optional UI detail. + - **Before:** one `highlight` step whose `content` was "Set the **Duration** field to how long you want the silence to last. Valid units are years (y), months (M), weeks (w), days (d), hours (h), minutes (m), and seconds (s). The default `2h` works for a short deployment…" — **Show me** rendered under the units list, four sentences below the ask. + - **After:** step 1 is a `highlight` with just "Set the **Duration** field to how long you want the silence to last, and Grafana calculates the end time for you"; step 2 is a `noop` with the units list. **Show me** now sits directly under the ask. -## Commit-by-commit rationale (oldest → newest) +## Added -### `b1f322d` alerting-first-rule-lj: add install-datasources milestone +### `alerting-query-language-lj` — new learning path (`1e472aa`) -**Why.** The path assumed the QuickPizza data source was already installed. Learners hitting the LJ fresh had no way to get it. Added a Pathfinder-driven install step using the shared `tutorial-datasources.json` template so the LJ is self-sufficient. +The learning-hub milestone `content/docs/learning-hub/basic-alerting/02-alert-with-query-language/` teaches the same first-rule flow with a PromQL/ratio query and adds one new concept (recovery threshold). There was no corresponding interactive LJ. -**Interaction added.** `install-datasources` milestone using the shared install pattern; manifest rewired to run `business-value → install-datasources → navigate-to-form`. +**Why a second LJ instead of extending the first one.** The first LJ (`alerting-first-rule-lj`) walks the learner through Builder mode, where Grafana composes the query from dropdowns. This second LJ exists so the learner can: -### `9b3a886` alerting-first-rule-lj: replace "form" with "new alert rule page" +- **Practice code mode.** They write PromQL directly in the query editor instead of picking from dropdowns — the same rule idea, but authored differently. This is the payoff for the Builder-mode primer they got in the first LJ. +- **Build a second alert rule from scratch.** Repeating the full create-a-rule flow (name → query → condition → labels → evaluation → handling → contact point → notification → save) reinforces every step of the first LJ. By the end, the learner has built two rules and knows the flow well enough to build a third one on their own. -**Why.** Grafana's alerting docs (v11.1+) describe this UI by page name, not as a "form." Learner-facing copy that says "the form" doesn't match what the docs, the button label, or the browser tab title say. Wire-level IDs, folder names, section IDs, and requirement tokens were not touched — only the human-visible copy. +**Structure.** 12 milestones mirroring `alerting-first-rule-lj` so the learner recognizes every step from the first path — the only truly new material is code mode, the ratio query semantics, and recovery threshold. -### `3bba6b1` alerting-first-rule-lj: rework query to watch QuickPizza 5xx errors +**Where it deliberately differs from `alerting-first-rule-lj`:** -**Why.** The prior query was generic and abstracted. Learners built a rule without ever seeing what it detects. Rewritten to build `sum(increase(quickpizza_server_http_requests_total{status=~"5.."}[5m]))` in Builder mode, pre-filling values where the Grafana combobox allows. +- `build-query` — writes PromQL directly instead of using Builder mode. Query is a ratio: `sum(rate(5xx)) / sum(rate(total))`. +- `set-condition` — threshold is `5` (percent). Section framing acknowledges the ratio's semantics (not "any errors"). +- `configure-labels-evaluation` — intro reframed as review because the learner met these settings in the first rule. `severity=critical` rationale lives on the parent button step; modal-covered instruction is short and dropdown-first. +- `set-evaluation-behavior` — intro review-framed. Includes all three settings; recommends leaving **Keep firing for** at **None** because the recovery-threshold set two milestones later handles flapping for a ratio query. +- `configure-handling` — keeps the existing no-data / error defaults, plus a **skippable** `set-recovery-threshold` section that turns on Custom recovery threshold and sets the recovery value to `3`. Intro teaches recovery threshold as value-based dampening (fire above 5, recover below 3). +- `configure-notification` — **Summary** uses `{{ $value | printf "%.1f" }}%` to format the ratio as a percentage; **Description** names the ratio explicitly. Intro calls out the difference vs first-rule (count → percentage) so the reader understands why the templates differ. -**Downstream ripples.** -- `set-condition`: threshold changed from `1` (req/s) to `0` (any errors); "traffic surge" framing dropped. -- `configure-labels-evaluation`, `save-rule`: `service_name=quickpizza-public-api` references removed because the aggregation drops labels. -- `save-rule` summary annotation now uses `{{ $value }}` since `sum()` aggregates labels away. +### `alerting-first-rule-lj/install-datasources` — new milestone (`b1f322d`) -### `2feb7c2` alerting-first-rule-lj: simplify build-query intro +The pre-branch LJ told the learner to install QuickPizza as a prerequisite — outside the tutorial, before starting. The `business-value` milestone said: _"This path uses the QuickPizza tutorial data sources, which you install as a prerequisite before you begin."_ In practice that meant the learner had to leave the sidebar, install a Prometheus data source manually (add it, point it at the right URL, name it correctly), come back, and hope they set it up the way the rest of the LJ expected. -**Why.** The opening paragraph was dense and mixed concept + steps. Split into a one-line lede, a plain-language bullet list of what the learner will do, and a one-sentence payoff. +**What this milestone does.** The install step now runs inside the tutorial. The Pathfinder interaction installs the QuickPizza data source for the learner in one click, wired up exactly the way the rest of the LJ expects. The learner stays in the sidebar the whole time and doesn't have to context-switch to Grafana's data-source pages before they can start building the rule. -### `60672a1` alerting-first-rule-lj: collapse section intros and pre-fill alert name +- Uses the shared `tutorial-datasources.json` template so this LJ installs QuickPizza the same way other tutorials do. +- Manifest rewired to run `business-value → install-datasources → navigate-to-form`, so the install happens before the learner is asked to pick a data source in `build-query`. +- The "install as a prerequisite" sentence in `business-value` is no longer needed and was removed. -**Why.** Sections had a markdown intro block followed by an interactive step gated on `on-page:...`. When the requirement wasn't met, the intro rendered as a lonely numbered sentence with no action next to it. Folded every section's lede markdown into the immediately-following interactive step's `content` field. +### `alerting-first-rule-lj/configure-notification` — new milestone (`1090f17`) -Also pre-filled the alert rule name (`QuickPizza server errors`) via `formfill` on `navigate-to-form` so it matches the query the learner builds in the next milestone. +Notification-message setup was previously bundled into `save-rule`, where it competed with save-and-activate for attention. -### `9cffeeb` alerting-first-rule-lj: tighten alert-name step copy +- Two `formfill` steps for **Summary** and **Description** with QuickPizza-specific values. +- Manifest updated to insert `configure-notification` between `select-contact-point` and `save-rule`. +- Recap bullets in `business-value` and `end-journey` split so the contact-point and annotation concepts aren't glued together. -**Why.** The alert-name step still had a three-block intro + formfill + trailing summary. Collapsed into one interactive step (intro sentence, why-it-matters sentence, formfill instruction) with the next-milestone line as the transition noop. +### `alerting-first-rule-lj/set-evaluation-behavior/set-keep-firing-for` — new section (`0ce3ce8`) -### `5c85ce6` alerting-first-rule-lj: fix build-query selectors and step wording +The learning-hub milestone `01-first-alert-no-query-language/04-builder-evaluation/index.md` teaches three evaluation-behavior settings: evaluation group, pending period, and **Keep firing for**. The LJ had the first two but not the third. -**Why.** Preview against live Grafana revealed the selectors: -- Operator picker: needs `[data-testid='data-testid Select match operator']`; step reworded to "Show me only" with no skip option. -- Explain toggle: needs `[data-testid='data-testid prometheus explain switch wrapper']`; section moved to run immediately after the data source pick, where it's actually useful. -- Value: reworded to "In the **Select value** box, enter 5.." to match the visible field label. +- New section `set-keep-firing-for` added after `set-pending-period`. +- Highlights `#keep-firing-for-input` with `doIt: false` and recommends leaving it at **None** — the query is a raw count, not a hovering ratio, so there's no flapping to dampen. +- Parent milestone's intro markdown updated to name all three settings. -### `82609fd` alerting-first-rule-lj: use stable Select value testid in build-query +## Changed -**Why.** Text-based `reftarget` on "Select value" matched multiple pickers on the page. Switched to a stable data-testid to disambiguate. +### `alerting-first-rule-lj/business-value` (`6728fa5`, `1090f17`) -### `2b70e75` alerting-first-rule-lj: rework set-condition milestone +- Dense hook + list paragraph split into a one-sentence hook and a scannable bullet list of what the learner will build. +- Contact-point and annotation bullets separated so they don't read as a single concept. -**Why.** Sections were titled with jargon ("Configure the threshold condition"), the intros repeated the same idea twice, and there were redundant markdown blocks. +### `alerting-first-rule-lj/navigate-to-form` and LJ intro (`9b3a886`, `60672a1`, `9cffeeb`) -- Sections retitled to "Choose when the alert fires" and "Preview rule" (learner outcomes, not UI vocabulary). -- Alert-condition area highlighted via the **WHEN QUERY** label instead of a fragile container selector. -- Is-above button scoped under `alert-rule step-2` so it doesn't match the same button elsewhere. -- Dropped "comparison operator" and "threshold" jargon; used plain language. -- Removed the trailing markdown wrap-up; folded into the final noop. +- "Form" replaced with "new alert rule page" throughout learner-facing copy. Matches the Grafana docs, the button label (**+ New alert rule**), and the browser tab title. +- Alert-rule name pre-filled (`QuickPizza server errors`) via `formfill` so it aligns with the query built in `build-query`. +- Alert-name step collapsed from three blocks (intro + formfill + summary) into one interactive step with a transition `noop`. -### `fee7182` alerting-first-rule-lj: rework configure-labels-evaluation milestone +### `alerting-first-rule-lj/build-query` (`3bba6b1`, `2feb7c2`, `5c85ce6`, `82609fd`) -**Why.** The intro leaned on `service_name` and "Sum step" terms that no longer applied after the query rework in `3bba6b1`. The section for creating a folder branched on whether the picker was disabled. +- Query rewritten to `sum(increase(quickpizza_server_http_requests_total{status=~"5.."}[5m]))`, built in Builder mode with values pre-filled where the Grafana combobox allows. +- Intro simplified to a one-line lede, a plain-language bullet list, and a one-sentence payoff. +- Selectors verified against live Grafana: operator picker uses `[data-testid='data-testid Select match operator']`; explain toggle uses `[data-testid='data-testid prometheus explain switch wrapper']` and moved to run right after the data source pick where it's useful; value field switched to a stable data-testid because text-based `reftarget` on "Select value" matched multiple pickers. -- Retitled to "Create a folder and add labels." -- Intro rewritten as a plain routing summary (no `annotations`, no `template variables` mentions). -- Section 1 reduced to a single create-folder path: click **New folder**, then a `noop` for name + Create inside the modal. -- Section 2: click **Add labels**, then a modal-aware `noop` with a concrete `severity=critical` example, then a brief transition. -- Dropped the reference-dump markdown table and the orphan wrap-up. +### `alerting-first-rule-lj/set-condition` (`2b70e75`) -### `fb29d42` alerting-first-rule-lj: rework set-evaluation-behavior milestone +- Section titles rewritten around learner outcomes ("Choose when the alert fires", "Preview rule") instead of UI vocabulary ("Configure the threshold condition"). +- Alert-condition area highlighted via the **WHEN QUERY** label instead of a fragile container selector; Is-above button scoped under `alert-rule step-2` to disambiguate. +- Threshold changed from `1` req/s to `0` (any errors) to match the new aggregation. "Traffic surge" framing dropped. +- Jargon ("comparison operator", "threshold") replaced with plain language; trailing wrap-up markdown folded into the final `noop`. -**Why.** The milestone had two reference tables (urgency vs interval, and alert-type vs pending period) that made the reader decide something they don't need to decide in a first-rule tutorial. Removed both. +### `alerting-first-rule-lj/configure-labels-evaluation` (`fee7182`) -- Section 1 retitled to "Create an evaluation group" (single path — no more "if you have an existing group, use it" branch). -- Replaced `highlight` on modal fields with a `button` click for the modal opener + `noop` for the modal's contents (which cover the sidebar). -- Concrete recommendations: 1m interval, 1m pending period. No decision tree. -- Trailing wrap-up markdown converted to a noop transition. +- Retitled "Create a folder and add labels" (learner outcome, not UI vocabulary). +- Intro rewritten as a plain routing summary; `annotations` and `template variables` references removed. +- Section 1 reduced to a single create-folder path (click **New folder**, `noop` for name + Create inside the modal — no more "if the picker is disabled" branch). +- Section 2: click **Add labels**, modal-aware `noop` with a concrete `severity=critical` example, brief transition. Reference-dump markdown table dropped. +- `service_name=quickpizza-public-api` references removed downstream because the new `sum()` aggregation drops labels. -### `1090f17` alerting-first-rule-lj: rework configure-handling, select-contact-point, save-rule; add configure-notification milestone +### `alerting-first-rule-lj/set-evaluation-behavior` (`fb29d42`) -**Why.** The tail end of the LJ had four issues: -1. `configure-handling` explained no-data / error handling in dense reference-doc language. -2. `select-contact-point` mixed picker interaction, a table of options, and a decision paragraph. -3. `save-rule` bundled the notification-message setup with the actual save. -4. Notification-message setup wasn't its own milestone, so it competed with save-and-activate for attention. +_(The new `set-keep-firing-for` section added to this milestone is under [Added](#alerting-first-rule-ljset-evaluation-behaviorset-keep-firing-for--new-section-0ce3ce8).)_ -- `configure-handling`: plain-language intro; auto-expand the collapse; keep-defaults rationale explaining why defaults are safest for a first rule; noop transition. -- `select-contact-point`: one `highlight` step on the picker with a concrete recommendation of `grafana-default-email`; transition noop. -- `configure-notification` (new milestone): two `formfill` steps for Summary and Description with QuickPizza-specific values. -- `save-rule` slimmed to just save-and-activate with `doIt: false` — the learner presses **Save**. -- Manifest updated to insert `configure-notification` between `select-contact-point` and `save-rule`. -- Business-value and end-journey recap bullets split so contact point and annotation aren't glued together. +- Two reference tables (urgency vs interval, alert-type vs pending period) dropped. Learner doesn't need to decide these in a first rule. +- Section 1 retitled "Create an evaluation group" — no more "if you have an existing group, use it" branch. +- `highlight` on modal fields replaced with `button` click for the modal opener + `noop` for modal contents (which cover the sidebar). +- Concrete recommendations baked in: 1m interval, 1m pending period. No decision tree. + +### `alerting-first-rule-lj/configure-handling` (`1090f17`) + +- Plain-language intro. Auto-expand the collapse. Keep-defaults rationale explaining why defaults are safest for a first rule. `noop` transition. + +### `alerting-first-rule-lj/select-contact-point` (`1090f17`) + +- Reduced from picker + options table + decision paragraph to one `highlight` step on the picker with a concrete recommendation of `grafana-default-email`. Transition `noop`. -### `6728fa5` alerting-first-rule-lj: fix save-rule selector and tighten business-value +### `alerting-first-rule-lj/save-rule` (`1090f17`, `6728fa5`, `3bba6b1`) -**Why.** -- Save button text is just "Save" and matches multiple buttons on the page. Switched `save-rule` to a `highlight` action with `[data-testid='save-rule']`. -- Converted the trailing wrap-up markdown to a `noop` transition so it renders as a numbered step, not orphan text. -- Business-value copy had a dense hook + list paragraph. Split into a one-sentence hook and a scannable bullet list of what the learner will build. +- Slimmed to just save-and-activate; notification-message setup moved to the new `configure-notification` milestone. `doIt: false` — the learner presses **Save**. +- Selector switched from text-based `Save` (matched multiple buttons) to `[data-testid='save-rule']`; action switched to `highlight`. +- Summary annotation now uses `{{ $value }}` because `sum()` aggregates labels away. -### `0ce3ce8` alerting-first-rule-lj: add Keep firing for step to set-evaluation-behavior +### `alerting-first-rule-lj/end-journey` (`1090f17`, `6728fa5`) -**Why.** The learning-hub conceptual milestone `content/docs/learning-hub/basic-alerting/01-first-alert-no-query-language/04-builder-evaluation/index.md` teaches three evaluation-behavior settings: evaluation group, pending period, and **Keep firing for**. The LJ had the first two but not the third. This commit closes the gap so the LJ matches what the doc says the learner will encounter. +- Recap bullets split so contact point and annotation aren't glued together. +- Trailing wrap-up markdown converted to a `noop` so it renders as a numbered step, not orphan text. -- New section `set-keep-firing-for` after `set-pending-period`. -- Highlights `#keep-firing-for-input` with `doIt: false` and recommends leaving the setting at **None** for this rule (the query is a raw count, not a hovering ratio — no flapping to dampen). -- Intro markdown updated to name all three evaluation-behavior settings. +### `alerting-create-silence-lj/create-silence` (`a5920ae`) -### `1e472aa` alerting-query-language-lj: initial learning path +The milestone was written against old UI vocabulary ("silence form"), had branching prose for optional time-range picking, mixed action + long reference material in single steps (so **Show me** landed under the reference paragraph instead of under the ask), and used a matcher example (`team=warning`) that wouldn't match anything the learner had built earlier. -**Why.** The learning-hub milestone `content/docs/learning-hub/basic-alerting/02-alert-with-query-language/` teaches the same first-rule flow with a PromQL/ratio query and adds one new concept (recovery threshold). There was no corresponding interactive LJ. +**Vocabulary.** "Silence form" replaced with "the **Silences** page" throughout. Section 1 title changed to "Start a new silence"; click step is now "Click **Create silence** to start a new silence." -**Structure.** Mirrors `alerting-first-rule-lj` with 12 milestones so the learner recognizes every step from the first path. +**Time-window section.** -**Where it deliberately differs from first-rule-lj:** -- `build-query`: writes PromQL directly instead of using Builder mode. Query is a ratio (`sum(rate(5xx)) / sum(rate(total))`). -- `set-condition`: threshold is 5 (percent). Section framing acknowledges the ratio's semantics (not "any errors"). -- `configure-labels-evaluation`: intro reframed as review, because the learner met these settings in the first rule. `severity=critical` rationale is on the parent button step; modal-covered instruction is short and dropdown-first. -- `set-evaluation-behavior`: intro is review-framed. Includes the same three settings including **Keep firing for**, but recommends leaving Keep firing for at **None** because the recovery-threshold set two milestones later handles flapping for a ratio query. -- `configure-handling`: keeps the existing no-data / error defaults. Adds a **skippable** `set-recovery-threshold` section that turns on Custom recovery threshold and sets the recovery value to 3. Intro markdown teaches recovery threshold as value-based dampening (fire above 5, recover below 3). -- `configure-notification`: **Summary** uses `{{ $value | printf "%.1f" }}%` to format the ratio as a percentage; **Description** names the ratio explicitly. Intro markdown calls out the difference vs first-rule (count → percentage) so the reader understands why the templates differ. +- **Silence start and end** step split into a `highlight` with the concept (two timestamps, defaults to now + 2h) and a follow-up `noop` describing the optional **Absolute time range** menu. **Show me** now points at the field right after the concept. +- Same split applied to **Duration**: `highlight` with the ask, `noop` listing valid units. Units rendered as `years (y), months (M), …` instead of backtick-chip single letters (single-char copy chips look wrong in the sidebar). + +**Matcher examples.** + +- Concrete example switched from `team=warning` (would produce an empty preview) to `severity=critical`, which aligns with the labels the learner adds in `alerting-first-rule-lj/configure-labels-evaluation`. +- **Label** and **Value** steps use copy-chip framing ("copy `severity` and paste it into the **Label** field"). +- Value step split so **Show me** appears immediately after the ask, with the operator-defaults explanation folded in as a single sentence. +- **Add matcher** step (`skippable: true`) uses a concrete `team=platform` alongside `severity=critical` example instead of abstract "add another matcher" prose. + +**Verify-and-submit section.** + +- **Affected alert instances** preview step now references "the alerts you created in the previous sections" and explains an empty preview is fine (the silence still catches future firings). +- **Comment** field step instructs the learner to **replace** the auto-populated timestamp text with a short reason (e.g. `Database upgrade in progress`). Grafana records creation time separately in metadata. + +**Selectors.** + +- New label-based selector `label:contains('Silence start and end')` for the time-range field, matching the `label:contains('WHEN QUERY')` pattern already used in `set-condition`. +- Other selectors (`#duration`, `#matcher-0-label`, `#matcher-0-value`, `#comment`, `a[href*='alerting/silence/new']`, `button:contains('Add matcher')`, `Save silence` button-text) are DOM-verified. + +**Not touched.** Section IDs (`open-silence-form` still gates `set-time-window` even though the section title now says "Start a new silence"), requirement tokens, and manifest. ## Alignment with learning-hub docs -The two LJs align 1:1 with the conceptual content in `grafana/website` PR #30466: +The two rule-building LJs align 1:1 with the conceptual content in `grafana/website` PR #30466: -| Concept | learning-hub location | alerting-first-rule-lj | alerting-query-language-lj | +| Concept | learning-hub location | `alerting-first-rule-lj` | `alerting-query-language-lj` | |---|---|---|---| | Evaluation group | milestone 01 `04-builder-evaluation` | ✅ `set-evaluation-behavior` | ✅ `set-evaluation-behavior` | | Pending period | milestone 01 `04-builder-evaluation` | ✅ `set-evaluation-behavior` | ✅ `set-evaluation-behavior` | @@ -155,9 +209,16 @@ The two LJs align 1:1 with the conceptual content in `grafana/website` PR #30466 ## Debugging note captured during the branch -Grafana's **New evaluation group** button in section 4 of the alert rule page is disabled (native 🚫 cursor) until a folder is selected in section 3. This looks like a Pathfinder overlay is blocking clicks but it's Grafana's own form validation. Do not add Pathfinder workarounds — the click succeeds once the learner completes the folder step in `configure-labels-evaluation`. +Grafana's **New evaluation group** button in section 4 of the alert rule page is disabled (native 🚫 cursor) until a folder is selected in section 3. This looks like a Pathfinder overlay blocking clicks but it's Grafana's own form validation. Do not add Pathfinder workarounds — the click succeeds once the learner completes the folder step in `configure-labels-evaluation`. + +## For JohnnyK — how to accept these changes and stay as the author + +You own PR #377. This PR (#482) stacks a batch of edits on top of your branch so you don't have to redo any of it. When you land PR #377, everything here goes with it — and you stay as the author on `main`. + +**Step 1: Merge PR #482 into your branch.** +PR #482 targets `docs/lp-basic-alerting` (your branch), not `main`. Review it like any other PR. When you're happy, click **Squash and merge** on PR #482. That collapses my commits into one commit on your branch — the attribution on that commit doesn't matter, because it won't survive step 2. -## Reviewer requests +**Step 2: Squash-merge PR #377 to `main`.** +When PR #377 is ready to land, use **Squash and merge** on it. GitHub produces one commit on `main` — authored by you — that contains everything: your original learning-path work plus the additions from PR #482. That's the only commit that shows up in `git log` on `main`, and it's yours. -- Merge strategy: squash-merge preferred so authorship consolidates on the merger. `Co-authored-by:` trailer is not required. -- These changes are intended to land as part of PR #377. If PR #482 merges into `docs/lp-basic-alerting` first, PR #377 carries everything to `main` when it merges. +You don't need to touch any code on your side — just review and click merge. From 20b5596be5dea11392104adbd853fe7913a4801d Mon Sep 17 00:00:00 2001 From: BeverlyJaneJ Date: Tue, 28 Jul 2026 20:19:52 -0500 Subject: [PATCH 19/19] PR #482: remove reviewer notes This scratchpad was used for reviewer coordination and is no longer needed. Rationale for each commit lives in the commit messages themselves. --- PR-482-REVIEW-NOTES.md | 224 ----------------------------------------- 1 file changed, 224 deletions(-) delete mode 100644 PR-482-REVIEW-NOTES.md diff --git a/PR-482-REVIEW-NOTES.md b/PR-482-REVIEW-NOTES.md deleted file mode 100644 index 998b5bd7..00000000 --- a/PR-482-REVIEW-NOTES.md +++ /dev/null @@ -1,224 +0,0 @@ -# PR #482 review notes - -Rationale for the 16 commits stacked on top of `docs/lp-basic-alerting` (PR #377). Organized by what was **added** (net-new content) vs what was **changed** (existing content reworked). - -## What's in this PR - -**Added:** - -- New learning path: **`alerting-query-language-lj`** (12 milestones). -- New milestone in `alerting-first-rule-lj`: **`install-datasources`**. -- New milestone in `alerting-first-rule-lj`: **`configure-notification`**. -- New section in `alerting-first-rule-lj/set-evaluation-behavior`: **`set-keep-firing-for`**. - -**Changed:** - -- **`alerting-first-rule-lj`** — 10 existing milestones reworked end to end for concrete QuickPizza copy, plain language, and stable selectors. -- **`alerting-create-silence-lj/create-silence`** — reworked for updated UI vocabulary, step splitting so **Show me** follows the ask, and a matcher example that aligns with the earlier LJs. - -## Stack shape - -- PR #377 (JohnnyK-Grafana): `main` ← `docs/lp-basic-alerting` — original first-rule learning path packages. -- PR #482 (Beverly): `docs/lp-basic-alerting` ← `docs/lp-basic-alerting-install-datasources` — everything documented here. - -PR #482 targets the branch PR #377 is landing, not `main` directly. See [Reviewer requests](#reviewer-requests) for the merge order. - -## Guiding principles - -Every rework in this PR follows the same rules. Each is tagged as a **learner-experience** improvement (a choice about wording, structure, or specificity that makes the tutorial easier for a person to follow), a **Pathfinder workaround** (compensating for a constraint in how the Pathfinder sidebar renders steps), or a **Pathfinder feature used intentionally**. - -1. **Concrete over generic.** _(Learner experience.)_ Every step targets QuickPizza with real metric names, threshold values, and annotation copy — not `` or "your metric here." Dictated examples keep the learner focused on the flow (build a rule end to end) instead of stalling to compare options. "What threshold should I pick?" or "what label should I add?" is a distraction on a first pass — the goal is learning how alert rules are structured, not choosing the perfect values. A specific recommendation (`severity=critical`, `1m` interval, threshold `0`) lets the learner move through the whole flow once, then explore variations on their own afterward. - - **Before:** "enter your metric name in the query field." - - **After:** "In the query field, enter `quickpizza_server_http_requests_total` and filter `status=~\"5..\"` to catch server errors." -2. **Concept-first, plain language.** _(Learner experience.)_ Jargon like "responders," "template variables," "rule-level labels ride along," "comparison operator," "signal," "annotations," and "service_name" was removed and replaced with what the reader is doing and why. - - **Before:** "Configure the comparison operator and threshold for the alert condition to define the signal." - - **After:** "Choose when the alert fires. Set it to fire whenever the count of 5xx errors is above `0`." -3. **One numbered step per action.** _(Pathfinder workaround.)_ The Pathfinder sidebar auto-numbers every block as a step in the checklist, whether the block has an action button attached or not. Standalone `markdown` blocks between interactive steps render as their own numbered step with no button — a floating sentence with nothing for the learner to click. Section intros were folded into the following interactive step's `content` field, so every numbered step in the sidebar has a matching **Do it** or **Show me** button next to it. - - **Before:** a section starts with a `markdown` block ("Now you'll configure the alert condition.") followed by an `interactive` block that highlights **Is above**. The sidebar shows step 1 with the intro sentence and no button, and step 2 with the button. Step 1 looks broken — there's nothing to click. - - **After:** the intro sentence is prepended to the `interactive` block's `content` field. The sidebar shows one step: "Now you'll configure the alert condition. Click **Is above**." — with a button attached. No orphan sentences. -4. **Single path, no branching prose.** _(Learner experience.)_ "If the picker is enabled do X, otherwise do Y" was reduced to the path the learner will actually see. Reference tables (Urgency/Interval, Alert type/Pending period) were dropped in favor of one concrete recommendation. - - **Before:** a table listing five urgency levels mapped to five evaluation intervals, asking the learner to choose. - - **After:** "Set the evaluation interval to `1m`. Short intervals catch issues quickly; longer intervals reduce query load." -5. **UI wording matches current Grafana docs (v11.1+).** _(Learner experience.)_ The alerting UI is called "the **new alert rule** page" (not "form"); the silence UI is called "the **Silences** page." Wire-level IDs, folder names, section IDs, and requirement tokens were not touched. - - **Before:** "In the silence form, enter the labels you want to match." - - **After:** "On the **Silences** page, click **Create silence** to open the new silence page." -6. **Stable selectors.** _(Pathfinder workaround.)_ Ambiguous text-based `reftarget` (e.g. multiple **Save** buttons on the page) was replaced with `[data-testid=...]` verified against a live Grafana instance. Pathfinder resolves `reftarget` by first DOM match, so ambiguous text sends **Show me** to the wrong element. - - **Before:** `"reftarget": "Save"` — matched the header **Save** button, the folder-modal **Save** button, and the rule-page **Save** button. - - **After:** `"reftarget": "[data-testid='save-rule']"` — matches only the alert-rule save button. -7. **Modal-aware copy.** _(Pathfinder workaround.)_ Steps that open a Grafana modal put instructional detail in a `noop` after the button click, because the modal covers the sidebar. Short, dropdown-name-first wording lets the learner read the step even with the modal open. - - **Before:** one step that highlights **New folder**, tells the learner what to type in the modal's **Name** field, and to click **Create** — with the modal covering the sidebar the moment step 1 executes. - - **After:** step 1 is a `button` click on **New folder**; step 2 is a `noop` that reads "In the **Name** field, enter a folder name, then click **Create**" — visible next to the open modal. -8. **`doIt: false` where the learner should own the outcome.** _(Pathfinder feature used intentionally.)_ Save, contact-point selection, and any destructive step leave the click to the user. `doIt: true` is used only for expand/collapse toggles and modal openers. - - **`doIt: false`:** `save-rule` (creating an active alert rule), `select-contact-point` (picking which team gets paged). - - **`doIt: true`:** `configure-handling` (expand the collapsed section so the learner can see the fields), `configure-labels-evaluation` (open the labels modal). -9. **Show me follows the ask.** _(Pathfinder workaround.)_ The **Show me** button always anchors to the bottom of a step's `content` field. Long reference material moves to a follow-up `noop` so **Show me** lands directly under the actionable sentence, not under a paragraph of optional UI detail. - - **Before:** one `highlight` step whose `content` was "Set the **Duration** field to how long you want the silence to last. Valid units are years (y), months (M), weeks (w), days (d), hours (h), minutes (m), and seconds (s). The default `2h` works for a short deployment…" — **Show me** rendered under the units list, four sentences below the ask. - - **After:** step 1 is a `highlight` with just "Set the **Duration** field to how long you want the silence to last, and Grafana calculates the end time for you"; step 2 is a `noop` with the units list. **Show me** now sits directly under the ask. - -## Added - -### `alerting-query-language-lj` — new learning path (`1e472aa`) - -The learning-hub milestone `content/docs/learning-hub/basic-alerting/02-alert-with-query-language/` teaches the same first-rule flow with a PromQL/ratio query and adds one new concept (recovery threshold). There was no corresponding interactive LJ. - -**Why a second LJ instead of extending the first one.** The first LJ (`alerting-first-rule-lj`) walks the learner through Builder mode, where Grafana composes the query from dropdowns. This second LJ exists so the learner can: - -- **Practice code mode.** They write PromQL directly in the query editor instead of picking from dropdowns — the same rule idea, but authored differently. This is the payoff for the Builder-mode primer they got in the first LJ. -- **Build a second alert rule from scratch.** Repeating the full create-a-rule flow (name → query → condition → labels → evaluation → handling → contact point → notification → save) reinforces every step of the first LJ. By the end, the learner has built two rules and knows the flow well enough to build a third one on their own. - -**Structure.** 12 milestones mirroring `alerting-first-rule-lj` so the learner recognizes every step from the first path — the only truly new material is code mode, the ratio query semantics, and recovery threshold. - -**Where it deliberately differs from `alerting-first-rule-lj`:** - -- `build-query` — writes PromQL directly instead of using Builder mode. Query is a ratio: `sum(rate(5xx)) / sum(rate(total))`. -- `set-condition` — threshold is `5` (percent). Section framing acknowledges the ratio's semantics (not "any errors"). -- `configure-labels-evaluation` — intro reframed as review because the learner met these settings in the first rule. `severity=critical` rationale lives on the parent button step; modal-covered instruction is short and dropdown-first. -- `set-evaluation-behavior` — intro review-framed. Includes all three settings; recommends leaving **Keep firing for** at **None** because the recovery-threshold set two milestones later handles flapping for a ratio query. -- `configure-handling` — keeps the existing no-data / error defaults, plus a **skippable** `set-recovery-threshold` section that turns on Custom recovery threshold and sets the recovery value to `3`. Intro teaches recovery threshold as value-based dampening (fire above 5, recover below 3). -- `configure-notification` — **Summary** uses `{{ $value | printf "%.1f" }}%` to format the ratio as a percentage; **Description** names the ratio explicitly. Intro calls out the difference vs first-rule (count → percentage) so the reader understands why the templates differ. - -### `alerting-first-rule-lj/install-datasources` — new milestone (`b1f322d`) - -The pre-branch LJ told the learner to install QuickPizza as a prerequisite — outside the tutorial, before starting. The `business-value` milestone said: _"This path uses the QuickPizza tutorial data sources, which you install as a prerequisite before you begin."_ In practice that meant the learner had to leave the sidebar, install a Prometheus data source manually (add it, point it at the right URL, name it correctly), come back, and hope they set it up the way the rest of the LJ expected. - -**What this milestone does.** The install step now runs inside the tutorial. The Pathfinder interaction installs the QuickPizza data source for the learner in one click, wired up exactly the way the rest of the LJ expects. The learner stays in the sidebar the whole time and doesn't have to context-switch to Grafana's data-source pages before they can start building the rule. - -- Uses the shared `tutorial-datasources.json` template so this LJ installs QuickPizza the same way other tutorials do. -- Manifest rewired to run `business-value → install-datasources → navigate-to-form`, so the install happens before the learner is asked to pick a data source in `build-query`. -- The "install as a prerequisite" sentence in `business-value` is no longer needed and was removed. - -### `alerting-first-rule-lj/configure-notification` — new milestone (`1090f17`) - -Notification-message setup was previously bundled into `save-rule`, where it competed with save-and-activate for attention. - -- Two `formfill` steps for **Summary** and **Description** with QuickPizza-specific values. -- Manifest updated to insert `configure-notification` between `select-contact-point` and `save-rule`. -- Recap bullets in `business-value` and `end-journey` split so the contact-point and annotation concepts aren't glued together. - -### `alerting-first-rule-lj/set-evaluation-behavior/set-keep-firing-for` — new section (`0ce3ce8`) - -The learning-hub milestone `01-first-alert-no-query-language/04-builder-evaluation/index.md` teaches three evaluation-behavior settings: evaluation group, pending period, and **Keep firing for**. The LJ had the first two but not the third. - -- New section `set-keep-firing-for` added after `set-pending-period`. -- Highlights `#keep-firing-for-input` with `doIt: false` and recommends leaving it at **None** — the query is a raw count, not a hovering ratio, so there's no flapping to dampen. -- Parent milestone's intro markdown updated to name all three settings. - -## Changed - -### `alerting-first-rule-lj/business-value` (`6728fa5`, `1090f17`) - -- Dense hook + list paragraph split into a one-sentence hook and a scannable bullet list of what the learner will build. -- Contact-point and annotation bullets separated so they don't read as a single concept. - -### `alerting-first-rule-lj/navigate-to-form` and LJ intro (`9b3a886`, `60672a1`, `9cffeeb`) - -- "Form" replaced with "new alert rule page" throughout learner-facing copy. Matches the Grafana docs, the button label (**+ New alert rule**), and the browser tab title. -- Alert-rule name pre-filled (`QuickPizza server errors`) via `formfill` so it aligns with the query built in `build-query`. -- Alert-name step collapsed from three blocks (intro + formfill + summary) into one interactive step with a transition `noop`. - -### `alerting-first-rule-lj/build-query` (`3bba6b1`, `2feb7c2`, `5c85ce6`, `82609fd`) - -- Query rewritten to `sum(increase(quickpizza_server_http_requests_total{status=~"5.."}[5m]))`, built in Builder mode with values pre-filled where the Grafana combobox allows. -- Intro simplified to a one-line lede, a plain-language bullet list, and a one-sentence payoff. -- Selectors verified against live Grafana: operator picker uses `[data-testid='data-testid Select match operator']`; explain toggle uses `[data-testid='data-testid prometheus explain switch wrapper']` and moved to run right after the data source pick where it's useful; value field switched to a stable data-testid because text-based `reftarget` on "Select value" matched multiple pickers. - -### `alerting-first-rule-lj/set-condition` (`2b70e75`) - -- Section titles rewritten around learner outcomes ("Choose when the alert fires", "Preview rule") instead of UI vocabulary ("Configure the threshold condition"). -- Alert-condition area highlighted via the **WHEN QUERY** label instead of a fragile container selector; Is-above button scoped under `alert-rule step-2` to disambiguate. -- Threshold changed from `1` req/s to `0` (any errors) to match the new aggregation. "Traffic surge" framing dropped. -- Jargon ("comparison operator", "threshold") replaced with plain language; trailing wrap-up markdown folded into the final `noop`. - -### `alerting-first-rule-lj/configure-labels-evaluation` (`fee7182`) - -- Retitled "Create a folder and add labels" (learner outcome, not UI vocabulary). -- Intro rewritten as a plain routing summary; `annotations` and `template variables` references removed. -- Section 1 reduced to a single create-folder path (click **New folder**, `noop` for name + Create inside the modal — no more "if the picker is disabled" branch). -- Section 2: click **Add labels**, modal-aware `noop` with a concrete `severity=critical` example, brief transition. Reference-dump markdown table dropped. -- `service_name=quickpizza-public-api` references removed downstream because the new `sum()` aggregation drops labels. - -### `alerting-first-rule-lj/set-evaluation-behavior` (`fb29d42`) - -_(The new `set-keep-firing-for` section added to this milestone is under [Added](#alerting-first-rule-ljset-evaluation-behaviorset-keep-firing-for--new-section-0ce3ce8).)_ - -- Two reference tables (urgency vs interval, alert-type vs pending period) dropped. Learner doesn't need to decide these in a first rule. -- Section 1 retitled "Create an evaluation group" — no more "if you have an existing group, use it" branch. -- `highlight` on modal fields replaced with `button` click for the modal opener + `noop` for modal contents (which cover the sidebar). -- Concrete recommendations baked in: 1m interval, 1m pending period. No decision tree. - -### `alerting-first-rule-lj/configure-handling` (`1090f17`) - -- Plain-language intro. Auto-expand the collapse. Keep-defaults rationale explaining why defaults are safest for a first rule. `noop` transition. - -### `alerting-first-rule-lj/select-contact-point` (`1090f17`) - -- Reduced from picker + options table + decision paragraph to one `highlight` step on the picker with a concrete recommendation of `grafana-default-email`. Transition `noop`. - -### `alerting-first-rule-lj/save-rule` (`1090f17`, `6728fa5`, `3bba6b1`) - -- Slimmed to just save-and-activate; notification-message setup moved to the new `configure-notification` milestone. `doIt: false` — the learner presses **Save**. -- Selector switched from text-based `Save` (matched multiple buttons) to `[data-testid='save-rule']`; action switched to `highlight`. -- Summary annotation now uses `{{ $value }}` because `sum()` aggregates labels away. - -### `alerting-first-rule-lj/end-journey` (`1090f17`, `6728fa5`) - -- Recap bullets split so contact point and annotation aren't glued together. -- Trailing wrap-up markdown converted to a `noop` so it renders as a numbered step, not orphan text. - -### `alerting-create-silence-lj/create-silence` (`a5920ae`) - -The milestone was written against old UI vocabulary ("silence form"), had branching prose for optional time-range picking, mixed action + long reference material in single steps (so **Show me** landed under the reference paragraph instead of under the ask), and used a matcher example (`team=warning`) that wouldn't match anything the learner had built earlier. - -**Vocabulary.** "Silence form" replaced with "the **Silences** page" throughout. Section 1 title changed to "Start a new silence"; click step is now "Click **Create silence** to start a new silence." - -**Time-window section.** - -- **Silence start and end** step split into a `highlight` with the concept (two timestamps, defaults to now + 2h) and a follow-up `noop` describing the optional **Absolute time range** menu. **Show me** now points at the field right after the concept. -- Same split applied to **Duration**: `highlight` with the ask, `noop` listing valid units. Units rendered as `years (y), months (M), …` instead of backtick-chip single letters (single-char copy chips look wrong in the sidebar). - -**Matcher examples.** - -- Concrete example switched from `team=warning` (would produce an empty preview) to `severity=critical`, which aligns with the labels the learner adds in `alerting-first-rule-lj/configure-labels-evaluation`. -- **Label** and **Value** steps use copy-chip framing ("copy `severity` and paste it into the **Label** field"). -- Value step split so **Show me** appears immediately after the ask, with the operator-defaults explanation folded in as a single sentence. -- **Add matcher** step (`skippable: true`) uses a concrete `team=platform` alongside `severity=critical` example instead of abstract "add another matcher" prose. - -**Verify-and-submit section.** - -- **Affected alert instances** preview step now references "the alerts you created in the previous sections" and explains an empty preview is fine (the silence still catches future firings). -- **Comment** field step instructs the learner to **replace** the auto-populated timestamp text with a short reason (e.g. `Database upgrade in progress`). Grafana records creation time separately in metadata. - -**Selectors.** - -- New label-based selector `label:contains('Silence start and end')` for the time-range field, matching the `label:contains('WHEN QUERY')` pattern already used in `set-condition`. -- Other selectors (`#duration`, `#matcher-0-label`, `#matcher-0-value`, `#comment`, `a[href*='alerting/silence/new']`, `button:contains('Add matcher')`, `Save silence` button-text) are DOM-verified. - -**Not touched.** Section IDs (`open-silence-form` still gates `set-time-window` even though the section title now says "Start a new silence"), requirement tokens, and manifest. - -## Alignment with learning-hub docs - -The two rule-building LJs align 1:1 with the conceptual content in `grafana/website` PR #30466: - -| Concept | learning-hub location | `alerting-first-rule-lj` | `alerting-query-language-lj` | -|---|---|---|---| -| Evaluation group | milestone 01 `04-builder-evaluation` | ✅ `set-evaluation-behavior` | ✅ `set-evaluation-behavior` | -| Pending period | milestone 01 `04-builder-evaluation` | ✅ `set-evaluation-behavior` | ✅ `set-evaluation-behavior` | -| Keep firing for | milestone 01 `04-builder-evaluation` | ✅ `set-evaluation-behavior` (added in `0ce3ce8`) | ✅ `set-evaluation-behavior` | -| No-data / error handling | milestone 01 `04-builder-evaluation` | ✅ `configure-handling` | ✅ `configure-handling` | -| Recovery threshold | milestone 02 `04-code-complete-rule` | n/a (not taught in milestone 01) | ✅ `configure-handling / set-recovery-threshold` | - -## Debugging note captured during the branch - -Grafana's **New evaluation group** button in section 4 of the alert rule page is disabled (native 🚫 cursor) until a folder is selected in section 3. This looks like a Pathfinder overlay blocking clicks but it's Grafana's own form validation. Do not add Pathfinder workarounds — the click succeeds once the learner completes the folder step in `configure-labels-evaluation`. - -## For JohnnyK — how to accept these changes and stay as the author - -You own PR #377. This PR (#482) stacks a batch of edits on top of your branch so you don't have to redo any of it. When you land PR #377, everything here goes with it — and you stay as the author on `main`. - -**Step 1: Merge PR #482 into your branch.** -PR #482 targets `docs/lp-basic-alerting` (your branch), not `main`. Review it like any other PR. When you're happy, click **Squash and merge** on PR #482. That collapses my commits into one commit on your branch — the attribution on that commit doesn't matter, because it won't survive step 2. - -**Step 2: Squash-merge PR #377 to `main`.** -When PR #377 is ready to land, use **Squash and merge** on it. GitHub produces one commit on `main` — authored by you — that contains everything: your original learning-path work plus the additions from PR #482. That's the only commit that shows up in `git log` on `main`, and it's yours. - -You don't need to touch any code on your side — just review and click merge.