diff --git a/web-common/src/features/canvas/components/BaseCanvasComponent.ts b/web-common/src/features/canvas/components/BaseCanvasComponent.ts index 47831b787e4..22abf8b412a 100644 --- a/web-common/src/features/canvas/components/BaseCanvasComponent.ts +++ b/web-common/src/features/canvas/components/BaseCanvasComponent.ts @@ -15,6 +15,7 @@ import type { V1TimeRange, } from "@rilldata/web-common/runtime-client"; import type { ComponentType, SvelteComponent } from "svelte"; +import type { Readable } from "svelte/store"; import { derived, get, writable, type Writable } from "svelte/store"; import { mergeFilters } from "../../dashboards/pivot/pivot-merge-filters"; import { @@ -31,9 +32,8 @@ import type { ComponentPath, SearchParamsStore, } from "../stores/canvas-entity"; -import { TimeState } from "../stores/time-state"; import type { FilterState } from "../stores/filter-state"; -import type { Readable } from "svelte/store"; +import { TimeState } from "../stores/time-state"; export abstract class BaseCanvasComponent { id: string; @@ -198,8 +198,7 @@ export abstract class BaseCanvasComponent { ], set, ) => { - const hasTimeSeries = - hasTimeSeriesMap.get(this.metricsViewName) ?? false; + const hasTimeSeries = hasTimeSeriesMap.get(this.metricsViewName); const mvFilters = metricsViewFilters.get(this.metricsViewName); diff --git a/web-common/src/features/canvas/components/pivot/util.ts b/web-common/src/features/canvas/components/pivot/util.ts index 82d29a32593..36928dc5fe8 100644 --- a/web-common/src/features/canvas/components/pivot/util.ts +++ b/web-common/src/features/canvas/components/pivot/util.ts @@ -114,8 +114,14 @@ export function createPivotConfig( const { timeRange, comparisonTimeRange, where } = $timeAndFilterStore; const metricsViewName = $tableSpec.metrics_view; const metricsView = - $canvasData?.data?.metricsViews[metricsViewName]?.state?.validSpec ?? - {}; + $canvasData?.data?.metricsViews[metricsViewName]?.state?.validSpec; + const ready = + !!metricsViewName && + !!metricsView && + ($timeAndFilterStore.hasTimeSeries === false || + ($timeAndFilterStore.hasTimeSeries === true && + !!timeRange?.start && + !!timeRange?.end)); let queryWhere: V1Expression | undefined; if (!$selfFiltered || $selfFiltered.size === 0) { @@ -135,6 +141,7 @@ export function createPivotConfig( comparisonTimeRange, pivotState, timeRange, + ready, ) : processPivot( $tableSpec, @@ -145,6 +152,7 @@ export function createPivotConfig( comparisonTimeRange, pivotState, timeRange, + ready, ); }, ); @@ -159,9 +167,11 @@ export function processPivot( comparisonTimeRange: V1TimeRange | undefined, pivotState: Writable, timeRange: V1TimeRange, + ready: boolean, ) { if (!$tableSpec) { return { + ready: false, measureNames: [], rowDimensionNames: [], colDimensionNames: [], @@ -182,6 +192,7 @@ export function processPivot( $timeAndFilterStore.showTimeComparison; const config: PivotDataStoreConfig = { + ready, measureNames: ($tableSpec?.measures || []).flatMap((name) => { const group = [name]; if (enableComparison) { @@ -239,9 +250,11 @@ export function processFlat( comparisonTimeRange: V1TimeRange | undefined, pivotState: Writable, timeRange: V1TimeRange, + ready: boolean, ) { if (!$tableSpec) { return { + ready: false, measureNames: [], rowDimensionNames: [], colDimensionNames: [], @@ -269,6 +282,7 @@ export function processFlat( $timeAndFilterStore.showTimeComparison; const config: PivotDataStoreConfig = { + ready, measureNames: (measures || []).flatMap((name) => { const group = [name]; if (enableComparison) { diff --git a/web-common/src/features/components/charts/cartesian/CartesianChartProvider.ts b/web-common/src/features/components/charts/cartesian/CartesianChartProvider.ts index e504c6b30fa..f44fa110138 100644 --- a/web-common/src/features/components/charts/cartesian/CartesianChartProvider.ts +++ b/web-common/src/features/components/charts/cartesian/CartesianChartProvider.ts @@ -36,6 +36,7 @@ import { type Writable, } from "svelte/store"; import { + canQueryWithTimeRange, getFilterWithNullHandling, isSortByDelta, vegaSortToAggregationSort, @@ -186,6 +187,7 @@ export class CartesianChartProvider { const dimensionName = config.x?.field; const rawSort = config.x?.sort; const sortIsDelta = isSortByDelta(rawSort); + const requiresTimeRange = config.x?.type === "temporal"; if (config.x?.type === "nominal" && dimensionName) { limit = config.x.limit ?? 100; @@ -218,7 +220,7 @@ export class CartesianChartProvider { } = $timeAndFilterStore; const enabled = $visible && - (!hasTimeSeries || (!!timeRange?.start && !!timeRange?.end)) && + canQueryWithTimeRange(hasTimeSeries, timeRange) && config.x?.type === "nominal" && !Array.isArray(config.x?.sort) && !!dimensionName; @@ -292,7 +294,7 @@ export class CartesianChartProvider { const enabled = $visible && !hasExplicitColorValues && - (!hasTimeSeries || (!!timeRange?.start && !!timeRange?.end)) && + canQueryWithTimeRange(hasTimeSeries, timeRange, requiresTimeRange) && hasColorDimension && !!colorDimensionName && !!colorLimit; @@ -342,7 +344,7 @@ export class CartesianChartProvider { const topNColorData = $topNColorQuery?.data?.data; const enabled = $visible && - (!hasTimeSeries || (!!timeRange?.start && !!timeRange?.end)) && + canQueryWithTimeRange(hasTimeSeries, timeRange, requiresTimeRange) && !!measures?.length && !!dimensions?.length && (hasColorDimension && @@ -448,7 +450,7 @@ export class CartesianChartProvider { comparisonTimeRange?.end ? comparisonTimeRange : undefined, - fillMissing: config.x?.type === "temporal", + fillMissing: requiresTimeRange, limit: hasColorDimension || !limit ? "5000" : limit?.toString(), }, { diff --git a/web-common/src/features/components/charts/circular/CircularChartProvider.ts b/web-common/src/features/components/charts/circular/CircularChartProvider.ts index 631934dbaaf..4a7c588320a 100644 --- a/web-common/src/features/components/charts/circular/CircularChartProvider.ts +++ b/web-common/src/features/components/charts/circular/CircularChartProvider.ts @@ -27,7 +27,10 @@ import { type Readable, type Writable, } from "svelte/store"; -import { getFilterWithNullHandling } from "../query-util"; +import { + canQueryWithTimeRange, + getFilterWithNullHandling, +} from "../query-util"; import { OTHER_VALUE, OTHER_VALUE_DOMAIN_KEY, @@ -106,7 +109,7 @@ export class CircularChartProvider { const { timeRange, where, hasTimeSeries } = $timeAndFilterStore; const enabled = $visible && - (!hasTimeSeries || (!!timeRange?.start && !!timeRange?.end)) && + canQueryWithTimeRange(hasTimeSeries, timeRange) && !!colorDimensionName && config.color?.type === "nominal" && !Array.isArray(config.color?.sort); @@ -146,7 +149,7 @@ export class CircularChartProvider { const { timeRange, where, hasTimeSeries } = $timeAndFilterStore; const enabled = $visible && - (!hasTimeSeries || (!!timeRange?.start && !!timeRange?.end)) && + canQueryWithTimeRange(hasTimeSeries, timeRange) && !!config.measure?.field; const totalWhere = getFilterWithNullHandling(where, config.color); @@ -192,7 +195,7 @@ export class CircularChartProvider { showOther && !!visibleValues && visibleValues.length > 0 && - (!hasTimeSeries || (!!timeRange?.start && !!timeRange?.end)) && + canQueryWithTimeRange(hasTimeSeries, timeRange) && !!config.measure?.field && !!colorDimensionName; @@ -245,7 +248,7 @@ export class CircularChartProvider { const topNColorData = $topNColorQuery?.data?.data; const enabled = $visible && - (!hasTimeSeries || (!!timeRange?.start && !!timeRange?.end)) && + canQueryWithTimeRange(hasTimeSeries, timeRange) && !!measures?.length && (config.color?.type === "nominal" && !Array.isArray(config.color?.sort) diff --git a/web-common/src/features/components/charts/combo/ComboChartProvider.ts b/web-common/src/features/components/charts/combo/ComboChartProvider.ts index 62d1122b73b..c0b8309f0fb 100644 --- a/web-common/src/features/components/charts/combo/ComboChartProvider.ts +++ b/web-common/src/features/components/charts/combo/ComboChartProvider.ts @@ -27,6 +27,7 @@ import { type Writable, } from "svelte/store"; import { + canQueryWithTimeRange, getFilterWithNullHandling, vegaSortToAggregationSort, } from "../query-util"; @@ -101,6 +102,7 @@ export class ComboChartProvider { } const dimensionName = config.x?.field; + const requiresTimeRange = config.x?.type === "temporal"; const xAxisQueryOptionsStore = derived( [timeAndFilterStore, visibleStore], @@ -108,7 +110,7 @@ export class ComboChartProvider { const { timeRange, where, hasTimeSeries } = $timeAndFilterStore; const enabled = $visible && - (!hasTimeSeries || (!!timeRange?.start && !!timeRange?.end)) && + canQueryWithTimeRange(hasTimeSeries, timeRange) && !!dimensionName && config?.x?.type === "nominal" && !Array.isArray(config.x?.sort) && @@ -162,7 +164,7 @@ export class ComboChartProvider { const enabled = $visible && - (!hasTimeSeries || (!!timeRange?.start && !!timeRange?.end)) && + canQueryWithTimeRange(hasTimeSeries, timeRange, requiresTimeRange) && !!measures?.length && (config.x?.type === "nominal" && !Array.isArray(config.x?.sort) ? xTopNData !== undefined @@ -215,7 +217,7 @@ export class ComboChartProvider { dimensions, where: combinedWhere, timeRange, - fillMissing: config.x?.type === "temporal", + fillMissing: requiresTimeRange, sort: config.x?.type === "temporal" ? [{ name: config.x?.field, desc: false }] diff --git a/web-common/src/features/components/charts/data-provider.ts b/web-common/src/features/components/charts/data-provider.ts index 1f7f8dde0c1..af4023e061c 100644 --- a/web-common/src/features/components/charts/data-provider.ts +++ b/web-common/src/features/components/charts/data-provider.ts @@ -115,10 +115,15 @@ export function getChartData( const domainValues = getDomainValues(); const hasComparison = $timeAndFilterStore.showTimeComparison; + const waitingForTimeState = + $timeAndFilterStore.hasTimeSeries === undefined || + ($timeAndFilterStore.hasTimeSeries === true && + (!$timeAndFilterStore.timeRange?.start || + !$timeAndFilterStore.timeRange?.end)); return { data: data || [], - isFetching: chartData?.isFetching ?? false, + isFetching: waitingForTimeState || (chartData?.isFetching ?? false), error: chartData?.error, fields: fieldSpecMap, domainValues, diff --git a/web-common/src/features/components/charts/funnel/FunnelChartProvider.ts b/web-common/src/features/components/charts/funnel/FunnelChartProvider.ts index 8d3ec76139e..2a65f9fca11 100644 --- a/web-common/src/features/components/charts/funnel/FunnelChartProvider.ts +++ b/web-common/src/features/components/charts/funnel/FunnelChartProvider.ts @@ -25,7 +25,10 @@ import { type Readable, type Writable, } from "svelte/store"; -import { getFilterWithNullHandling } from "../query-util"; +import { + canQueryWithTimeRange, + getFilterWithNullHandling, +} from "../query-util"; export type FunnelMode = "width" | "order"; export type FunnelColorMode = "stage" | "measure" | "name" | "value"; @@ -131,7 +134,7 @@ export class FunnelChartProvider { const { timeRange, where, hasTimeSeries } = $timeAndFilterStore; const enabled = $visible && - (!hasTimeSeries || (!!timeRange?.start && !!timeRange?.end)) && + canQueryWithTimeRange(hasTimeSeries, timeRange) && !!stageDimensionName && !isMultiMeasure && !Array.isArray(config.stage?.sort); @@ -167,7 +170,7 @@ export class FunnelChartProvider { const topNStageData = $topNStageQuery?.data?.data; const enabled = $visible && - (!hasTimeSeries || (!!timeRange?.start && !!timeRange?.end)) && + canQueryWithTimeRange(hasTimeSeries, timeRange) && !!measures?.length && (isMultiMeasure || !!dimensions?.length) && (!isMultiMeasure && diff --git a/web-common/src/features/components/charts/heatmap/HeatmapChartProvider.ts b/web-common/src/features/components/charts/heatmap/HeatmapChartProvider.ts index d47fdf32c5a..84280efdca3 100644 --- a/web-common/src/features/components/charts/heatmap/HeatmapChartProvider.ts +++ b/web-common/src/features/components/charts/heatmap/HeatmapChartProvider.ts @@ -26,6 +26,7 @@ import { type Writable, } from "svelte/store"; import { + canQueryWithTimeRange, getFilterWithNullHandling, vegaSortToAggregationSort, } from "../query-util"; @@ -89,7 +90,7 @@ export class HeatmapChartProvider { const { timeRange, where, hasTimeSeries } = $timeAndFilterStore; const enabled = $visible && - (!hasTimeSeries || (!!timeRange?.start && !!timeRange?.end)) && + canQueryWithTimeRange(hasTimeSeries, timeRange) && !!config.x?.field && config?.x?.type !== "temporal" && !Array.isArray(config.x?.sort); @@ -134,7 +135,7 @@ export class HeatmapChartProvider { const { timeRange, where, hasTimeSeries } = $timeAndFilterStore; const enabled = $visible && - (!hasTimeSeries || (!!timeRange?.start && !!timeRange?.end)) && + canQueryWithTimeRange(hasTimeSeries, timeRange) && !!config.y?.field && config?.y?.type !== "temporal" && !Array.isArray(config.y?.sort); @@ -185,7 +186,7 @@ export class HeatmapChartProvider { const enabled = $visible && - (!hasTimeSeries || (!!timeRange?.start && !!timeRange?.end)) && + canQueryWithTimeRange(hasTimeSeries, timeRange) && (config.x?.type === "nominal" && !Array.isArray(config.x?.sort) ? xTopNData !== undefined : true) && diff --git a/web-common/src/features/components/charts/query-util.ts b/web-common/src/features/components/charts/query-util.ts index 95d864ea490..301ca758ecc 100644 --- a/web-common/src/features/components/charts/query-util.ts +++ b/web-common/src/features/components/charts/query-util.ts @@ -13,8 +13,22 @@ import { createInExpression } from "@rilldata/web-common/features/dashboards/sto import type { V1Expression, V1MetricsViewAggregationSort, + V1TimeRange, } from "@rilldata/web-common/runtime-client"; +export function canQueryWithTimeRange( + hasTimeSeries: boolean | undefined, + timeRange: V1TimeRange | undefined, + requiresTimeRange = false, +): boolean { + const hasTimeRange = !!timeRange?.start && !!timeRange?.end; + if (requiresTimeRange) return hasTimeRange; + if (hasTimeSeries === false) return true; + if (hasTimeSeries === true) return hasTimeRange; + // hasTimeSeries === undefined: metrics view spec not yet resolved, block the query. + return false; +} + export function getFilterWithNullHandling( where: V1Expression | undefined, fieldConfig: FieldConfig | undefined, diff --git a/web-common/src/features/components/charts/scatter/ScatterPlotChartProvider.ts b/web-common/src/features/components/charts/scatter/ScatterPlotChartProvider.ts index 3cd9d274875..3e4467cc151 100644 --- a/web-common/src/features/components/charts/scatter/ScatterPlotChartProvider.ts +++ b/web-common/src/features/components/charts/scatter/ScatterPlotChartProvider.ts @@ -24,7 +24,10 @@ import { type Readable, type Writable, } from "svelte/store"; -import { getFilterWithNullHandling } from "../query-util"; +import { + canQueryWithTimeRange, + getFilterWithNullHandling, +} from "../query-util"; export type ScatterPlotChartSpec = { metrics_view: string; @@ -95,14 +98,20 @@ export class ScatterPlotChartProvider { hasColorDimension = true; } + const hasTemporalDimension = + config.x?.type === "temporal" || config.y?.type === "temporal"; + const topNColorQueryOptionsStore = derived( [timeAndFilterStore, visibleStore], ([$timeAndFilterStore, $visible]) => { - const { timeRange, where } = $timeAndFilterStore; + const { timeRange, where, hasTimeSeries } = $timeAndFilterStore; const enabled = $visible && - !!timeRange?.start && - !!timeRange?.end && + canQueryWithTimeRange( + hasTimeSeries, + timeRange, + hasTemporalDimension, + ) && hasColorDimension && !!colorDimensionName && !!colorLimit; @@ -139,12 +148,16 @@ export class ScatterPlotChartProvider { const queryOptionsStore = derived( [timeAndFilterStore, topNColorQuery, visibleStore], ([$timeAndFilterStore, $topNColorQuery, $visible]) => { - const { timeRange, where, timeGrain } = $timeAndFilterStore; + const { timeRange, where, timeGrain, hasTimeSeries } = + $timeAndFilterStore; const topNColorData = $topNColorQuery?.data?.data; const enabled = $visible && - !!timeRange?.start && - !!timeRange?.end && + canQueryWithTimeRange( + hasTimeSeries, + timeRange, + hasTemporalDimension, + ) && !!measures?.length && !!dimensions?.length && (hasColorDimension && colorDimensionName && colorLimit @@ -169,9 +182,6 @@ export class ScatterPlotChartProvider { } let finalDimensions = dimensions; - const hasTemporalDimension = - config.x?.type === "temporal" || config.y?.type === "temporal"; - if (timeGrain && hasTemporalDimension) { finalDimensions = dimensions.map((d) => { if ( diff --git a/web-common/src/features/dashboards/pivot/pivot-data-store.ts b/web-common/src/features/dashboards/pivot/pivot-data-store.ts index 72e349b0538..0bf8ecbf437 100644 --- a/web-common/src/features/dashboards/pivot/pivot-data-store.ts +++ b/web-common/src/features/dashboards/pivot/pivot-data-store.ts @@ -219,6 +219,16 @@ export function createPivotDataStore( const { rowDimensionNames, colDimensionNames, measureNames, isFlat } = config; + if (config.ready === false) { + return configSet({ + isFetching: true, + data: [], + columnDef: [], + assembled: false, + totalColumns: 0, + }); + } + if ( (!rowDimensionNames.length && !measureNames.length) || (colDimensionNames.length && !measureNames.length) diff --git a/web-common/src/features/dashboards/pivot/pivot-queries.ts b/web-common/src/features/dashboards/pivot/pivot-queries.ts index b214ce205c3..ba286f455ae 100644 --- a/web-common/src/features/dashboards/pivot/pivot-queries.ts +++ b/web-common/src/features/dashboards/pivot/pivot-queries.ts @@ -100,7 +100,7 @@ export function createPivotAggregationRowQuery( }, { query: { - enabled, + enabled: enabled && config.ready !== false, placeholderData: keepPreviousData, }, }, diff --git a/web-common/src/features/dashboards/pivot/types.ts b/web-common/src/features/dashboards/pivot/types.ts index 2e8b8528567..32da2f4803d 100644 --- a/web-common/src/features/dashboards/pivot/types.ts +++ b/web-common/src/features/dashboards/pivot/types.ts @@ -95,6 +95,7 @@ export interface PivotQueryError { * This is the config that is passed to the pivot data store methods */ export interface PivotDataStoreConfig { + ready?: boolean; measureNames: string[]; rowDimensionNames: string[]; colDimensionNames: string[];