Conversation
nishantmonu51
left a comment
There was a problem hiding this comment.
The branch conflicts with main in KPIProvider.svelte and kpi-grid/index.ts, and the rebase needs to carry more than a marker resolution. On main, totalQuery and comparisonTotalQuery are gated on supportsTotal (#9814), because a measure with required_dimensions has no single total and KPI.svelte hides its comparison block, and every request goes through mapEphemeralMeasuresForRequest / splitTimeSeriesMeasures (#9855, #9864) so adhoc_measures resolve. The new measureComparison branches check only showComparison && isValid && visible and send a raw [{ name: comparisonMeasureKey }], so once merged a kpi_grid measure with required_dimensions fires a comparison totals query the card never renders, and a compare_to pointing at an adhoc_measures entry is sent as a plain measure name and rejected by the runtime as unknown. The backend carries the mirror decision: the measures loop in validateKPIGrid on main accepts ephemeral names while the new compare_to check uses metricsViewHasMeasure only, so both sides need to agree on whether compare_to may name an ad-hoc measure.
One detail to preserve in the resolution: this PR moves .toLowerCase() out of KPI.svelte into the provider's time-comparison branch so a target's display name keeps its casing, while main still lowercases in KPI.svelte and that hunk auto-merges. The provider-side .toLowerCase() has to survive, or time-comparison labels change from "previous period" to "Previous period".
| if !ok { | ||
| return errors.New("each entry in 'measure_comparisons' must be an object with 'measure' and 'compare_to'") | ||
| } | ||
| if _, ok := pathutil.GetPathString(entry, "measure"); !ok { |
There was a problem hiding this comment.
measure is only checked for being a string, so a typo such as measure: revenu reconciles cleanly, KPIGrid.svelte finds no matching entry, and the card silently keeps the time comparison with no error anywhere. Validating it against the metrics view the way compare_to is validated a few lines below would catch that while preserving the intended behaviour that an entry for a measure removed from the grid is inert, since a removed measure is still a measure of the metrics view.
There was a problem hiding this comment.
Done in a5fa389. measure is now checked against the metrics view and the ad-hoc measures, with a test for the typo case. A measure removed from the grid is still valid, as you said.
| comparison?: ComponentComparisonOptions[]; | ||
| // Measure to compare against over the primary time range (e.g. a target), | ||
| // instead of the time comparison. Takes precedence over it when set. | ||
| comparison_measure?: string; |
There was a problem hiding this comment.
comparison_measure lands on KPISpec, which is also the shape of the standalone kpi component, and validateKPI does not know the key, so the two component types now validate the same concept differently. Since KPIProvider is only instantiated from KPIGrid, either keep this out of the public kpi spec or mark it as internal.
There was a problem hiding this comment.
Done in a5fa389. I kept it out of the spec: KPIGrid passes the target to KPIProvider as a prop, and KPISpec is the same as on main.
A canvas KPI can only compare a measure with itself, over an earlier time
range. But a budget, a target or a forecast is a second measure, so there is
no way to render "revenue vs target" today, and those cards end up drawn by
hand as Vega custom charts.
`measure_comparisons` says which measure a KPI is compared against:
```yaml
kpi_grid:
metrics_view: sales
measures: [revenue]
measure_comparisons:
- measure: revenue
compare_to: target_revenue
comparison: [previous, percent_change]
```
The comparison query then asks for that measure over the same time range,
instead of the same measure over an earlier range. The result is stored under
the name of the first measure, so the existing rendering path does not change.
Each pair is a list item, because other canvas widgets already store
per-measure settings that way.
Both measures must belong to the same metrics view, and the reconciler checks
`compare_to`. An entry for a measure the grid no longer shows is ignored, and
not an error: removing a measure in the inspector leaves its entry behind, and
breaking the resource for that would leave a state nobody can fix from the UI,
since the inspector does not show this option.
It also works without a time dimension, which the time comparison does not.
For a percentage measure use `delta`, since Rill already hides
`percent_change` there, and `delta` gives the difference in points.
- Gate the target's totals query on supportsTotal, for the KPI's own measure and for the target. A target without a single total shows no comparison. - Send the target through mapEphemeralMeasuresForRequest and splitTimeSeriesMeasures, so compare_to can name an adhoc_measures entry. The backend now accepts that too. - Validate the measure of each measure_comparisons entry against the metrics view and the ad-hoc measures. - Deleting an ad-hoc measure also drops the entries that compare against it, so the grid does not break on a leftover compare_to. - Pass the target to KPIProvider as a prop instead of adding comparison_measure to the public KPISpec.
187a2f4 to
a5fa389
Compare
|
Thanks for the careful review. I rebased on main and put the changes in a second commit (a5fa389), so they are easy to see.
Happy to move the target to the metrics view later if you prefer. |
We wanted to show a measure next to its target in a canvas KPI, and we could not find a way to do it. If there is one and we missed it please let us know, feel free to close this.
What we found: a canvas KPI can only compare a measure with itself, over an earlier time range. But a budget, a target or a forecast is a second measure, so today there is no way to render "revenue vs target". We drew those cards by hand instead, as Vega custom charts, about 35 lines of positioned text per card. This PR is what we use now, in case it is useful to you too.
How it works: the comparison query asks for the other measure over the same time range. It does not ask for the same measure over an earlier range. The result is then stored under the name of the first measure, so
KPI.svelterenders it as before and we did not have to touch it.Things we were not sure about
measure_comparisons. We avoidedcomparison_measuresbecause that name already means something else in the codebase. Rename it if you prefer.delta, notpercent_change. Rill already hidespercent_changefor percentage measures.deltathen gives the difference in points. We wrote this in the docs instead of adding a special case.Some questions. We do not know Rill well enough to answer these ourselves.
Happy to rename things or rework it, and just as happy to keep it on our side if you do not want it.