Skip to content

feat(dashboards): chart Google Cloud metrics live with PromQL - #1480

Open
JeremyFunk wants to merge 23 commits into
feat/gcp-live-query-accessfrom
feat/gcp-promql-widget
Open

JeremyFunk wants to merge 23 commits into
feat/gcp-live-query-accessfrom
feat/gcp-promql-widget

Conversation

@JeremyFunk

@JeremyFunk JeremyFunk commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #1477 (feat/gcp-live-query-access), which sits on #1475 (feat/gcp-promql-query). #1478 (MCP tools) sits on this branch. Merge the stack bottom-up.

What

Dashboard widgets can chart a PromQL query over the organization's Google Cloud Monitoring metrics. The query is read live from Google through #1475's GcpPromQlService; nothing is ingested.

  • Data source kind promql (packages/widgets/src/dashboard/v3/data-source.ts): { kind: "promql", query, projects?, legend?, transform? }. The public v2 dashboards API mirrors it (V2PromQlDataSource, query capped at 4,000 characters like the read).
  • Request path: toWidgetRequest lowers the source to endpoint gcp_promql with params { promql, projects?, legend? }. planWidgetRequest then fills the window and substitutes dashboard variables. A tile with no query stays at promql_query_empty: it renders "No query yet" and sends no request. The browser's server function (apps/web/src/api/warehouse/gcp-promql.ts, loaded on first use) and the share resolver (DashboardWidgetDataService) share the same pure helpers in packages/query-engine/src/gcp-promql.ts. They build the range request with step = the chart bucket for the window, never under 60 s, and turn the answer into chart rows.
  • Chart rows: one row per timestamp, { bucket, [series]: value }. A series is named by the legend template ({{label}}). Without a legend it is named by its labels, minus __name__, and minus project_id when a single project answered. A missing point leaves its key out of that row.
  • Variables: substituted plainly into the query. All becomes a regex alternation (a|b) for a =~ matcher.
  • Editor: a Google Cloud source tab for line, area, bar, stat and gauge panels. In this mode the type picker offers only those types.
    • Metric search uses gcpMetricSearch. Picking a metric replaces the query with a starter query from gcpMetricDescribe: a rate for DELTA/CUMULATIVE, avg for GAUGE, histogram_quantile(0.95, …_bucket) for distributions. It uses the UTF-8 selector form and adds a monitored_resource matcher when the metric has several resource types.
    • Projects and legend fields, and Run preview, as with raw SQL, since Google bills every read. Edits made after the last run are marked as such.
    • Stat and gauge tiles reduce the series by the rail's Aggregate.
  • States:
    • Google's refusals (invalid query, more than 500 series) show on the tile and in the editor.
    • A partial answer adds a one-line notice under the chart (notice on the ready state).
    • Not connected, no Google identity on the deployment, and more than 20 projects show the muted setup state with the next step.
  • Shared boards:
    • The share resolver runs only the stored query. The viewer can change the window and the variable values, nothing else.
    • A query variable that a PromQL tile references must now also pass the free-text charset, including every option of an All. Its options are telemetry values, and PromQL takes them unescaped.
    • redactForShare drops query, projects and legend.
    • Google's error text stays off the share, and so does any project id: the partial-answer notice only counts the failed scopes.
  • Docs: a Live queries section in integrations/gcp.md, and the new source tab in dashboards/build-dashboards.md.

Decisions taken without the owner

  • Projects is a text field (comma- or space-separated ids), not a multi-select. No endpoint lists the projects the connections reach, and the brief said to keep it simple. Empty = every project, up to 20.
  • Step = the chart policy bucket (computeBucketSecondsForRange, ~100 points), floored at 60 s. It does not follow the tile-width model, so the step does not change with tile width and Google's edge cache sees fewer distinct keys.
  • The stat/gauge "Value field" select is hidden outside the query builder. It lists query-builder series, and neither raw SQL nor PromQL read it. For raw SQL it already had no effect.
  • Share-side GCP setup errors (not connected, too many projects) still reach the viewer as the generic retryable "couldn't be loaded" and are logged at error level, like every other share failure.
  • Retries unchanged: a widget fetch is still retried twice. Google does not bill a refused query.
  • No MCP changes here: describe_dashboard_schema takes its kinds from WIDGET_DATA_SOURCE_KINDS, so it already lists promql. feat(mcp): query Google Cloud metrics live with PromQL #1478 has the tools.

Verify

Scoped vitest only; CI runs the rest:

  • packages/widgets: src/dashboard/promql-source.test.ts, plus the package's typecheck
  • packages/domain: src/http/v2/dashboard-widget-parity.test.ts
  • packages/query-engine: src/gcp-promql.test.ts, src/widget-request.test.ts, src/dashboard-variables/interpolate.test.ts
  • packages/backend: DashboardWidgetDataService.test.ts, share-variables.test.ts, widget-data-parity.test.ts
  • apps/web: data-source-registry.test.ts, share-support.test.ts, use-widget-data.test.ts, dashboard-summary.test.ts
  • bun run ios:openapi:check: up to date

Not verified without a live system; this needs a visual pass at the top of the stack:

  • Editor: the Google Cloud tab on a line widget, metric search (including its searching and failure states), a starter query written on pick, Run preview drawing the chart, and the stale-edit marker.
  • Tile states:
    • invalid query: red, Google's message, Details
    • more than 500 series
    • not connected, unavailable, or more than 20 projects: muted, with the next step
    • no query yet
    • partial-answer notice under the chart
    • empty window
  • Stat and gauge tiles from a PromQL query.
  • A shared board and a single-chart share with a PromQL tile, including a dashboard variable set to All.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

A widget data source kind `promql` stores a PromQL query, optional projects and a legend template. The browser and the share API both run it through GcpPromQlService as a range query with the chart's bucket as step, and draw the answer as wide chart rows. The editor gets a Google Cloud source tab with a metric search that writes a starter query.
Share links hold a query variable a PromQL tile reads to the free-text charset and count failed scopes instead of naming them. Variables reach PromQL under a `promql` param key, with All as a regex alternation. Browser and share path share the request and row helpers; a request the author can fix is a fix_request error. The editor marks a stale preview, shows search progress and failures, and reduces stat and gauge tiles by the rail's Aggregate.
@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4bdff291-3ede-4249-a691-5609ba65a8ee

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🔴 Confidence 4/10 · risky as written
The share charset guard misses sparkline PromQL sources and two stored shapes fail at view time; the edge cases behind both were read in the code, not assumed.
quality 70/100 · 3 warnings · tests partial · risk medium · 2/2 new units observable

Adds a promql widget data source that charts the org's Google Cloud metrics live through #1475/#1477, with an editor tab, share redaction and variable handling. The shape is sound but the share's variable guard misses sparkline queries, and two stored shapes (empty projects, a stat reduction) fail or misreport at view time. The open WarehouseInvalidInputError issue in warehouse-query-atoms.ts is not addressed: the new atom's failures come from runWarehouseQuery's WarehouseApiError | BackendError channel, not from the query-set clients that raise it.

Findings

🟠 Warning · F1 · promQlQueries skips sparkline sources, so a variable can escape the guard

security · apps/api/src/routes/v2/share.http.ts:305-313

The share executes widget.display?.sparkline?.dataSource when the request asks for source: "sparkline" (DashboardWidgetDataService.ts:267, and the source is whatever the unauthenticated caller sent at share.http.ts:349), but this list walks widget.dataSource alone, so a variable referenced only by a sparkline's PromQL never enters promQlNames and promQlUnsafe stays false (share-variables.ts:150). The viewer's value then needs only to be one of the options the warehouse listed, and a telemetry value containing a quote reaches the promql branch of interpolateString unescaped — plainly, or as every option of an All joined with | — rewriting the author's matcher on a public link. Collect both data sources here.

Build the list from `widget.dataSource` and `widget.display?.sparkline?.dataSource` alike before mapping out the query strings, and add a sparkline-only reference to the `share-variables.test.ts` case.
🟠 Warning · F2 · dataSourcePromQl forwards an empty projects list every read rejects

correctness · packages/widgets/src/dashboard/access.ts:187-189

projects: [] is the documented "every project" form and V2PromQlDataSource stores it, but PromQlProjects requires at least one id (packages/domain/src/http/gcp-promql.ts:31), so promQlRangeInput (packages/query-engine/src/gcp-promql.ts:28) hands the empty array to GcpPromQlRangeRequest and the decode fails: the tile shows a request error on every view, signed in and on a share, instead of reading all connected projects. The editor already omits the field when nothing is typed (widget-query-builder-page.tsx:177).

export const dataSourcePromQl = (dataSource: unknown): PromQlDataSource | null => {
	if (!isRecord(dataSource) || dataSource.kind !== "promql") return null
	// An empty list means "every project", but `PromQlProjects` wants at least one id.
	const projects = Array.isArray(dataSource.projects) ? dataSource.projects.filter(isString) : []
	return {
		query: isString(dataSource.query) ? dataSource.query : "",
		...(projects.length > 0 ? { projects } : undefined),
		...(isString(dataSource.legend) ? { legend: dataSource.legend } : undefined),
	}
}
🟠 Warning · F3 · Stat/gauge PromQL tiles reduce a value field the rows never carry

correctness · apps/web/src/components/dashboard-builder/config/widget-query-builder-page.tsx:172

promQlChartRows emits wide rows { bucket, [series]: value } (packages/query-engine/src/gcp-promql.ts:68), so reduceToValue.field: "value" never matches and applyTransform falls back to the first numeric column of rows[0] (use-widget-data.ts:206): a stat over a query with several series sums, averages, mins or maxes one arbitrary series, and count returns the number of time buckets rather than the series count. Reduce the row's value columns for a PromQL source instead of naming a value field.

Emit a `value` column from `promQlChartRows` (or reshape with a `flattenSeries`-style transform) so the rail's Aggregate applies to every series rather than the first one.
🤖 Prompt to fix all 3 findings with an AI agent
Findings from an automated review of commit 7c45912f90649a16a32a4a4b19b941697791c54c. Verify each one against the current code before changing anything, fix only those that still apply, and keep each fix to the lines it names.

---

F1 · Warning · security · apps/api/src/routes/v2/share.http.ts:305-313
`promQlQueries` skips sparkline sources, so a variable can escape the guard
The share executes `widget.display?.sparkline?.dataSource` when the request asks for `source: "sparkline"` (`DashboardWidgetDataService.ts:267`, and the source is whatever the unauthenticated caller sent at `share.http.ts:349`), but this list walks `widget.dataSource` alone, so a variable referenced only by a sparkline's PromQL never enters `promQlNames` and `promQlUnsafe` stays false (`share-variables.ts:150`). The viewer's value then needs only to be one of the options the warehouse listed, and a telemetry value containing a quote reaches the `promql` branch of `interpolateString` unescaped — plainly, or as every option of an All joined with `|` — rewriting the author's matcher on a public link. Collect both data sources here.
Suggested fix: Build the list from `widget.dataSource` and `widget.display?.sparkline?.dataSource` alike before mapping out the query strings, and add a sparkline-only reference to the `share-variables.test.ts` case.

---

F2 · Warning · correctness · packages/widgets/src/dashboard/access.ts:187-189
`dataSourcePromQl` forwards an empty `projects` list every read rejects
`projects: []` is the documented "every project" form and `V2PromQlDataSource` stores it, but `PromQlProjects` requires at least one id (`packages/domain/src/http/gcp-promql.ts:31`), so `promQlRangeInput` (`packages/query-engine/src/gcp-promql.ts:28`) hands the empty array to `GcpPromQlRangeRequest` and the decode fails: the tile shows a request error on every view, signed in and on a share, instead of reading all connected projects. The editor already omits the field when nothing is typed (`widget-query-builder-page.tsx:177`).
Replace those lines with:
export const dataSourcePromQl = (dataSource: unknown): PromQlDataSource | null => {
	if (!isRecord(dataSource) || dataSource.kind !== "promql") return null
	// An empty list means "every project", but `PromQlProjects` wants at least one id.
	const projects = Array.isArray(dataSource.projects) ? dataSource.projects.filter(isString) : []
	return {
		query: isString(dataSource.query) ? dataSource.query : "",
		...(projects.length > 0 ? { projects } : undefined),
		...(isString(dataSource.legend) ? { legend: dataSource.legend } : undefined),
	}
}

---

F3 · Warning · correctness · apps/web/src/components/dashboard-builder/config/widget-query-builder-page.tsx:172
Stat/gauge PromQL tiles reduce a `value` field the rows never carry
`promQlChartRows` emits wide rows `{ bucket, [series]: value }` (`packages/query-engine/src/gcp-promql.ts:68`), so `reduceToValue.field: "value"` never matches and `applyTransform` falls back to the first numeric column of `rows[0]` (`use-widget-data.ts:206`): a stat over a query with several series sums, averages, mins or maxes one arbitrary series, and `count` returns the number of time buckets rather than the series count. Reduce the row's value columns for a PromQL source instead of naming a `value` field.
Suggested fix: Emit a `value` column from `promQlChartRows` (or reshape with a `flattenSeries`-style transform) so the rail's Aggregate applies to every series rather than the first one.

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/web/errors/WarehouseInvalidInputError maple-web 3 apps/web/src/lib/services/atoms/warehouse-query-atoms.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/backend/src/services/dashboards/DashboardWidgetDataService.ts 3 DashboardWidgetDataService.resolve 3/day, 23.8% err
Telemetry this change adds and removes (3)
  • ➕ span name Integrations.getGcpPromQlChart · apps/web/src/api/warehouse/gcp-promql.ts:24
  • ➕ span name Integrations.getGcpMetricSearch · apps/web/src/api/warehouse/gcp-promql.ts:44
  • ➕ span name Integrations.getGcpMetricDescriptor · apps/web/src/api/warehouse/gcp-promql.ts:58
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
getGcpPromQlChart / metric search / describe server functions (apps/web) server function + outbound read yes runWarehouseQuery wraps each in Effect.withSpan(operation) (apps/web/src/api/warehouse/effect-utils.ts:236)
Share PromQL tile execution (apps/api share widgetData) inbound endpoint + outbound Google read yes resolve annotates maple.widget.* (DashboardWidgetDataService.ts:249); GcpPromQlService.queryRange annotates orgId (GcpPromQlService.ts:400)

7c45912 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@maple-review-bot maple-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 inline notes from Maple's review. The score and summary are in the review comment above.

})
})

const promQlQueries = document.widgets.flatMap((widget) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

promQlQueries skips sparkline sources, so a variable can escape the guard

F1 · Warning · security

The share executes widget.display?.sparkline?.dataSource when the request asks for source: "sparkline" (DashboardWidgetDataService.ts:267, and the source is whatever the unauthenticated caller sent at share.http.ts:349), but this list walks widget.dataSource alone, so a variable referenced only by a sparkline's PromQL never enters promQlNames and promQlUnsafe stays false (share-variables.ts:150). The viewer's value then needs only to be one of the options the warehouse listed, and a telemetry value containing a quote reaches the promql branch of interpolateString unescaped — plainly, or as every option of an All joined with | — rewriting the author's matcher on a public link. Collect both data sources here.

Build the list from `widget.dataSource` and `widget.display?.sparkline?.dataSource` alike before mapping out the query strings, and add a sparkline-only reference to the `share-variables.test.ts` case.
🤖 Prompt to fix with an AI agent
In `apps/api/src/routes/v2/share.http.ts:305-313`: `promQlQueries` skips sparkline sources, so a variable can escape the guard.

The share executes `widget.display?.sparkline?.dataSource` when the request asks for `source: "sparkline"` (`DashboardWidgetDataService.ts:267`, and the source is whatever the unauthenticated caller sent at `share.http.ts:349`), but this list walks `widget.dataSource` alone, so a variable referenced only by a sparkline's PromQL never enters `promQlNames` and `promQlUnsafe` stays false (`share-variables.ts:150`). The viewer's value then needs only to be one of the options the warehouse listed, and a telemetry value containing a quote reaches the `promql` branch of `interpolateString` unescaped — plainly, or as every option of an All joined with `|` — rewriting the author's matcher on a public link. Collect both data sources here.

Suggested fix: Build the list from `widget.dataSource` and `widget.display?.sparkline?.dataSource` alike before mapping out the query strings, and add a sparkline-only reference to the `share-variables.test.ts` case.

Verify the problem exists at that location before changing it, and keep the fix to those lines.

Comment on lines +187 to +189
...(Array.isArray(dataSource.projects)
? { projects: dataSource.projects.filter(isString) }
: undefined),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

dataSourcePromQl forwards an empty projects list every read rejects

F2 · Warning · correctness

projects: [] is the documented "every project" form and V2PromQlDataSource stores it, but PromQlProjects requires at least one id (packages/domain/src/http/gcp-promql.ts:31), so promQlRangeInput (packages/query-engine/src/gcp-promql.ts:28) hands the empty array to GcpPromQlRangeRequest and the decode fails: the tile shows a request error on every view, signed in and on a share, instead of reading all connected projects. The editor already omits the field when nothing is typed (widget-query-builder-page.tsx:177).

Suggested change
...(Array.isArray(dataSource.projects)
? { projects: dataSource.projects.filter(isString) }
: undefined),
export const dataSourcePromQl = (dataSource: unknown): PromQlDataSource | null => {
if (!isRecord(dataSource) || dataSource.kind !== "promql") return null
// An empty list means "every project", but `PromQlProjects` wants at least one id.
const projects = Array.isArray(dataSource.projects) ? dataSource.projects.filter(isString) : []
return {
query: isString(dataSource.query) ? dataSource.query : "",
...(projects.length > 0 ? { projects } : undefined),
...(isString(dataSource.legend) ? { legend: dataSource.legend } : undefined),
}
}
🤖 Prompt to fix with an AI agent
In `packages/widgets/src/dashboard/access.ts:187-189`: `dataSourcePromQl` forwards an empty `projects` list every read rejects.

`projects: []` is the documented "every project" form and `V2PromQlDataSource` stores it, but `PromQlProjects` requires at least one id (`packages/domain/src/http/gcp-promql.ts:31`), so `promQlRangeInput` (`packages/query-engine/src/gcp-promql.ts:28`) hands the empty array to `GcpPromQlRangeRequest` and the decode fails: the tile shows a request error on every view, signed in and on a share, instead of reading all connected projects. The editor already omits the field when nothing is typed (`widget-query-builder-page.tsx:177`).

Replace those lines with:

export const dataSourcePromQl = (dataSource: unknown): PromQlDataSource | null => {
	if (!isRecord(dataSource) || dataSource.kind !== "promql") return null
	// An empty list means "every project", but `PromQlProjects` wants at least one id.
	const projects = Array.isArray(dataSource.projects) ? dataSource.projects.filter(isString) : []
	return {
		query: isString(dataSource.query) ? dataSource.query : "",
		...(projects.length > 0 ? { projects } : undefined),
		...(isString(dataSource.legend) ? { legend: dataSource.legend } : undefined),
	}
}

Verify the problem exists at that location before changing it, and keep the fix to those lines.

const existingTransform = dataSourceTransform(widget.dataSource)
const transform =
widgetTypeByVisualization(state.visualization)?.isScalar === true
? { ...existingTransform, reduceToValue: { field: "value", aggregate: state.statAggregate } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Stat/gauge PromQL tiles reduce a value field the rows never carry

F3 · Warning · correctness

promQlChartRows emits wide rows { bucket, [series]: value } (packages/query-engine/src/gcp-promql.ts:68), so reduceToValue.field: "value" never matches and applyTransform falls back to the first numeric column of rows[0] (use-widget-data.ts:206): a stat over a query with several series sums, averages, mins or maxes one arbitrary series, and count returns the number of time buckets rather than the series count. Reduce the row's value columns for a PromQL source instead of naming a value field.

Emit a `value` column from `promQlChartRows` (or reshape with a `flattenSeries`-style transform) so the rail's Aggregate applies to every series rather than the first one.
🤖 Prompt to fix with an AI agent
In `apps/web/src/components/dashboard-builder/config/widget-query-builder-page.tsx:172`: Stat/gauge PromQL tiles reduce a `value` field the rows never carry.

`promQlChartRows` emits wide rows `{ bucket, [series]: value }` (`packages/query-engine/src/gcp-promql.ts:68`), so `reduceToValue.field: "value"` never matches and `applyTransform` falls back to the first numeric column of `rows[0]` (`use-widget-data.ts:206`): a stat over a query with several series sums, averages, mins or maxes one arbitrary series, and `count` returns the number of time buckets rather than the series count. Reduce the row's value columns for a PromQL source instead of naming a `value` field.

Suggested fix: Emit a `value` column from `promQlChartRows` (or reshape with a `flattenSeries`-style transform) so the rail's Aggregate applies to every series rather than the first one.

Verify the problem exists at that location before changing it, and keep the fix to those lines.

Starter queries group by project_id, and default series keys keep metrics_scope, which tells a scope's projects apart behind one answering project.
@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🟡 Confidence 5/10 · needs attention
The hunks since 7c45912 are narrow and additive, pinned by tests, but the three earlier PromQL warnings still hold at this head.
quality 70/100 · 3 warnings · tests covered · risk medium · 3/3 new units observable

Dashboard tiles can chart a live PromQL query over Google Cloud metrics, with a Google Cloud source tab, a share path that runs only the stored query, and starter queries now grouped per project. The three earlier warnings (F1, F2, F3) still hold at this head; the WarehouseInvalidInputError in warehouse-query-atoms.ts is a breakdown-query error this change does not touch.

  • promQlStarterQuery now groups every starter query by project_id, so projects no longer merge
  • New promql widget data source, lowered to the gcp_promql endpoint and redacted on shares
  • Share variables a PromQL tile references are held to the free-text charset
  • Partial Google answers surface a notice under the chart

Before merge

Still open from earlier reviews

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/web/errors/WarehouseInvalidInputError maple-web 3 apps/web/src/lib/services/atoms/warehouse-query-atoms.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/backend/src/services/dashboards/DashboardWidgetDataService.ts 3 DashboardWidgetDataService.resolve 3/day, 23.8% err
Telemetry this change adds and removes (3)
  • ➕ span name Integrations.getGcpPromQlChart · apps/web/src/api/warehouse/gcp-promql.ts:24
  • ➕ span name Integrations.getGcpMetricSearch · apps/web/src/api/warehouse/gcp-promql.ts:44
  • ➕ span name Integrations.getGcpMetricDescriptor · apps/web/src/api/warehouse/gcp-promql.ts:58
What was checked
  • Step can never exceed Google's 86 400 s cap: computeBucketSecondsForRange picks from AUTO_BUCKET_LADDER (max 86 400, datetime.ts:750)
  • Legend collisions stay apart via the used set suffixing (2) (gcp-promql.ts:66)
  • Share PromQL reads are spanned: runWarehouseQuery (effect-utils.ts:236) plus GcpPromQlService.queryRange
Observability coverage: 3 of 3 changes observable
Change Kind Observable Evidence
Browser gcp_promql widget read (server function) outbound yes runWarehouseQuery("gcpPromQlQueryRange", …) wraps it in a span (apps/web/src/api/warehouse/effect-utils.ts:236)
Share resolution of a PromQL tile outbound yes DashboardWidgetDataService.resolve is an Effect.fn span and GcpPromQlService.queryRange annotates it (DashboardWidgetDataService.ts:242, GcpPromQlService.ts:396)
Editor metric search / describe outbound yes getGcpMetricSearch and getGcpMetricDescriptor both go through runWarehouseQuery (gcp-promql.ts:49, 61)

3372d50 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🔴 Confidence 4/10 · risky as written
The share and interpolation paths are unit-tested and the new endpoint is traced; the All-alternation splice is the one unescaped edge left.
quality 60/100 · 4 warnings · tests covered · risk medium · 3/3 new units observable

Adds a promql dashboard source that charts live Google Cloud Monitoring reads in the tile, the editor and shared boards, with share variables held to the free-text charset. One defect: an All selection splices an unescaped alternation into the matcher. The open WarehouseInvalidInputError in warehouse-query-atoms.ts is not addressed by this diff.

  • promql data source lowers to endpoint gcp_promql and renders wide chart rows
  • The share resolver runs the stored query through GcpPromQlService, redacting query, projects and legend
  • A Google Cloud source tab with metric search, starter queries and Run preview
  • All of a query variable becomes a | alternation inside a PromQL matcher

Findings

🟠 Warning · F4 · An All selection splices a bare a|b alternation into the matcher

correctness · packages/query-engine/src/dashboard-variables/interpolate.ts:106

variable.options.join("|") is substituted at the reference's position, so a matcher that is more than the bare variable describes the wrong set: with options ["dev","prod"], {env=~"api-$env"} becomes api-dev|prod, which (PromQL anchors =~ over the whole expression) also matches a series named prod. An option holding a regex metacharacter — a hostname or a project id with a ., a value with + — matches values it does not name, on every member view as well as the share. Group and escape each alternative the way the sql branch escapes its literals, e.g. "(" + options.map(escapePromQlRegex).join("|") + ")".

Add a small PromQL-regex escaper next to `escapeClickHouseString` usage and return `(${variable.options.map(escapePromQlRegex).join("|")})` for an All, so the alternation is grouped at the reference's position and each option is matched literally. Grafana's Prometheus datasource does the same.
🤖 Prompt to fix this finding with an AI agent
Findings from an automated review of commit 24cd5c76d1c61806f1e16e2a1fc5853e774a9772. Verify each one against the current code before changing anything, fix only those that still apply, and keep each fix to the lines it names.

---

F4 · Warning · correctness · packages/query-engine/src/dashboard-variables/interpolate.ts:106
An All selection splices a bare `a|b` alternation into the matcher
`variable.options.join("|")` is substituted at the reference's position, so a matcher that is more than the bare variable describes the wrong set: with options `["dev","prod"]`, `{env=~"api-$env"}` becomes `api-dev|prod`, which (PromQL anchors `=~` over the whole expression) also matches a series named `prod`. An option holding a regex metacharacter — a hostname or a project id with a `.`, a value with `+` — matches values it does not name, on every member view as well as the share. Group and escape each alternative the way the `sql` branch escapes its literals, e.g. `"(" + options.map(escapePromQlRegex).join("|") + ")"`.
Suggested fix: Add a small PromQL-regex escaper next to `escapeClickHouseString` usage and return `(${variable.options.map(escapePromQlRegex).join("|")})` for an All, so the alternation is grouped at the reference's position and each option is matched literally. Grafana's Prometheus datasource does the same.

Still open from earlier reviews

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/web/errors/WarehouseInvalidInputError maple-web 3 apps/web/src/lib/services/atoms/warehouse-query-atoms.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/backend/src/services/dashboards/DashboardWidgetDataService.ts 3 DashboardWidgetDataService.resolve 3/day, 23.8% err
Telemetry this change adds and removes (3)
  • ➕ span name Integrations.getGcpPromQlChart · apps/web/src/api/warehouse/gcp-promql.ts:24
  • ➕ span name Integrations.getGcpMetricSearch · apps/web/src/api/warehouse/gcp-promql.ts:44
  • ➕ span name Integrations.getGcpMetricDescriptor · apps/web/src/api/warehouse/gcp-promql.ts:58
What was checked
  • Share redaction drops query, projects and legend (packages/widgets/src/dashboard/redact.ts:90, pinned by promql-source.test.ts:60)
  • Share variable values stay inside TEXTBOX_ALLOWED, All included (packages/backend/src/services/dashboards/share-variables.ts:150)
  • promQlRangeInput trims the query and floors the step at 60 s (packages/query-engine/src/gcp-promql.ts:24)
Observability coverage: 3 of 3 changes observable
Change Kind Observable Evidence
gcp_promql range read from a dashboard tile / share outbound call yes Effect.fn("GcpPromQlService.queryRange") with orgId annotation and maple.gcp.projects (packages/backend/src/services/integrations/GcpPromQlService.ts:495); resolve annotates org/widget/source (DashboardWidgetDataService.ts:249)
browser server function getGcpPromQlChart / getGcpMetricSearch outbound call yes runWarehouseQuery("gcpPromQlQueryRange") wraps the /internal client call (apps/web/src/api/warehouse/gcp-promql.ts:34)
new tile failure path (Google refusal, partial answer) error path yes asWidgetOutcomeFailure(request.widgetId, "promql") logs the cause and maps to a tagged failure (DashboardWidgetDataService.ts:445)

24cd5c7 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@maple-review-bot maple-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 inline note from Maple's review. The score and summary are in the review comment above.

return replaceRefs(input, (name) => {
const variable = values[name]
if (variable === undefined) return null
return variable.isAll ? variable.options.join("|") : variable.value

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

An All selection splices a bare a|b alternation into the matcher

F4 · Warning · correctness

variable.options.join("|") is substituted at the reference's position, so a matcher that is more than the bare variable describes the wrong set: with options ["dev","prod"], {env=~"api-$env"} becomes api-dev|prod, which (PromQL anchors =~ over the whole expression) also matches a series named prod. An option holding a regex metacharacter — a hostname or a project id with a ., a value with + — matches values it does not name, on every member view as well as the share. Group and escape each alternative the way the sql branch escapes its literals, e.g. "(" + options.map(escapePromQlRegex).join("|") + ")".

Add a small PromQL-regex escaper next to `escapeClickHouseString` usage and return `(${variable.options.map(escapePromQlRegex).join("|")})` for an All, so the alternation is grouped at the reference's position and each option is matched literally. Grafana's Prometheus datasource does the same.
🤖 Prompt to fix with an AI agent
In `packages/query-engine/src/dashboard-variables/interpolate.ts:106`: An All selection splices a bare `a|b` alternation into the matcher.

`variable.options.join("|")` is substituted at the reference's position, so a matcher that is more than the bare variable describes the wrong set: with options `["dev","prod"]`, `{env=~"api-$env"}` becomes `api-dev|prod`, which (PromQL anchors `=~` over the whole expression) also matches a series named `prod`. An option holding a regex metacharacter — a hostname or a project id with a `.`, a value with `+` — matches values it does not name, on every member view as well as the share. Group and escape each alternative the way the `sql` branch escapes its literals, e.g. `"(" + options.map(escapePromQlRegex).join("|") + ")"`.

Suggested fix: Add a small PromQL-regex escaper next to `escapeClickHouseString` usage and return `(${variable.options.map(escapePromQlRegex).join("|")})` for an All, so the alternation is grouped at the reference's position and each option is matched literally. Grafana's Prometheus datasource does the same.

Verify the problem exists at that location before changing it, and keep the fix to those lines.

@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🔴 Confidence 2/10 · do not merge
All four earlier warnings are still live at this head, one of them in the share's PromQL variable guard, and none has a failing test.
quality 60/100 · 4 warnings · tests partial · risk high · 3/3 new units observable

Adds a promql data source that charts Google Cloud Monitoring live, through the editor, the share resolver and the v2 API. Mostly additive and sound, but four earlier warnings still stand, including a share-link variable guard that a variable can escape. The open WarehouseInvalidInputError ("No query data found in selected time range") is the pre-existing empty-result state in the warehouse atoms; this diff does not fix it and adds one more way to reach it (an empty PromQL answer).

  • promql sources lower to the gcp_promql endpoint with { promql, projects?, legend? }
  • Share resolver holds query variables used inside PromQL to the free-text charset
  • redactForShare strips a PromQL tile's query, projects and legend
  • Editor gains a Google Cloud tab with metric search and starter queries

Before merge

Still open from earlier reviews

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/web/errors/WarehouseInvalidInputError maple-web 9 apps/web/src/lib/services/atoms/warehouse-query-atoms.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/backend/src/services/dashboards/DashboardWidgetDataService.ts 3 DashboardWidgetDataService.resolve 3/day, 11.1% err
Telemetry this change adds and removes (3)
  • ➕ span name Integrations.getGcpPromQlChart · apps/web/src/api/warehouse/gcp-promql.ts:24
  • ➕ span name Integrations.getGcpMetricSearch · apps/web/src/api/warehouse/gcp-promql.ts:44
  • ➕ span name Integrations.getGcpMetricDescriptor · apps/web/src/api/warehouse/gcp-promql.ts:58
What was checked
  • A stored projects: [] rides dataSourcePromQl into GcpPromQlRangeRequest, which rejects it (access.ts:187, gcp-promql.ts:45)
  • The share resolver runs the stored, unredacted document while the browser gets the redacted one (share.http.ts:346 vs :421)
  • The new server function and outbound reads carry spans via runWarehouseQuery and GcpPromQlService.queryRange (effect-utils.ts:236, GcpPromQlService.ts:495)
Observability coverage: 3 of 3 changes observable
Change Kind Observable Evidence
getGcpPromQlChart widget server function inbound server function yes runWarehouseQuery("gcpPromQlChart", …) applies Effect.withSpan(operation) (apps/web/src/api/warehouse/effect-utils.ts:236)
PromQL tile read in DashboardWidgetDataService.resolve outbound call yes GcpPromQlService.queryRange is Effect.fn and annotates orgId (packages/backend/src/services/integrations/GcpPromQlService.ts:495)
Metric search and describe from the editor outbound call yes runWarehouseQuery wrappers in apps/web/src/api/warehouse/gcp-promql.ts over GcpPromQlService.searchMetrics / describeMetric

d512231 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🔴 Confidence 2/10 · do not merge
Four earlier warnings (share variable guard, empty projects, stat reducer field, All alternation) are unfixed at this head; the new lowering, redaction and row-shaping code reads correctly.
quality 60/100 · 4 warnings · tests partial · risk high · 2/2 new units observable

Adds a promql widget data source kind that charts live Google Cloud Monitoring PromQL reads, with an editor tab, share redaction and variable interpolation. The new code is sound, but the four earlier findings are unaddressed at this head; the open WarehouseInvalidInputError is not fixed by the diff and the new decode path can still raise it.

  • promql sources lower to endpoint gcp_promql and chart rows live from Google
  • Share redaction drops a promql source's query, projects and legend
  • An All selection is spliced into a PromQL matcher as a|b
  • The editor offers a Google Cloud tab for line, area, bar, stat and gauge

Still open from earlier reviews

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/web/errors/WarehouseInvalidInputError maple-web 9 apps/web/src/lib/services/atoms/warehouse-query-atoms.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/backend/src/services/dashboards/DashboardWidgetDataService.ts 3 DashboardWidgetDataService.resolve 3/day, 11.1% err
Telemetry this change adds and removes (3)
  • ➕ span name Integrations.getGcpPromQlChart · apps/web/src/api/warehouse/gcp-promql.ts:24
  • ➕ span name Integrations.getGcpMetricSearch · apps/web/src/api/warehouse/gcp-promql.ts:44
  • ➕ span name Integrations.getGcpMetricDescriptor · apps/web/src/api/warehouse/gcp-promql.ts:58
What was checked
  • promQlRangeInput floors the step at 60 s and the read caps it (gcp-promql.ts:24)
  • Only the member notice carries scope and cause; the share notice counts scopes (gcp-promql.ts:95)
  • redactDataSource is an allowlist and drops the promql query, projects and legend (redact.ts:90)
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
PromQL widget read from the editor's server function getGcpPromQlChart client call to the API's PromQL proxy yes runWarehouseQuery("gcpPromQlQueryRange", …) drives a client span (apps/web/src/api/warehouse/gcp-promql.ts:34)
Share resolver reads Google through GcpPromQlService.queryRange client call to Google Cloud Monitoring yes Effect.fn("GcpPromQlService.queryRange") span, pendingReads/PendingScopes annotation (packages/backend/src/services/integrations/GcpPromQlService.ts:100-110)

bc3e84b · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🔴 Confidence 2/10 · do not merge
Four open warnings survive at this head: the share variable guard misses sparkline sources, and an empty projects list breaks every read it is stored on.
quality 60/100 · 4 warnings · tests partial · risk high · 2/2 new units observable

Adds a promql widget data source that charts live Google Cloud metrics, from the editor and from the public v2 API, with the pure row-shaping helpers shared by the browser server function and the share resolver. The four open findings are all still present in this head, so it needs attention before merge. The open @maple/web/errors/WarehouseInvalidInputError issue (message "No query data found in selected time range", last seen 2026-08-11) is not addressed by this diff; it only adds a new call site for genuinely malformed input.

Still open from earlier reviews

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/web/errors/WarehouseInvalidInputError maple-web 9 apps/web/src/lib/services/atoms/warehouse-query-atoms.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/backend/src/services/dashboards/DashboardWidgetDataService.ts 3 DashboardWidgetDataService.resolve 3/day, 11.1% err
Telemetry this change adds and removes (3)
  • ➕ span name Integrations.getGcpPromQlChart · apps/web/src/api/warehouse/gcp-promql.ts:24
  • ➕ span name Integrations.getGcpMetricSearch · apps/web/src/api/warehouse/gcp-promql.ts:44
  • ➕ span name Integrations.getGcpMetricDescriptor · apps/web/src/api/warehouse/gcp-promql.ts:58
What was checked
  • Every hunk of the 40-file diff was read; the 13 files the kickoff lists as changed since bc3e84b are not in this PR's base..head diff (git diff --stat vs 356069a)
  • promQlQueries still walks widget.dataSource only (share.http.ts:305) while the share executes display.sparkline.dataSource (DashboardWidgetDataService.ts:267) — F1 confirmed open
  • PromQlProjects requires isMinLength(1) (packages/domain/src/http/gcp-promql.ts:45-48) and both decoders accept projects: [], so F2 confirmed open
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
gcp_promql widget data through the browser server function client yes runWarehouseQuery wraps the internal client call with a span (apps/web/src/api/warehouse/effect-utils.ts:230); the function is registered in data-source-registry.ts:77
Share-resolver PromQL read (DashboardWidgetDataService.resolve → GcpPromQlService.queryRange) client yes runs inside the existing DashboardWidgetDataService.resolve span (DashboardWidgetDataService.ts:433)

830d90a · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🔴 Confidence 3/10 · risky as written
All four earlier findings still reproduce at this head; the new lowering, rows, share guard and service branch are pinned by tests.
quality 60/100 · 4 warnings · tests partial · risk medium · 2/2 new units observable

Adds a live promql dashboard data source: chart rows from Google PromQL, an editor tab with metric search, and the share path. The shared primitives are tested, but the four earlier findings all still reproduce here, so it is not ready as-is. The open WarehouseInvalidInputError in warehouse-query-atoms.ts is untouched by this diff.

  • toWidgetRequest lowers a promql source to endpoint gcp_promql
  • promQlWidgetData turns an answer into one keyed row per timestamp
  • The browser runs it lazily through getGcpPromQlChart, not the warehouse
  • resolveShareVariables holds PromQL-referenced query variables to the free-text charset

Before merge

Still open from earlier reviews

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/web/errors/WarehouseInvalidInputError maple-web 9 apps/web/src/lib/services/atoms/warehouse-query-atoms.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/backend/src/services/dashboards/DashboardWidgetDataService.ts 3 DashboardWidgetDataService.resolve 3/day, 11.1% err
Telemetry this change adds and removes (3)
  • ➕ span name Integrations.getGcpPromQlChart · apps/web/src/api/warehouse/gcp-promql.ts:24
  • ➕ span name Integrations.getGcpMetricSearch · apps/web/src/api/warehouse/gcp-promql.ts:44
  • ➕ span name Integrations.getGcpMetricDescriptor · apps/web/src/api/warehouse/gcp-promql.ts:58
What was checked
  • Empty projects survives to the request: PromQlProjects requires at least one (packages/domain/src/http/gcp-promql.ts:45)
  • Share failures log the cause and return a generic message, not Google's text (DashboardWidgetDataService.ts:87)
  • Disabled promql_query_empty plans are handled on both hosts (widget-request.ts:168, DashboardWidgetDataService.ts:309)
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
Browser server function getGcpPromQlChart / getGcpMetricSearch / getGcpMetricDescriptor (outbound call to the internal API) client yes each goes through runWarehouseQuery + MapleInternalAtomClient (apps/web/src/api/warehouse/gcp-promql.ts:34), the pattern every other warehouse fn on this page uses
GCP_PROMQL_ENDPOINT branch of the batch/share resolver consumer yes inside Effect.fn("DashboardWidgetDataService.resolve"), which reports live (DashboardWidgetDataService.resolve, p95 11100 ms); failures log via executionFailed (DashboardWidgetDataService.ts:445)

b4105f3 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🔴 Confidence 2/10 · do not merge
Four warnings unresolved on the share and stat paths; the sparkline branch and the base-branch configure/lab files sit outside this PR's diff.
quality 60/100 · 4 warnings · tests partial · risk high · 2/2 new units observable

Adds a promql widget data source that charts Google Cloud metrics live, with its editor, share guard and redaction. The four earlier warnings are unchanged at this head, and the open WarehouseInvalidInputError issue is on another path and untouched.

  • promql data source lowers to the gcp_promql endpoint and charts live Google metrics.
  • promQlRangeInput/promQlChartRows are shared by the browser server function and the share resolver.
  • The editor gained a Google Cloud tab with metric search and starter queries.
  • Share resolves only the stored query and redacts query, projects and legend.

Before merge

Still open from earlier reviews

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/web/errors/WarehouseInvalidInputError maple-web 9 apps/web/src/lib/services/atoms/warehouse-query-atoms.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/backend/src/services/dashboards/DashboardWidgetDataService.ts 3 DashboardWidgetDataService.resolve 3/day, 11.1% err
Telemetry this change adds and removes (3)
  • ➕ span name Integrations.getGcpPromQlChart · apps/web/src/api/warehouse/gcp-promql.ts:24
  • ➕ span name Integrations.getGcpMetricSearch · apps/web/src/api/warehouse/gcp-promql.ts:44
  • ➕ span name Integrations.getGcpMetricDescriptor · apps/web/src/api/warehouse/gcp-promql.ts:58
What was checked
  • The share guard keeps a PromQL-referenced query variable to the free-text charset, All included (share-variables.ts:126).
  • stepSeconds floors at 60 s and the ladder caps at 86,400 s, inside GcpPromQlRangeRequest's bounds (datetime.ts:750).
  • The 8 files changed since the last review are base-branch content; pr_file_diff refuses them, so they were only read at head.
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
Dashboard share resolver's live Google Cloud PromQL read outbound call yes Effect.fn("GcpPromQlService.queryRange") at packages/backend/src/services/integrations/GcpPromQlService.ts:495, called from DashboardWidgetDataService.resolve
Browser server functions getGcpPromQlChart / getGcpMetricSearch / getGcpMetricDescriptor outbound call yes runWarehouseQuery wraps the call in Effect.withSpan(operation) (apps/web/src/api/warehouse/effect-utils.ts:236)

b30cd15 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🔴 Confidence 3/10 · risky as written
Thin traffic (~3 share resolves/day) but the four open findings all still reproduce at this head; no test covers the empty-projects or sparkline guard paths.
quality 60/100 · 4 warnings · tests partial · risk medium · 2/2 new units observable

Adds a promql widget data source that charts live Google Cloud Monitoring metrics through a PromQL proxy, on the board, in the editor and on share links. The lowering, redaction and notice plumbing are sound, but the four earlier findings all reproduce at this head, so it should not merge as is. The web WarehouseInvalidInputError issue is not addressed: this change adds a new decode site that raises it (apps/web/src/api/warehouse/gcp-promql.ts:33), for a query the read refuses or an empty projects list.

Still open from earlier reviews

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/web/errors/WarehouseInvalidInputError maple-web 9 apps/web/src/lib/services/atoms/warehouse-query-atoms.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/backend/src/services/dashboards/DashboardWidgetDataService.ts 3 DashboardWidgetDataService.resolve 3/day, 11.1% err
Telemetry this change adds and removes (3)
  • ➕ span name Integrations.getGcpPromQlChart · apps/web/src/api/warehouse/gcp-promql.ts:24
  • ➕ span name Integrations.getGcpMetricSearch · apps/web/src/api/warehouse/gcp-promql.ts:44
  • ➕ span name Integrations.getGcpMetricDescriptor · apps/web/src/api/warehouse/gcp-promql.ts:58
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
getGcpPromQlChart browser server function → API gcp_promql range read (live Google) outbound call yes Server-side GcpPromQlService.queryRange is an Effect.fn with annotateCurrentSpan({ orgId }) (GcpPromQlService.ts:506-510)
share resolver executing a PromQL tile background request work yes Runs inside the existing DashboardWidgetDataService.resolve span (DashboardWidgetDataService.ts:433)

7a655ed · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🔴 Confidence 3/10 · risky as written
Four earlier warnings (share guard, empty projects, stat reduce, All alternation) are all still in the head; the only file changed since the last pass is a bundle-budget constant.
quality 60/100 · 4 warnings · tests partial · risk medium · 2/2 new units observable

Charts Google Cloud metrics live by adding a promql widget data source, plan/execution path, editor and share handling. Safe to merge only after the four open warnings are answered: nothing in the intervening commits fixed them. The open WarehouseInvalidInputError in warehouse-query-atoms.ts is untouched by this diff.

Before merge

  • Manual · Google Cloud connections with a read-only identity must exist in the prd and dev deployments, or every PromQL tile renders the not-connected setup state

Still open from earlier reviews

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/web/errors/WarehouseInvalidInputError maple-web 9 apps/web/src/lib/services/atoms/warehouse-query-atoms.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/backend/src/services/dashboards/DashboardWidgetDataService.ts 3 DashboardWidgetDataService.resolve 3/day, 11.1% err
Telemetry this change adds and removes (3)
  • ➕ span name Integrations.getGcpPromQlChart · apps/web/src/api/warehouse/gcp-promql.ts:24
  • ➕ span name Integrations.getGcpMetricSearch · apps/web/src/api/warehouse/gcp-promql.ts:44
  • ➕ span name Integrations.getGcpMetricDescriptor · apps/web/src/api/warehouse/gcp-promql.ts:58
What was checked
  • dataSourcePromQl keeps projects: [] and promQlRangeInput forwards it, while PromQlProjects requires one id (packages/domain/src/http/gcp-promql.ts:45)
  • promQlChartRows emits wide { bucket, [series]: value } rows (packages/query-engine/src/gcp-promql.ts:69), so reduceToValue.field: "value" misses
  • interpolateString splices options.join("|") at the reference position with no grouping (packages/query-engine/src/dashboard-variables/interpolate.ts:106)
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
Shared-board PromQL tile resolution (DashboardWidgetDataService → GcpPromQlService) inbound resolve + outbound Google read yes Effect.fn("DashboardWidgetDataService.resolve") with Effect.annotateCurrentSpan incl. maple.widget.source (DashboardWidgetDataService.ts:242-254)
Browser server function getGcpPromQlChart outbound call to the API's PromQL proxy yes wrapped in runWarehouseQuery("gcpPromQlQueryRange", …) (apps/web/src/api/warehouse/gcp-promql.ts:34)

a06cada · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🔴 Confidence 3/10 · risky as written
The reviewed delta is a comment and constant, but all four earlier warnings still reproduce at this head.
quality 60/100 · 4 warnings · tests partial · risk medium

Resolves the bundle-budget merge conflicts and raises maxGzipBytes to 706 KB; that change is sound and I found no new defect in it. The four warnings from the earlier review are unchanged at this head, and the new GCP metric-search atom does not touch the WarehouseInvalidInputError path.

  • check-bundle-budget.ts conflict markers removed, budget raised to 706 KB

Still open from earlier reviews

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/web/errors/WarehouseInvalidInputError maple-web 9 apps/web/src/lib/services/atoms/warehouse-query-atoms.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/backend/src/services/dashboards/DashboardWidgetDataService.ts 3 DashboardWidgetDataService.resolve 3/day, 11.1% err
Telemetry this change adds and removes (3)
  • ➕ span name Integrations.getGcpPromQlChart · apps/web/src/api/warehouse/gcp-promql.ts:24
  • ➕ span name Integrations.getGcpMetricSearch · apps/web/src/api/warehouse/gcp-promql.ts:44
  • ➕ span name Integrations.getGcpMetricDescriptor · apps/web/src/api/warehouse/gcp-promql.ts:58
What was checked
  • No conflict markers left in apps/web/perf (sandbox grep)
  • gcpMetricSearchResultAtom only adds a lazy metric-search atom, unrelated to WarehouseInvalidInputError

27385c0 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

# Conflicts:
#	apps/web/perf/check-bundle-budget.ts
@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🔴 Confidence 2/10 · do not merge
The four earlier defects are all still present verbatim at this head; the new lowering and share code themselves read correctly.
quality 60/100 · 4 warnings · tests partial · risk medium · 0/1 new units observable

Adds a promql dashboard source charted live from Google Cloud Monitoring via GcpPromQlService, with editor, share redaction and variable substitution. All four defects raised at 27385c0 survive at this head, so it is not safe to merge yet. The open WarehouseInvalidInputError in warehouse-query-atoms.ts is untouched by this change.

  • toWidgetRequest lowers a promql source to endpoint gcp_promql
  • promQlChartRows turns Google series into wide {bucket, series} rows
  • Share guard promQlQueries restricts PromQL variable values to the free-text charset
  • Google Cloud source tab with metric search and starter queries

Still open from earlier reviews

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/web/errors/WarehouseInvalidInputError maple-web 9 apps/web/src/lib/services/atoms/warehouse-query-atoms.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/backend/src/services/dashboards/DashboardWidgetDataService.ts 3 DashboardWidgetDataService.resolve 3/day, 11.1% err
Telemetry this change adds and removes (3)
  • ➕ span name Integrations.getGcpPromQlChart · apps/web/src/api/warehouse/gcp-promql.ts:24
  • ➕ span name Integrations.getGcpMetricSearch · apps/web/src/api/warehouse/gcp-promql.ts:44
  • ➕ span name Integrations.getGcpMetricDescriptor · apps/web/src/api/warehouse/gcp-promql.ts:58
What was checked
  • promQlRangeInput floors the step at GCP_PROMQL_MIN_STEP_SECONDS (packages/query-engine/src/gcp-promql.ts:24)
  • Share redaction drops query, projects and legend from a PromQL source
  • Share-variables guard still reads only widget.dataSource, so F1 stands (share.http.ts:305)
Observability coverage: 0 of 1 changes observable
Change Kind Observable Evidence
getGcpPromQlChart server function (browser -> Google Monitoring read) server function + outbound call no apps/web/src/api/warehouse/gcp-promql.ts reviewed; no explicit span and I did not verify framework auto-instrumentation

65b0807 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

# Conflicts:
#	apps/web/perf/check-bundle-budget.ts
@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🔴 Confidence 3/10 · risky as written
Only a bundle-budget constant changed since the last review, but all four earlier warnings still reproduce at this head.
quality 60/100 · 4 warnings · tests partial · risk medium · 2/2 new units observable

Adds a live PromQL data source that charts Google Cloud Monitoring metrics on dashboard tiles, plus an editor tab, share-path redaction and docs. The only change since the last review is the web bundle budget; the four open warnings are unchanged, so this is not ready to merge. The open WarehouseInvalidInputError ("Breakdown query too broad without filters") is on the breakdown-query path this diff does not touch, so it is not addressed here.

  • Widgets gain a promql v3 data source read live from Google, with no ingest
  • check-bundle-budget.ts raises maxGzipBytes from 707.5 KB to 710 KB
  • New Google Cloud source tab and PromQlEditorPanel for time-series panels
  • redactForShare drops query, projects and legend for shared boards

Still open from earlier reviews

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/web/errors/WarehouseInvalidInputError maple-web 9 apps/web/src/lib/services/atoms/warehouse-query-atoms.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/backend/src/services/dashboards/DashboardWidgetDataService.ts 3 DashboardWidgetDataService.resolve 3/day, 11.1% err
Telemetry this change adds and removes (3)
  • ➕ span name Integrations.getGcpPromQlChart · apps/web/src/api/warehouse/gcp-promql.ts:24
  • ➕ span name Integrations.getGcpMetricSearch · apps/web/src/api/warehouse/gcp-promql.ts:44
  • ➕ span name Integrations.getGcpMetricDescriptor · apps/web/src/api/warehouse/gcp-promql.ts:58
What was checked
  • Only edit since the last review is the budget comment and maxGzipBytes constant (apps/web/perf/check-bundle-budget.ts:113), no logic change
  • interpolate.ts:106, access.ts:188, widget-query-builder-page.tsx:172 and share.http.ts:305 re-read at the head; all four open warnings still reproduce unchanged
  • promQlChartRows emits wide { bucket, [series]: value } rows (gcp-promql.ts:68), confirming the open stat/gauge finding is not fixed
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
widget PromQL range read (getGcpPromQlChart → internal atom client) client yes Effect.fn("Integrations.getGcpPromQlChart") span, apps/web/src/api/warehouse/gcp-promql.ts:24
GCP metric search and describe reads (getGcpMetricSearch, getGcpMetricDescriptor) client yes Effect.fn("Integrations.getGcpMetricSearch" / "getGcpMetricDescriptor"), apps/web/src/api/warehouse/gcp-promql.ts:44

bac2dcc · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant