From 7af4a35cb24c8dd6f07ebef531cf8c93a649d577 Mon Sep 17 00:00:00 2001 From: Parag Jain Date: Fri, 4 Sep 2026 12:19:17 +0530 Subject: [PATCH 1/2] docs: flatten AI resolver `context` in schema and fix deprecated examples The AI resolver has always declared `explore`, `dimensions` and `measures` at the top level of the `ai` block, as decided during review of #8673, but the JSON schema and the docs generated from it still nested them under `context`. Since unknown resolver properties only produce a warning, the documented example silently dropped the explore. Also replaces the `iso_duration`/`iso_offset` examples, which the resolver rejects, with Rill time expressions, and documents the `time_zone` and `where` properties. Co-Authored-By: Claude Fable 5.1 --- docs/docs/reference/project-files/alerts.md | 24 +++---- docs/docs/reference/project-files/models.md | 48 +++++--------- docs/docs/reference/project-files/reports.md | 27 +++----- runtime/parser/schema/project.schema.yaml | 69 ++++++++------------ 4 files changed, 65 insertions(+), 103 deletions(-) diff --git a/docs/docs/reference/project-files/alerts.md b/docs/docs/reference/project-files/alerts.md index 5f95fe9e29ef..29c9dfd7d9ab 100644 --- a/docs/docs/reference/project-files/alerts.md +++ b/docs/docs/reference/project-files/alerts.md @@ -119,37 +119,31 @@ _[oneOf]_ - Data source for the alert _(required)_ - **`prompt`** - _[string]_ - Custom prompt to guide the AI analysis. If not provided, a default analysis prompt is used. - - **`time_range`** - _[object]_ - Time range for the analysis period + - **`time_range`** - _[object]_ - Time range for the analysis period. Use either a Rill time expression or fixed start and end timestamps. - - **`iso_duration`** - _[string]_ - ISO 8601 duration (e.g., P7D for 7 days, P1M for 1 month) - - - **`iso_offset`** - _[string]_ - ISO 8601 offset from current time (e.g., P1D to start from yesterday) + - **`expression`** - _[string]_ - Rill time expression (e.g., '7D as of latest/D', '1M as of latest/M') - **`start`** - _[string]_ - Start timestamp in ISO 8601 format - **`end`** - _[string]_ - End timestamp in ISO 8601 format - - **`expression`** - _[string]_ - Rill time expression (e.g., 'last 7 days', 'this month') - - - **`comparison_time_range`** - _[object]_ - Optional comparison time range for period-over-period analysis - - - **`iso_duration`** - _[string]_ - ISO 8601 duration for comparison period + - **`comparison_time_range`** - _[object]_ - Optional comparison time range for period-over-period analysis. Use either a Rill time expression or fixed start and end timestamps. - - **`iso_offset`** - _[string]_ - ISO 8601 offset for comparison period (e.g., P7D to compare with previous week) + - **`expression`** - _[string]_ - Rill time expression for the comparison period (e.g., '7D as of latest/D offset -7D') - **`start`** - _[string]_ - Start timestamp in ISO 8601 format - **`end`** - _[string]_ - End timestamp in ISO 8601 format - - **`expression`** - _[string]_ - Rill time expression for comparison period + - **`time_zone`** - _[string]_ - IANA time zone used to evaluate the time range expressions (e.g., 'America/New_York'). Defaults to UTC. - - **`context`** - _[object]_ - Context to constrain the AI analysis + - **`explore`** - _[string]_ - Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard, and the time range is resolved against it. - - **`explore`** - _[string]_ - Name of the explore dashboard to analyze + - **`dimensions`** - _[array of string]_ - List of dimensions to include in the analysis - - **`dimensions`** - _[array of string]_ - List of dimensions to include in analysis + - **`measures`** - _[array of string]_ - List of measures to include in the analysis - - **`measures`** - _[array of string]_ - List of measures to include in analysis + - **`where`** - _[object]_ - Optional filter expression to apply to the analysis, in the same format as metrics view query filters ### `for` diff --git a/docs/docs/reference/project-files/models.md b/docs/docs/reference/project-files/models.md index d7f3b73a74e2..5fec121acc01 100644 --- a/docs/docs/reference/project-files/models.md +++ b/docs/docs/reference/project-files/models.md @@ -182,37 +182,31 @@ _[oneOf]_ - Refers to the explicitly defined state of your model, cannot be used - **`prompt`** - _[string]_ - Custom prompt to guide the AI analysis. If not provided, a default analysis prompt is used. - - **`time_range`** - _[object]_ - Time range for the analysis period + - **`time_range`** - _[object]_ - Time range for the analysis period. Use either a Rill time expression or fixed start and end timestamps. - - **`iso_duration`** - _[string]_ - ISO 8601 duration (e.g., P7D for 7 days, P1M for 1 month) - - - **`iso_offset`** - _[string]_ - ISO 8601 offset from current time (e.g., P1D to start from yesterday) + - **`expression`** - _[string]_ - Rill time expression (e.g., '7D as of latest/D', '1M as of latest/M') - **`start`** - _[string]_ - Start timestamp in ISO 8601 format - **`end`** - _[string]_ - End timestamp in ISO 8601 format - - **`expression`** - _[string]_ - Rill time expression (e.g., 'last 7 days', 'this month') - - - **`comparison_time_range`** - _[object]_ - Optional comparison time range for period-over-period analysis - - - **`iso_duration`** - _[string]_ - ISO 8601 duration for comparison period + - **`comparison_time_range`** - _[object]_ - Optional comparison time range for period-over-period analysis. Use either a Rill time expression or fixed start and end timestamps. - - **`iso_offset`** - _[string]_ - ISO 8601 offset for comparison period (e.g., P7D to compare with previous week) + - **`expression`** - _[string]_ - Rill time expression for the comparison period (e.g., '7D as of latest/D offset -7D') - **`start`** - _[string]_ - Start timestamp in ISO 8601 format - **`end`** - _[string]_ - End timestamp in ISO 8601 format - - **`expression`** - _[string]_ - Rill time expression for comparison period + - **`time_zone`** - _[string]_ - IANA time zone used to evaluate the time range expressions (e.g., 'America/New_York'). Defaults to UTC. - - **`context`** - _[object]_ - Context to constrain the AI analysis + - **`explore`** - _[string]_ - Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard, and the time range is resolved against it. - - **`explore`** - _[string]_ - Name of the explore dashboard to analyze + - **`dimensions`** - _[array of string]_ - List of dimensions to include in the analysis - - **`dimensions`** - _[array of string]_ - List of dimensions to include in analysis + - **`measures`** - _[array of string]_ - List of measures to include in the analysis - - **`measures`** - _[array of string]_ - List of measures to include in analysis + - **`where`** - _[object]_ - Optional filter expression to apply to the analysis, in the same format as metrics view query filters ```yaml state: @@ -280,37 +274,31 @@ _[oneOf]_ - Refers to how your data is partitioned; cannot be used with state. ( - **`prompt`** - _[string]_ - Custom prompt to guide the AI analysis. If not provided, a default analysis prompt is used. - - **`time_range`** - _[object]_ - Time range for the analysis period + - **`time_range`** - _[object]_ - Time range for the analysis period. Use either a Rill time expression or fixed start and end timestamps. - - **`iso_duration`** - _[string]_ - ISO 8601 duration (e.g., P7D for 7 days, P1M for 1 month) - - - **`iso_offset`** - _[string]_ - ISO 8601 offset from current time (e.g., P1D to start from yesterday) + - **`expression`** - _[string]_ - Rill time expression (e.g., '7D as of latest/D', '1M as of latest/M') - **`start`** - _[string]_ - Start timestamp in ISO 8601 format - **`end`** - _[string]_ - End timestamp in ISO 8601 format - - **`expression`** - _[string]_ - Rill time expression (e.g., 'last 7 days', 'this month') - - - **`comparison_time_range`** - _[object]_ - Optional comparison time range for period-over-period analysis - - - **`iso_duration`** - _[string]_ - ISO 8601 duration for comparison period + - **`comparison_time_range`** - _[object]_ - Optional comparison time range for period-over-period analysis. Use either a Rill time expression or fixed start and end timestamps. - - **`iso_offset`** - _[string]_ - ISO 8601 offset for comparison period (e.g., P7D to compare with previous week) + - **`expression`** - _[string]_ - Rill time expression for the comparison period (e.g., '7D as of latest/D offset -7D') - **`start`** - _[string]_ - Start timestamp in ISO 8601 format - **`end`** - _[string]_ - End timestamp in ISO 8601 format - - **`expression`** - _[string]_ - Rill time expression for comparison period + - **`time_zone`** - _[string]_ - IANA time zone used to evaluate the time range expressions (e.g., 'America/New_York'). Defaults to UTC. - - **`context`** - _[object]_ - Context to constrain the AI analysis + - **`explore`** - _[string]_ - Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard, and the time range is resolved against it. - - **`explore`** - _[string]_ - Name of the explore dashboard to analyze + - **`dimensions`** - _[array of string]_ - List of dimensions to include in the analysis - - **`dimensions`** - _[array of string]_ - List of dimensions to include in analysis + - **`measures`** - _[array of string]_ - List of measures to include in the analysis - - **`measures`** - _[array of string]_ - List of measures to include in analysis + - **`where`** - _[object]_ - Optional filter expression to apply to the analysis, in the same format as metrics view query filters ```yaml partitions: diff --git a/docs/docs/reference/project-files/reports.md b/docs/docs/reference/project-files/reports.md index 06b5fc2df153..98228573aa7c 100644 --- a/docs/docs/reference/project-files/reports.md +++ b/docs/docs/reference/project-files/reports.md @@ -121,37 +121,31 @@ Supports ai resolvers only as of now. - **`prompt`** - _[string]_ - Custom prompt to guide the AI analysis. If not provided, a default analysis prompt is used. - - **`time_range`** - _[object]_ - Time range for the analysis period + - **`time_range`** - _[object]_ - Time range for the analysis period. Use either a Rill time expression or fixed start and end timestamps. - - **`iso_duration`** - _[string]_ - ISO 8601 duration (e.g., P7D for 7 days, P1M for 1 month) - - - **`iso_offset`** - _[string]_ - ISO 8601 offset from current time (e.g., P1D to start from yesterday) + - **`expression`** - _[string]_ - Rill time expression (e.g., '7D as of latest/D', '1M as of latest/M') - **`start`** - _[string]_ - Start timestamp in ISO 8601 format - **`end`** - _[string]_ - End timestamp in ISO 8601 format - - **`expression`** - _[string]_ - Rill time expression (e.g., 'last 7 days', 'this month') - - - **`comparison_time_range`** - _[object]_ - Optional comparison time range for period-over-period analysis - - - **`iso_duration`** - _[string]_ - ISO 8601 duration for comparison period + - **`comparison_time_range`** - _[object]_ - Optional comparison time range for period-over-period analysis. Use either a Rill time expression or fixed start and end timestamps. - - **`iso_offset`** - _[string]_ - ISO 8601 offset for comparison period (e.g., P7D to compare with previous week) + - **`expression`** - _[string]_ - Rill time expression for the comparison period (e.g., '7D as of latest/D offset -7D') - **`start`** - _[string]_ - Start timestamp in ISO 8601 format - **`end`** - _[string]_ - End timestamp in ISO 8601 format - - **`expression`** - _[string]_ - Rill time expression for comparison period + - **`time_zone`** - _[string]_ - IANA time zone used to evaluate the time range expressions (e.g., 'America/New_York'). Defaults to UTC. - - **`context`** - _[object]_ - Context to constrain the AI analysis + - **`explore`** - _[string]_ - Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard, and the time range is resolved against it. - - **`explore`** - _[string]_ - Name of the explore dashboard to analyze + - **`dimensions`** - _[array of string]_ - List of dimensions to include in the analysis - - **`dimensions`** - _[array of string]_ - List of dimensions to include in analysis + - **`measures`** - _[array of string]_ - List of measures to include in the analysis - - **`measures`** - _[array of string]_ - List of measures to include in analysis + - **`where`** - _[object]_ - Optional filter expression to apply to the analysis, in the same format as metrics view query filters ### `query` @@ -257,8 +251,7 @@ data: expression: "1D as of latest/D" comparison_time_range: expression: "1D as of latest/D offset -1D" - context: - explore: my_explore + explore: my_explore notify: email: recipients: diff --git a/runtime/parser/schema/project.schema.yaml b/runtime/parser/schema/project.schema.yaml index eaac919df70e..cce5ecba331d 100644 --- a/runtime/parser/schema/project.schema.yaml +++ b/runtime/parser/schema/project.schema.yaml @@ -2881,8 +2881,7 @@ definitions: expression: "1D as of latest/D" comparison_time_range: expression: "1D as of latest/D offset -1D" - context: - explore: my_explore + explore: my_explore notify: email: recipients: @@ -3764,71 +3763,59 @@ definitions: description: Custom prompt to guide the AI analysis. If not provided, a default analysis prompt is used. time_range: type: object - description: Time range for the analysis period + description: Time range for the analysis period. Use either a Rill time expression or fixed start and end timestamps. properties: - iso_duration: - type: string - description: ISO 8601 duration (e.g., P7D for 7 days, P1M for 1 month) - iso_offset: + expression: type: string - description: ISO 8601 offset from current time (e.g., P1D to start from yesterday) + description: Rill time expression (e.g., '7D as of latest/D', '1M as of latest/M') start: type: string description: Start timestamp in ISO 8601 format end: type: string description: End timestamp in ISO 8601 format - expression: - type: string - description: Rill time expression (e.g., 'last 7 days', 'this month') comparison_time_range: type: object - description: Optional comparison time range for period-over-period analysis + description: Optional comparison time range for period-over-period analysis. Use either a Rill time expression or fixed start and end timestamps. properties: - iso_duration: - type: string - description: ISO 8601 duration for comparison period - iso_offset: + expression: type: string - description: ISO 8601 offset for comparison period (e.g., P7D to compare with previous week) + description: Rill time expression for the comparison period (e.g., '7D as of latest/D offset -7D') start: type: string description: Start timestamp in ISO 8601 format end: type: string description: End timestamp in ISO 8601 format - expression: - type: string - description: Rill time expression for comparison period - context: + time_zone: + type: string + description: IANA time zone used to evaluate the time range expressions (e.g., 'America/New_York'). Defaults to UTC. + explore: + type: string + description: Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard, and the time range is resolved against it. + dimensions: + type: array + items: + type: string + description: List of dimensions to include in the analysis + measures: + type: array + items: + type: string + description: List of measures to include in the analysis + where: type: object - description: Context to constrain the AI analysis - properties: - explore: - type: string - description: Name of the explore dashboard to analyze - dimensions: - type: array - items: - type: string - description: List of dimensions to include in analysis - measures: - type: array - items: - type: string - description: List of measures to include in analysis + description: Optional filter expression to apply to the analysis, in the same format as metrics view query filters required: - ai examples: - ai: prompt: "Analyze revenue trends and identify top performing regions" time_range: - iso_duration: P7D + expression: "7D as of latest/D" comparison_time_range: - iso_duration: P7D - iso_offset: P7D - context: - explore: sales_explore + expression: "7D as of latest/D offset -7D" + explore: sales_explore explore_time_range_properties: oneOf: From 2b18a63dd5df33338c949269fe1ca5c2aa135326 Mon Sep 17 00:00:00 2001 From: Parag Jain Date: Fri, 4 Sep 2026 12:24:11 +0530 Subject: [PATCH 2/2] feat: resolve AI report time ranges against latest data with `watermark: inherit` Reports already support `watermark: inherit`, which uses the lowest watermark of the report's refs as the execution time so that `latest` in time range expressions means the latest data rather than the trigger time. AI reports could not use it because they had no refs, so the reconciler silently fell back to the trigger time. - Add the `explore` of an AI report as a ref. This also makes the report wait for the explore to reconcile before running. - In the report reconciler, resolve explore refs to their metrics view before querying the watermark. This keeps the `ResourceWatermark` query keyed on the metrics view, whose cache key tracks the underlying data, rather than on the explore, which does not change when data refreshes. - Document the behavior on the `explore` property of the AI resolver. Co-Authored-By: Claude Fable 5.1 --- docs/docs/reference/project-files/alerts.md | 2 +- docs/docs/reference/project-files/models.md | 4 +- docs/docs/reference/project-files/reports.md | 2 +- runtime/parser/parse_partial_data.go | 5 ++ runtime/parser/parser_test.go | 88 ++++++++++++++++++++ runtime/parser/schema/project.schema.yaml | 2 +- runtime/reconcilers/report.go | 14 ++++ runtime/reconcilers/report_test.go | 79 ++++++++++++++++++ 8 files changed, 191 insertions(+), 5 deletions(-) diff --git a/docs/docs/reference/project-files/alerts.md b/docs/docs/reference/project-files/alerts.md index 29c9dfd7d9ab..87f7febb21b6 100644 --- a/docs/docs/reference/project-files/alerts.md +++ b/docs/docs/reference/project-files/alerts.md @@ -137,7 +137,7 @@ _[oneOf]_ - Data source for the alert _(required)_ - **`time_zone`** - _[string]_ - IANA time zone used to evaluate the time range expressions (e.g., 'America/New_York'). Defaults to UTC. - - **`explore`** - _[string]_ - Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard, and the time range is resolved against it. + - **`explore`** - _[string]_ - Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard. Combined with `watermark: inherit` on the report, time range expressions are resolved against the latest data in the metrics view instead of the report's trigger time. - **`dimensions`** - _[array of string]_ - List of dimensions to include in the analysis diff --git a/docs/docs/reference/project-files/models.md b/docs/docs/reference/project-files/models.md index 5fec121acc01..3e3358aec2d5 100644 --- a/docs/docs/reference/project-files/models.md +++ b/docs/docs/reference/project-files/models.md @@ -200,7 +200,7 @@ _[oneOf]_ - Refers to the explicitly defined state of your model, cannot be used - **`time_zone`** - _[string]_ - IANA time zone used to evaluate the time range expressions (e.g., 'America/New_York'). Defaults to UTC. - - **`explore`** - _[string]_ - Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard, and the time range is resolved against it. + - **`explore`** - _[string]_ - Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard. Combined with `watermark: inherit` on the report, time range expressions are resolved against the latest data in the metrics view instead of the report's trigger time. - **`dimensions`** - _[array of string]_ - List of dimensions to include in the analysis @@ -292,7 +292,7 @@ _[oneOf]_ - Refers to how your data is partitioned; cannot be used with state. ( - **`time_zone`** - _[string]_ - IANA time zone used to evaluate the time range expressions (e.g., 'America/New_York'). Defaults to UTC. - - **`explore`** - _[string]_ - Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard, and the time range is resolved against it. + - **`explore`** - _[string]_ - Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard. Combined with `watermark: inherit` on the report, time range expressions are resolved against the latest data in the metrics view instead of the report's trigger time. - **`dimensions`** - _[array of string]_ - List of dimensions to include in the analysis diff --git a/docs/docs/reference/project-files/reports.md b/docs/docs/reference/project-files/reports.md index 98228573aa7c..4359e3deb329 100644 --- a/docs/docs/reference/project-files/reports.md +++ b/docs/docs/reference/project-files/reports.md @@ -139,7 +139,7 @@ Supports ai resolvers only as of now. - **`time_zone`** - _[string]_ - IANA time zone used to evaluate the time range expressions (e.g., 'America/New_York'). Defaults to UTC. - - **`explore`** - _[string]_ - Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard, and the time range is resolved against it. + - **`explore`** - _[string]_ - Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard. Combined with `watermark: inherit` on the report, time range expressions are resolved against the latest data in the metrics view instead of the report's trigger time. - **`dimensions`** - _[array of string]_ - List of dimensions to include in the analysis diff --git a/runtime/parser/parse_partial_data.go b/runtime/parser/parse_partial_data.go index 484ec1e3a8ba..b0c6f5afc196 100644 --- a/runtime/parser/parse_partial_data.go +++ b/runtime/parser/parse_partial_data.go @@ -116,6 +116,11 @@ func (p *Parser) parseDataYAML(paths []string, raw *DataYAML, contextualConnecto count++ resolver = "ai" resolverProps = raw.AI + // The agent can access any metrics view, so the explore is the only dependency known up front. + // Adding it as a ref makes reports wait for it to reconcile and lets watermark: inherit resolve time ranges against its data. + if explore, ok := raw.AI["explore"].(string); ok && explore != "" { + refs = append(refs, ResourceName{Kind: ResourceKindExplore, Name: explore}) + } } // Handle union resolver diff --git a/runtime/parser/parser_test.go b/runtime/parser/parser_test.go index d8b28163a898..01154d26fca2 100644 --- a/runtime/parser/parser_test.go +++ b/runtime/parser/parser_test.go @@ -1524,6 +1524,94 @@ annotations: requireResourcesAndErrors(t, p, resources, nil) } +func TestReportAIExploreRef(t *testing.T) { + ctx := context.Background() + repo := makeRepo(t, map[string]string{ + `rill.yaml`: ``, + `reports/r1.yaml`: ` +type: report +display_name: AI Report + +refresh: + cron: 0 8 * * * + +watermark: inherit + +data: + ai: + prompt: Analyze key metrics + time_range: + expression: 1D as of latest/D + explore: e1 + +notify: + email: + recipients: + - user_1@example.com +`, + // Without an explore, the report has no refs. + `reports/r2.yaml`: ` +type: report +display_name: AI Report + +refresh: + cron: 0 8 * * * + +data: + ai: + prompt: Analyze key metrics + +notify: + email: + recipients: + - user_1@example.com +`, + }) + + resources := []*Resource{ + { + Name: ResourceName{Kind: ResourceKindReport, Name: "r1"}, + Paths: []string{"/reports/r1.yaml"}, + Refs: []ResourceName{{Kind: ResourceKindExplore, Name: "e1"}}, + ReportSpec: &runtimev1.ReportSpec{ + DisplayName: "AI Report", + RefreshSchedule: &runtimev1.Schedule{Cron: "0 8 * * *"}, + Resolver: "ai", + ResolverProperties: must(structpb.NewStruct(map[string]any{ + "prompt": "Analyze key metrics", + "time_range": map[string]any{"expression": "1D as of latest/D"}, + "explore": "e1", + })), + Notifiers: []*runtimev1.Notifier{{ + Connector: "email", + Properties: must(structpb.NewStruct(map[string]any{"recipients": []any{"user_1@example.com"}})), + }}, + WatermarkInherit: true, + }, + }, + { + Name: ResourceName{Kind: ResourceKindReport, Name: "r2"}, + Paths: []string{"/reports/r2.yaml"}, + ReportSpec: &runtimev1.ReportSpec{ + DisplayName: "AI Report", + RefreshSchedule: &runtimev1.Schedule{Cron: "0 8 * * *"}, + Resolver: "ai", + ResolverProperties: must(structpb.NewStruct(map[string]any{ + "prompt": "Analyze key metrics", + })), + Notifiers: []*runtimev1.Notifier{{ + Connector: "email", + Properties: must(structpb.NewStruct(map[string]any{"recipients": []any{"user_1@example.com"}})), + }}, + }, + }, + } + + p, err := Parse(ctx, repo, "", "", "duckdb", true) + require.NoError(t, err) + requireResourcesAndErrors(t, p, resources, nil) +} + func TestReportPdfValidation(t *testing.T) { ctx := context.Background() repo := makeRepo(t, map[string]string{ diff --git a/runtime/parser/schema/project.schema.yaml b/runtime/parser/schema/project.schema.yaml index cce5ecba331d..43a970965520 100644 --- a/runtime/parser/schema/project.schema.yaml +++ b/runtime/parser/schema/project.schema.yaml @@ -3792,7 +3792,7 @@ definitions: description: IANA time zone used to evaluate the time range expressions (e.g., 'America/New_York'). Defaults to UTC. explore: type: string - description: Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard, and the time range is resolved against it. + description: "Name of the explore dashboard to analyze. If provided, the analysis is limited to the metrics view of this dashboard. Combined with `watermark: inherit` on the report, time range expressions are resolved against the latest data in the metrics view instead of the report's trigger time." dimensions: type: array items: diff --git a/runtime/reconcilers/report.go b/runtime/reconcilers/report.go index 59e896685993..9fbaa3bf67d3 100644 --- a/runtime/reconcilers/report.go +++ b/runtime/reconcilers/report.go @@ -887,6 +887,20 @@ func formatExportFormat(f runtimev1.ExportFormat) string { func (r *ReportReconciler) computeInheritedWatermark(ctx context.Context, refs []*runtimev1.ResourceName) (time.Time, bool, error) { var t time.Time for _, ref := range refs { + // Explores (referenced by AI reports) inherit the watermark of their metrics view. + // Resolving the metrics view here instead of in the watermark query keeps the query's cache keyed on the metrics view's data. + if ref.Kind == runtime.ResourceKindExplore { + res, err := r.C.Get(ctx, ref, false) + if err != nil { + return t, false, fmt.Errorf("failed to get explore %q: %w", ref.Name, err) + } + spec := res.GetExplore().State.ValidSpec + if spec == nil { + return t, false, fmt.Errorf("explore %q is not valid", ref.Name) + } + ref = &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: spec.MetricsView} + } + q := &queries.ResourceWatermark{ ResourceKind: ref.Kind, ResourceName: ref.Name, diff --git a/runtime/reconcilers/report_test.go b/runtime/reconcilers/report_test.go index 876e50778adb..3ddb1f57c930 100644 --- a/runtime/reconcilers/report_test.go +++ b/runtime/reconcilers/report_test.go @@ -2,6 +2,7 @@ package reconcilers_test import ( "testing" + "time" runtimev1 "github.com/rilldata/rill/proto/gen/rill/runtime/v1" "github.com/rilldata/rill/runtime" @@ -9,6 +10,84 @@ import ( "github.com/stretchr/testify/require" ) +func TestReportAIExploreWatermarkInherit(t *testing.T) { + rt, id := testruntime.NewInstance(t) + testruntime.PutFiles(t, rt, id, map[string]string{ + "/models/bar.sql": ` +SELECT '2024-01-01T00:00:00Z'::TIMESTAMP as __time, 'Denmark' as country +`, + "/metrics/mv1.yaml": ` +version: 1 +type: metrics_view +model: bar +timeseries: __time +dimensions: +- column: country +measures: +- expression: count(*) +`, + "/explores/e1.yaml": ` +type: explore +metrics_view: mv1 +`, + // AI report that inherits its watermark through the explore + "/reports/r1.yaml": ` +type: report +display_name: AI Report +refresh: + cron: 0 8 * * * +watermark: inherit +data: + ai: + prompt: Analyze key metrics + explore: e1 +notify: + email: + recipients: + - somebody@example.com +`, + // Same report without watermark: inherit, which should use the trigger time + "/reports/r2.yaml": ` +type: report +display_name: AI Report +refresh: + cron: 0 8 * * * +data: + ai: + prompt: Analyze key metrics + explore: e1 +notify: + email: + recipients: + - somebody@example.com +`, + }) + testruntime.ReconcileParserAndWait(t, rt, id) + testruntime.RequireReconcileState(t, rt, id, 6, 0, 0) + + r1 := testruntime.GetResource(t, rt, id, runtime.ResourceKindReport, "r1") + require.Len(t, r1.Meta.Refs, 1) + require.Equal(t, runtime.ResourceKindExplore, r1.Meta.Refs[0].Kind) + require.Equal(t, "e1", r1.Meta.Refs[0].Name) + + // Trigger both reports. + // The execution's report time is recorded before the report is sent, so it is available regardless of whether the AI session could be created. + triggerTime := time.Now() + testruntime.RefreshAndWait(t, rt, id, &runtimev1.ResourceName{Kind: runtime.ResourceKindReport, Name: "r1"}) + testruntime.RefreshAndWait(t, rt, id, &runtimev1.ResourceName{Kind: runtime.ResourceKindReport, Name: "r2"}) + + // r1 should have resolved the watermark of the explore's metrics view. + // The watermark is max(__time) plus one second, since it's used as an exclusive upper bound (see ResourceWatermark). + r1 = testruntime.GetResource(t, rt, id, runtime.ResourceKindReport, "r1") + require.Len(t, r1.GetReport().State.ExecutionHistory, 1) + require.Equal(t, time.Date(2024, 1, 1, 0, 0, 1, 0, time.UTC), r1.GetReport().State.ExecutionHistory[0].ReportTime.AsTime()) + + // r2 should have used the trigger time + r2 := testruntime.GetResource(t, rt, id, runtime.ResourceKindReport, "r2") + require.Len(t, r2.GetReport().State.ExecutionHistory, 1) + require.WithinDuration(t, triggerTime, r2.GetReport().State.ExecutionHistory[0].ReportTime.AsTime(), time.Minute) +} + func TestReportCanvasResolveTransitiveAccess(t *testing.T) { rt, id := testruntime.NewInstanceWithOptions(t, testruntime.InstanceOptions{ Files: map[string]string{"rill.yaml": ""},