diff --git a/.github/workflows/grafana-sync-check.yml b/.github/workflows/grafana-sync-check.yml index 6d35db9e..d07dc529 100644 --- a/.github/workflows/grafana-sync-check.yml +++ b/.github/workflows/grafana-sync-check.yml @@ -18,13 +18,13 @@ on: push: branches: [main] paths: - - "grafana/**" + - "dashboards/grafana/**" - "scripts/check_grafana_queries_sync.py" - ".github/workflows/grafana-sync-check.yml" pull_request: branches: [main] paths: - - "grafana/**" + - "dashboards/grafana/**" - "scripts/check_grafana_queries_sync.py" - ".github/workflows/grafana-sync-check.yml" diff --git a/.gitignore b/.gitignore index 76d52965..51195424 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,9 @@ deploy/streaming_evaluation/.streaming_evaluation_state.json ._* grafana/datasource.yaml grafana/.local/ +dashboards/grafana/datasource.yaml +dashboards/grafana/.local/ # Service-account credentials (never commit downloaded JSON keys). grafana/*.json +dashboards/grafana/*.json diff --git a/dashboard/looker_studio/README.md b/dashboard/looker_studio/README.md index 66ccd699..2eb5c6b9 100644 --- a/dashboard/looker_studio/README.md +++ b/dashboard/looker_studio/README.md @@ -171,7 +171,7 @@ from a signed-in, non-owner, out-of-domain account (anonymous HTTP cannot observe either). While the template is unavailable, the `tools/hydrate_dashboard.py` preflight below still validates the table — but its creation URL copies the same template, so it is not an outage workaround; -the [Grafana dashboard](../../grafana/) and the Looker Agent Analytics block +the [Grafana dashboard](../../dashboards/grafana/) and the Looker Agent Analytics block (below) remain available. Looker Studio report parameters are intentionally not used for these values: diff --git a/dashboard/looker_studio/USER_MANUAL.md b/dashboard/looker_studio/USER_MANUAL.md index 521c69be..87a397e2 100644 --- a/dashboard/looker_studio/USER_MANUAL.md +++ b/dashboard/looker_studio/USER_MANUAL.md @@ -232,7 +232,7 @@ configurator produces. | What you see | What's happening and what to do | |---|---| | The new tab shows loading, not-found, or an error page right after clicking **Create my dashboard** | Google is still provisioning your report copy — this resolves within about 10 seconds. Don't close the tab. If it's still broken after a minute, close it and click the button again. (Exception: the "This report isn't shared with you" dialog below never resolves by waiting or retrying.) | -| The new tab shows **"This report isn't shared with you"** with *Reload* / *Return to report list* / *Go to report template* buttons | The shared dashboard *template* is unavailable to the Google account signed in to that tab — nothing is wrong with your project, dataset, table, access, or the identifiers you entered; they haven't been consulted yet. First check the tab is using the account you intend (Looker Studio uses the browser's default Google account — switch accounts or use a profile signed in to only the right one, then click **Create my dashboard** again). If you're on a work or school account, your own organization's sharing policy can also block receiving Looker Studio assets from outside domains — try a personal account if you can. If the dialog still persists, the template's copy path is blocked: report it on [issue #445](https://github.com/GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK/issues/445) — say only whether your signed-in account is personal or part of an organization; do not post the account's email address. While it's blocked, the [compatibility check](#check-compatibility-before-creating-optional-needs-a-terminal) still validates your table (it is not a workaround — its URL copies the same template), and the repository's [Grafana dashboard](https://github.com/GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK/tree/main/grafana) works independently of Looker Studio. | +| The new tab shows **"This report isn't shared with you"** with *Reload* / *Return to report list* / *Go to report template* buttons | The shared dashboard *template* is unavailable to the Google account signed in to that tab — nothing is wrong with your project, dataset, table, access, or the identifiers you entered; they haven't been consulted yet. First check the tab is using the account you intend (Looker Studio uses the browser's default Google account — switch accounts or use a profile signed in to only the right one, then click **Create my dashboard** again). If you're on a work or school account, your own organization's sharing policy can also block receiving Looker Studio assets from outside domains — try a personal account if you can. If the dialog still persists, the template's copy path is blocked: report it on [issue #445](https://github.com/GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK/issues/445) — say only whether your signed-in account is personal or part of an organization; do not post the account's email address. While it's blocked, the [compatibility check](#check-compatibility-before-creating-optional-needs-a-terminal) still validates your table (it is not a workaround — its URL copies the same template), and the repository's [Grafana dashboard](https://github.com/GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK/tree/main/dashboards/grafana) works independently of Looker Studio. | | Charts blank or trickling in after opening a page | Normal on a cold load — allow up to 90 seconds. If a chart is still empty after that, widen the date range: your table may have no events in the selected window. | | Layout looks cut off on the left | Collapse the Looker Studio navigation drawer, and make the window at least 1280 px wide. Phones and narrow tablets aren't supported. | | Bottom charts clipped on Token Consumption or Latency | You're on a copy created before 2026-07-29, which keeps the old page geometry. Create a fresh copy from the configurator. | diff --git a/dashboard/looker_studio/docs/index.html b/dashboard/looker_studio/docs/index.html index aede621b..f54900a1 100644 --- a/dashboard/looker_studio/docs/index.html +++ b/dashboard/looker_studio/docs/index.html @@ -189,7 +189,7 @@

Save, then secure sharing

validate your table, but it copies the same template, so it is not a workaround; while the template is blocked, use the - + Grafana dashboard. diff --git a/grafana/README.md b/dashboards/grafana/README.md similarity index 97% rename from grafana/README.md rename to dashboards/grafana/README.md index 163ba460..95bd260b 100644 --- a/grafana/README.md +++ b/dashboards/grafana/README.md @@ -2,7 +2,7 @@ Visualize BQAA telemetry straight from BigQuery in Grafana. Works on the free tier of Grafana Cloud. It runs alongside the -[`dashboard_v2/`](../dashboard_v2) React app rather than replacing it — both +[`dashboard_v2/`](../../dashboard_v2) React app rather than replacing it — both read the same data. ``` @@ -26,7 +26,7 @@ AI Agent app ──SDK──▶ BigQuery agent_events ──ViewManager──▶ On macOS or Linux with `gcloud auth application-default login` already run: ```bash -python3 grafana/run_local.py --project YOUR_PROJECT --dataset YOUR_DATASET +python3 dashboards/grafana/run_local.py --project YOUR_PROJECT --dataset YOUR_DATASET ``` That downloads a pinned Grafana (cached and checksum-verified after the @@ -41,7 +41,7 @@ work by default — the job location is selected automatically; pass `--processing-location EU` (or a region) to pin it. `--sa-key key.json` switches to the JWT auth documented below and writes the credential file with `0600` permissions; `--stop` tears it down; everything generated lives -in the disposable, gitignored `grafana/.local/`. Production setups should +in the disposable, gitignored `dashboards/grafana/.local/`. Production setups should still follow the full steps below with a scoped service account. > **Known pitfalls if you run Grafana your own way instead:** @@ -77,7 +77,7 @@ still follow the full steps below with a scoped service account. 3. **Keys → Add Key → Create new key → JSON**, and download it. > **Keep the key out of the repo.** `.gitignore` only covers new `*.json` -> files inside `grafana/`. A key saved anywhere else can be committed by +> files inside `dashboards/grafana/`. A key saved anywhere else can be committed by > accident. ### 3. Prepare the data @@ -138,7 +138,7 @@ form with literal `\n` escapes. ```bash # Docker -cp grafana/datasource.example.yaml grafana/datasource.yaml +cp dashboards/grafana/datasource.example.yaml dashboards/grafana/datasource.yaml docker run -d -p 3000:3000 \ -e "GF_INSTALL_PLUGINS=grafana-bigquery-datasource" \ -v /path/to/your/datasource.yaml:/etc/grafana/provisioning/datasources/datasource.yaml \ @@ -267,7 +267,7 @@ every panel's SQL: sed -e 's/YOUR_PROJECT_ID/my-gcp-project/g' \ -e 's/YOUR_DATASET_ID/my_demo_dataset/g' \ -e 's/agent_events/my_demo_table/g' \ - grafana/bqaa-public-demo.json > grafana/bqaa-public-demo.ready.json + dashboards/grafana/bqaa-public-demo.json > dashboards/grafana/bqaa-public-demo.ready.json ``` The third substitution only matters if your events table is not named diff --git a/grafana/bqaa-dashboard.json b/dashboards/grafana/bqaa-dashboard.json similarity index 99% rename from grafana/bqaa-dashboard.json rename to dashboards/grafana/bqaa-dashboard.json index 7ce8a176..e595d455 100644 --- a/grafana/bqaa-dashboard.json +++ b/dashboards/grafana/bqaa-dashboard.json @@ -1,7 +1,7 @@ { "title": "BigQuery Agent Analytics", "uid": "bqaa-dashboard", - "description": "Interactive dashboard for the BigQuery Agent Analytics SDK. Panels query the raw agent_events table and the typed ViewManager views (adk_* by default). Panel SQL is maintained in grafana/queries/*.sql \u2014 treat those files as the source of truth.", + "description": "Interactive dashboard for the BigQuery Agent Analytics SDK. Panels query the raw agent_events table and the typed ViewManager views (adk_* by default). Panel SQL is maintained in dashboards/grafana/queries/*.sql — treat those files as the source of truth.", "tags": [ "bqaa", "bigquery", @@ -444,7 +444,7 @@ "x": 12, "y": 1 }, - "description": "Share of events counted as errors: event_type ending in _ERROR, or a non-NULL error_message, or UPPER(status) = 'ERROR'. See grafana/queries/README.md. Ignores the Event Type filter: the four Overview stats share one query that stays unfiltered so the error rate keeps its global context.", + "description": "Share of events counted as errors: event_type ending in _ERROR, or a non-NULL error_message, or UPPER(status) = 'ERROR'. See dashboards/grafana/queries/README.md. Ignores the Event Type filter: the four Overview stats share one query that stays unfiltered so the error rate keeps its global context.", "datasource": { "type": "datasource", "uid": "-- Dashboard --" diff --git a/grafana/bqaa-public-demo.json b/dashboards/grafana/bqaa-public-demo.json similarity index 97% rename from grafana/bqaa-public-demo.json rename to dashboards/grafana/bqaa-public-demo.json index b56915bb..d3063cb7 100644 --- a/grafana/bqaa-public-demo.json +++ b/dashboards/grafana/bqaa-public-demo.json @@ -43,7 +43,7 @@ ], "title": "BQAA Public Demo", "uid": "bqaa-public-demo", - "description": "Public-sharing build of bqaa-dashboard.json. Everything a public dashboard cannot evaluate has been removed: templating.list is empty, panel SQL carries no dashboard-variable interpolation, hourly buckets come from BigQuery's own TIMESTAMP_TRUNC instead of Grafana's frontend time-group macros, the cost rates are hardcoded literals, no panel reads another panel's result through the \"-- Dashboard --\" datasource (that resolution happens in the browser and never runs for an anonymous viewer, so such panels render empty), and the per-session Trace detail panel is gone so no raw event timeline is exposed. Every stat therefore issues its own BigQuery query — set a BigQuery Custom Quota on the project before sharing, as grafana/README.md describes. The 72-hour window is enforced in the SQL itself — every panel filters on timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 72 HOUR) rather than Grafana's time-range macro. A hidden time picker only removes the widget; a from/to pair in the URL still reaches the backend, so a macro-based range would let any viewer widen the BigQuery scan at will. Keep the bound in SQL: it holds no matter how the dashboard is shared or what the URL says. timepicker.refresh_intervals is empty and refresh is off so a viewer cannot pin the dashboard to a fast auto-refresh, editable is false, and timezone is utc to match the UTC buckets TIMESTAMP_TRUNC produces. Before importing, replace the literal YOUR_PROJECT_ID and YOUR_DATASET_ID placeholders in every panel's SQL (see the sed shortcut in grafana/README.md) and confirm adk_ matches your ViewManager view prefix. Do not point this at real production telemetry; seed a dedicated demo dataset instead. Edit bqaa-dashboard.json, not this file, for interactive use.", + "description": "Public-sharing build of bqaa-dashboard.json. Everything a public dashboard cannot evaluate has been removed: templating.list is empty, panel SQL carries no dashboard-variable interpolation, hourly buckets come from BigQuery's own TIMESTAMP_TRUNC instead of Grafana's frontend time-group macros, the cost rates are hardcoded literals, no panel reads another panel's result through the \"-- Dashboard --\" datasource (that resolution happens in the browser and never runs for an anonymous viewer, so such panels render empty), and the per-session Trace detail panel is gone so no raw event timeline is exposed. Every stat therefore issues its own BigQuery query — set a BigQuery Custom Quota on the project before sharing, as dashboards/grafana/README.md describes. The 72-hour window is enforced in the SQL itself — every panel filters on timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 72 HOUR) rather than Grafana's time-range macro. A hidden time picker only removes the widget; a from/to pair in the URL still reaches the backend, so a macro-based range would let any viewer widen the BigQuery scan at will. Keep the bound in SQL: it holds no matter how the dashboard is shared or what the URL says. timepicker.refresh_intervals is empty and refresh is off so a viewer cannot pin the dashboard to a fast auto-refresh, editable is false, and timezone is utc to match the UTC buckets TIMESTAMP_TRUNC produces. Before importing, replace the literal YOUR_PROJECT_ID and YOUR_DATASET_ID placeholders in every panel's SQL (see the sed shortcut in dashboards/grafana/README.md) and confirm adk_ matches your ViewManager view prefix. Do not point this at real production telemetry; seed a dedicated demo dataset instead. Edit bqaa-dashboard.json, not this file, for interactive use.", "tags": [ "bqaa", "bigquery", @@ -237,7 +237,7 @@ "x": 12, "y": 1 }, - "description": "Share of events counted as errors: event_type ending in _ERROR, or a non-NULL error_message, or UPPER(status) = 'ERROR'. See grafana/queries/README.md.", + "description": "Share of events counted as errors: event_type ending in _ERROR, or a non-NULL error_message, or UPPER(status) = 'ERROR'. See dashboards/grafana/queries/README.md.", "datasource": { "type": "grafana-bigquery-datasource", "uid": "${DS_BIGQUERY}" diff --git a/grafana/datasource.example.yaml b/dashboards/grafana/datasource.example.yaml similarity index 98% rename from grafana/datasource.example.yaml rename to dashboards/grafana/datasource.example.yaml index 17de75c5..1de3cf86 100644 --- a/grafana/datasource.example.yaml +++ b/dashboards/grafana/datasource.example.yaml @@ -3,7 +3,7 @@ # OPTIONAL — only for self-managed Grafana (Docker, k8s, bare metal). # On Grafana Cloud, add the data source through the UI instead # (Connections -> Data sources -> Add data source -> "Google BigQuery"); -# see grafana/README.md. +# see dashboards/grafana/README.md. # # Usage (self-managed): # 1. Install the plugin: grafana-cli plugins install grafana-bigquery-datasource diff --git a/grafana/queries/README.md b/dashboards/grafana/queries/README.md similarity index 99% rename from grafana/queries/README.md rename to dashboards/grafana/queries/README.md index 8489043b..d79f530a 100644 --- a/grafana/queries/README.md +++ b/dashboards/grafana/queries/README.md @@ -1,7 +1,7 @@ # Grafana Panel Queries: Source of Truth The `.sql` files in this directory are the **canonical source of truth** for -every panel and query template variable in `grafana/bqaa-dashboard.json`. +every panel and query template variable in `dashboards/grafana/bqaa-dashboard.json`. The dashboard JSON embeds a copy of each query (Grafana has no "include SQL from file" mechanism), so: diff --git a/grafana/queries/errors_over_time.sql b/dashboards/grafana/queries/errors_over_time.sql similarity index 100% rename from grafana/queries/errors_over_time.sql rename to dashboards/grafana/queries/errors_over_time.sql diff --git a/grafana/queries/estimated_cost.sql b/dashboards/grafana/queries/estimated_cost.sql similarity index 100% rename from grafana/queries/estimated_cost.sql rename to dashboards/grafana/queries/estimated_cost.sql diff --git a/grafana/queries/events_by_agent.sql b/dashboards/grafana/queries/events_by_agent.sql similarity index 100% rename from grafana/queries/events_by_agent.sql rename to dashboards/grafana/queries/events_by_agent.sql diff --git a/grafana/queries/events_over_time.sql b/dashboards/grafana/queries/events_over_time.sql similarity index 100% rename from grafana/queries/events_over_time.sql rename to dashboards/grafana/queries/events_over_time.sql diff --git a/grafana/queries/llm_calls_total.sql b/dashboards/grafana/queries/llm_calls_total.sql similarity index 100% rename from grafana/queries/llm_calls_total.sql rename to dashboards/grafana/queries/llm_calls_total.sql diff --git a/grafana/queries/llm_latency_percentiles.sql b/dashboards/grafana/queries/llm_latency_percentiles.sql similarity index 100% rename from grafana/queries/llm_latency_percentiles.sql rename to dashboards/grafana/queries/llm_latency_percentiles.sql diff --git a/grafana/queries/llm_tokens_over_time.sql b/dashboards/grafana/queries/llm_tokens_over_time.sql similarity index 100% rename from grafana/queries/llm_tokens_over_time.sql rename to dashboards/grafana/queries/llm_tokens_over_time.sql diff --git a/grafana/queries/overview_totals.sql b/dashboards/grafana/queries/overview_totals.sql similarity index 100% rename from grafana/queries/overview_totals.sql rename to dashboards/grafana/queries/overview_totals.sql diff --git a/grafana/queries/public-demo/README.md b/dashboards/grafana/queries/public-demo/README.md similarity index 98% rename from grafana/queries/public-demo/README.md rename to dashboards/grafana/queries/public-demo/README.md index 9272f6d4..e5915ff4 100644 --- a/grafana/queries/public-demo/README.md +++ b/dashboards/grafana/queries/public-demo/README.md @@ -1,7 +1,7 @@ # Public Demo Panel Queries The `.sql` files here are the **canonical source of truth** for every panel in -`grafana/bqaa-public-demo.json` — one file per panel, embedded as a copy in the +`dashboards/grafana/bqaa-public-demo.json` — one file per panel, embedded as a copy in the dashboard JSON. > **Edit the query here first, then paste it into the matching panel in diff --git a/grafana/queries/public-demo/errors_over_time.sql b/dashboards/grafana/queries/public-demo/errors_over_time.sql similarity index 100% rename from grafana/queries/public-demo/errors_over_time.sql rename to dashboards/grafana/queries/public-demo/errors_over_time.sql diff --git a/grafana/queries/public-demo/estimated_cost.sql b/dashboards/grafana/queries/public-demo/estimated_cost.sql similarity index 100% rename from grafana/queries/public-demo/estimated_cost.sql rename to dashboards/grafana/queries/public-demo/estimated_cost.sql diff --git a/grafana/queries/public-demo/events_by_agent.sql b/dashboards/grafana/queries/public-demo/events_by_agent.sql similarity index 100% rename from grafana/queries/public-demo/events_by_agent.sql rename to dashboards/grafana/queries/public-demo/events_by_agent.sql diff --git a/grafana/queries/public-demo/events_over_time.sql b/dashboards/grafana/queries/public-demo/events_over_time.sql similarity index 100% rename from grafana/queries/public-demo/events_over_time.sql rename to dashboards/grafana/queries/public-demo/events_over_time.sql diff --git a/grafana/queries/public-demo/llm_calls_total.sql b/dashboards/grafana/queries/public-demo/llm_calls_total.sql similarity index 100% rename from grafana/queries/public-demo/llm_calls_total.sql rename to dashboards/grafana/queries/public-demo/llm_calls_total.sql diff --git a/grafana/queries/public-demo/llm_latency_percentiles.sql b/dashboards/grafana/queries/public-demo/llm_latency_percentiles.sql similarity index 100% rename from grafana/queries/public-demo/llm_latency_percentiles.sql rename to dashboards/grafana/queries/public-demo/llm_latency_percentiles.sql diff --git a/grafana/queries/public-demo/llm_tokens_over_time.sql b/dashboards/grafana/queries/public-demo/llm_tokens_over_time.sql similarity index 100% rename from grafana/queries/public-demo/llm_tokens_over_time.sql rename to dashboards/grafana/queries/public-demo/llm_tokens_over_time.sql diff --git a/grafana/queries/public-demo/overview_avg_llm_latency.sql b/dashboards/grafana/queries/public-demo/overview_avg_llm_latency.sql similarity index 100% rename from grafana/queries/public-demo/overview_avg_llm_latency.sql rename to dashboards/grafana/queries/public-demo/overview_avg_llm_latency.sql diff --git a/grafana/queries/public-demo/overview_error_rate.sql b/dashboards/grafana/queries/public-demo/overview_error_rate.sql similarity index 100% rename from grafana/queries/public-demo/overview_error_rate.sql rename to dashboards/grafana/queries/public-demo/overview_error_rate.sql diff --git a/grafana/queries/public-demo/overview_events.sql b/dashboards/grafana/queries/public-demo/overview_events.sql similarity index 100% rename from grafana/queries/public-demo/overview_events.sql rename to dashboards/grafana/queries/public-demo/overview_events.sql diff --git a/grafana/queries/public-demo/overview_sessions.sql b/dashboards/grafana/queries/public-demo/overview_sessions.sql similarity index 100% rename from grafana/queries/public-demo/overview_sessions.sql rename to dashboards/grafana/queries/public-demo/overview_sessions.sql diff --git a/grafana/queries/public-demo/recent_sessions.sql b/dashboards/grafana/queries/public-demo/recent_sessions.sql similarity index 100% rename from grafana/queries/public-demo/recent_sessions.sql rename to dashboards/grafana/queries/public-demo/recent_sessions.sql diff --git a/grafana/queries/public-demo/tokens_by_model.sql b/dashboards/grafana/queries/public-demo/tokens_by_model.sql similarity index 100% rename from grafana/queries/public-demo/tokens_by_model.sql rename to dashboards/grafana/queries/public-demo/tokens_by_model.sql diff --git a/grafana/queries/public-demo/tool_errors.sql b/dashboards/grafana/queries/public-demo/tool_errors.sql similarity index 100% rename from grafana/queries/public-demo/tool_errors.sql rename to dashboards/grafana/queries/public-demo/tool_errors.sql diff --git a/grafana/queries/public-demo/tool_latency.sql b/dashboards/grafana/queries/public-demo/tool_latency.sql similarity index 100% rename from grafana/queries/public-demo/tool_latency.sql rename to dashboards/grafana/queries/public-demo/tool_latency.sql diff --git a/grafana/queries/public-demo/tool_usage.sql b/dashboards/grafana/queries/public-demo/tool_usage.sql similarity index 100% rename from grafana/queries/public-demo/tool_usage.sql rename to dashboards/grafana/queries/public-demo/tool_usage.sql diff --git a/grafana/queries/public-demo/top_errors.sql b/dashboards/grafana/queries/public-demo/top_errors.sql similarity index 100% rename from grafana/queries/public-demo/top_errors.sql rename to dashboards/grafana/queries/public-demo/top_errors.sql diff --git a/grafana/queries/public-demo/total_tokens.sql b/dashboards/grafana/queries/public-demo/total_tokens.sql similarity index 100% rename from grafana/queries/public-demo/total_tokens.sql rename to dashboards/grafana/queries/public-demo/total_tokens.sql diff --git a/grafana/queries/recent_sessions.sql b/dashboards/grafana/queries/recent_sessions.sql similarity index 100% rename from grafana/queries/recent_sessions.sql rename to dashboards/grafana/queries/recent_sessions.sql diff --git a/grafana/queries/tokens_by_model.sql b/dashboards/grafana/queries/tokens_by_model.sql similarity index 100% rename from grafana/queries/tokens_by_model.sql rename to dashboards/grafana/queries/tokens_by_model.sql diff --git a/grafana/queries/tool_errors.sql b/dashboards/grafana/queries/tool_errors.sql similarity index 100% rename from grafana/queries/tool_errors.sql rename to dashboards/grafana/queries/tool_errors.sql diff --git a/grafana/queries/tool_latency.sql b/dashboards/grafana/queries/tool_latency.sql similarity index 100% rename from grafana/queries/tool_latency.sql rename to dashboards/grafana/queries/tool_latency.sql diff --git a/grafana/queries/tool_usage.sql b/dashboards/grafana/queries/tool_usage.sql similarity index 100% rename from grafana/queries/tool_usage.sql rename to dashboards/grafana/queries/tool_usage.sql diff --git a/grafana/queries/top_errors.sql b/dashboards/grafana/queries/top_errors.sql similarity index 100% rename from grafana/queries/top_errors.sql rename to dashboards/grafana/queries/top_errors.sql diff --git a/grafana/queries/trace_detail.sql b/dashboards/grafana/queries/trace_detail.sql similarity index 100% rename from grafana/queries/trace_detail.sql rename to dashboards/grafana/queries/trace_detail.sql diff --git a/grafana/queries/var_agent.sql b/dashboards/grafana/queries/var_agent.sql similarity index 100% rename from grafana/queries/var_agent.sql rename to dashboards/grafana/queries/var_agent.sql diff --git a/grafana/queries/var_event_type.sql b/dashboards/grafana/queries/var_event_type.sql similarity index 100% rename from grafana/queries/var_event_type.sql rename to dashboards/grafana/queries/var_event_type.sql diff --git a/grafana/queries/var_session_id.sql b/dashboards/grafana/queries/var_session_id.sql similarity index 100% rename from grafana/queries/var_session_id.sql rename to dashboards/grafana/queries/var_session_id.sql diff --git a/grafana/queries/var_user_id.sql b/dashboards/grafana/queries/var_user_id.sql similarity index 100% rename from grafana/queries/var_user_id.sql rename to dashboards/grafana/queries/var_user_id.sql diff --git a/grafana/run_local.py b/dashboards/grafana/run_local.py similarity index 97% rename from grafana/run_local.py rename to dashboards/grafana/run_local.py index 6a056067..58bf7a85 100644 --- a/grafana/run_local.py +++ b/dashboards/grafana/run_local.py @@ -14,15 +14,15 @@ # limitations under the License. """One-command local Grafana for BigQuery Agent Analytics. - python3 grafana/run_local.py --project MY_PROJECT --dataset MY_DATASET + python3 dashboards/grafana/run_local.py --project MY_PROJECT --dataset MY_DATASET -does everything the manual setup chain in grafana/README.md does: downloads +does everything the manual setup chain in dashboards/grafana/README.md does: downloads a pinned Grafana, installs the pinned BigQuery datasource plugin, provisions the datasource (Application Default Credentials by default, --sa-key for the documented JWT path), writes a copy of bqaa-dashboard.json with the six constant variables filled in, and launches bound to 127.0.0.1 with the plugin preinstaller disabled. `--stop` tears it down. Everything generated -lives under grafana/.local/ and is disposable; the committed dashboard and +lives under dashboards/grafana/.local/ and is disposable; the committed dashboard and example files are never modified. Requires only the Python standard library. Views (`adk_*`) are created via @@ -64,7 +64,7 @@ PLUGIN_VERSION = "3.3.1" DASHBOARD_UID = "bqaa-dashboard" DATASOURCE_UID = "bqaa-bigquery" -# Matches grafana/datasource.example.yaml: a per-query BigQuery cost cap of +# Matches dashboards/grafana/datasource.example.yaml: a per-query BigQuery cost cap of # 100 MB billed. The key spelling is significant (see the example file). DEFAULT_MAX_BYTES_BILLED = "100000000" @@ -78,7 +78,7 @@ # ("us-central1", "asia-northeast1"). LOCATION_RE = re.compile(r"^[A-Za-z][A-Za-z0-9-]{0,31}$") # The two price constants are interpolated into panel arithmetic -# (grafana/README.md warns a Textbox there is an injection risk), so only a +# (dashboards/grafana/README.md warns a Textbox there is an injection risk), so only a # strict decimal literal is accepted. Same reasoning for the bytes cap. PRICE_RE = re.compile(r"^\d{1,9}(\.\d{1,9})?$") BYTES_RE = re.compile(r"^[1-9]\d{0,17}$") @@ -108,7 +108,7 @@ def require_price(label: str, value: str) -> str: if not PRICE_RE.fullmatch(value): raise ValueError( f"{label} {value!r} must be a plain decimal like 1.25 (it is" - " interpolated into panel arithmetic; see grafana/README.md)." + " interpolated into panel arithmetic; see dashboards/grafana/README.md)." ) return value @@ -180,13 +180,13 @@ def render_datasource_yaml( With no key: `gce` authentication, which the plugin resolves through Application Default Credentials when Grafana runs off-GCE — no service-account key needed for local evaluation. With a key: the JWT path - documented in grafana/README.md, with the private key as a real YAML + documented in dashboards/grafana/README.md, with the private key as a real YAML block scalar (never literal \\n escapes). `processingLocation` is omitted by default so the plugin selects the job location automatically; hard-coding a multi-region breaks datasets that live anywhere else. `MaxBytesBilled` preserves the per-query cost cap - from grafana/datasource.example.yaml. + from dashboards/grafana/datasource.example.yaml. """ header = ( "apiVersion: 1\n" @@ -324,7 +324,7 @@ def pick_dist(system: str | None = None, machine: str | None = None) -> str: raise ValueError( f"unsupported platform {system}/{machine}: this launcher covers" " macOS and Linux on amd64/arm64; on other platforms follow the" - " manual steps in grafana/README.md." + " manual steps in dashboards/grafana/README.md." ) return f"{system}-{arch}" @@ -657,7 +657,7 @@ def main(argv: list[str] | None = None) -> int: "--max-bytes-billed", default=DEFAULT_MAX_BYTES_BILLED, help="per-query BigQuery cost cap in bytes (default 100000000, matching" - " grafana/datasource.example.yaml)", + " dashboards/grafana/datasource.example.yaml)", ) parser.add_argument( "--sa-key", diff --git a/scripts/check_grafana_queries_sync.py b/scripts/check_grafana_queries_sync.py index 0cc6367f..d9b9d614 100644 --- a/scripts/check_grafana_queries_sync.py +++ b/scripts/check_grafana_queries_sync.py @@ -11,10 +11,12 @@ from typing import Any, NamedTuple REPOSITORY_ROOT = Path(__file__).resolve().parents[1] -DASHBOARD_PATH = REPOSITORY_ROOT / "grafana" / "bqaa-dashboard.json" -QUERIES_DIRECTORY = REPOSITORY_ROOT / "grafana" / "queries" +DASHBOARD_PATH = ( + REPOSITORY_ROOT / "dashboards" / "grafana" / "bqaa-dashboard.json" +) +QUERIES_DIRECTORY = REPOSITORY_ROOT / "dashboards" / "grafana" / "queries" PUBLIC_DEMO_DASHBOARD_PATH = ( - REPOSITORY_ROOT / "grafana" / "bqaa-public-demo.json" + REPOSITORY_ROOT / "dashboards" / "grafana" / "bqaa-public-demo.json" ) PUBLIC_DEMO_QUERIES_DIRECTORY = QUERIES_DIRECTORY / "public-demo" @@ -533,7 +535,7 @@ def check_dashboard_sync( def check_main_dashboard() -> int: - """Check the interactive dashboard against grafana/queries/*.sql. + """Check the interactive dashboard against dashboards/grafana/queries/*.sql. On top of the shared sync, the interactive build owns the template variables: their SQL must match its canonical file, and the panels that reuse another @@ -672,7 +674,7 @@ def check_main_dashboard() -> int: def check_public_demo_dashboard() -> int: - """Check the public demo build against grafana/queries/public-demo/*.sql. + """Check the public demo build against dashboards/grafana/queries/public-demo/*.sql. The public build is shared with anonymous viewers, so on top of the same bidirectional SQL sync its queries must satisfy the public SQL policy, its @@ -780,8 +782,8 @@ def main() -> int: Validates the integrity and synchronization of the Grafana dashboard queries. This CI script strictly enforces these key conditions, for the interactive - dashboard against grafana/queries/ and for the public demo build against - grafana/queries/public-demo/: + dashboard against dashboards/grafana/queries/ and for the public demo build against + dashboards/grafana/queries/public-demo/: 1. Drift Prevention: The 'rawSql' inside the JSON dashboard exactly matches the canonical '.sql' files in the queries directory (printing unified diffs on failure). 2. Unmapped File Detection: Every '.sql' file in the queries directory is diff --git a/tests/test_check_grafana_queries_sync.py b/tests/test_check_grafana_queries_sync.py index 33b9eb60..3eabc476 100644 --- a/tests/test_check_grafana_queries_sync.py +++ b/tests/test_check_grafana_queries_sync.py @@ -35,7 +35,9 @@ ROOT = Path(__file__).resolve().parents[1] CHECK_SCRIPT = ROOT / "scripts" / "check_grafana_queries_sync.py" -PUBLIC_DEMO_QUERIES = ROOT / "grafana" / "queries" / "public-demo" +PUBLIC_DEMO_QUERIES = ( + ROOT / "dashboards" / "grafana" / "queries" / "public-demo" +) def _load_check(): @@ -216,7 +218,7 @@ def test_or_true_mutant_is_not_caught_today(): Such a query is unbounded and must still fail review. If a future change makes the lint reject it, replace this test with one asserting rejection and - update the CI-scope note in `grafana/queries/public-demo/README.md`. + update the CI-scope note in `dashboards/grafana/queries/public-demo/README.md`. """ assert _lint(OR_TRUE_MUTANT, "mutant_or_true.sql") == 0 diff --git a/tests/test_grafana_local_runner.py b/tests/test_grafana_local_runner.py index 5a330fe7..5c883ca0 100644 --- a/tests/test_grafana_local_runner.py +++ b/tests/test_grafana_local_runner.py @@ -22,7 +22,7 @@ import pytest ROOT = Path(__file__).resolve().parents[1] -RUNNER = ROOT / "grafana" / "run_local.py" +RUNNER = ROOT / "dashboards" / "grafana" / "run_local.py" def _load_runner(): @@ -83,7 +83,9 @@ def test_prices_rejected(value): def _real_dashboard(): - return json.loads((ROOT / "grafana" / "bqaa-dashboard.json").read_text()) + return json.loads( + (ROOT / "dashboards" / "grafana" / "bqaa-dashboard.json").read_text() + ) def test_patch_dashboard_fills_all_six_constants(): @@ -173,7 +175,7 @@ def test_datasource_yaml_jwt_uses_block_scalar(tmp_path): rendered ) # Real line breaks under a block scalar, never literal \n escapes - # (grafana/README.md calls this out explicitly). + # (dashboards/grafana/README.md calls this out explicitly). assert "privateKey: |" in rendered assert " -----BEGIN PRIVATE KEY-----\n AAAA\n" in rendered assert "\\n" not in rendered