diff --git a/web-common/src/features/dashboards/workspace/DashboardCTAs.svelte b/web-common/src/features/dashboards/workspace/DashboardCTAs.svelte
index 0b1724b7efb..165b394b37d 100644
--- a/web-common/src/features/dashboards/workspace/DashboardCTAs.svelte
+++ b/web-common/src/features/dashboards/workspace/DashboardCTAs.svelte
@@ -2,18 +2,18 @@
import MetricsIcon from "@rilldata/web-common/components/icons/Metrics.svelte";
import LocalAvatarButton from "@rilldata/web-common/features/authentication/LocalAvatarButton.svelte";
import GlobalDimensionSearch from "@rilldata/web-common/features/dashboards/dimension-search/GlobalDimensionSearch.svelte";
+ import { useExplore } from "@rilldata/web-common/features/explores/selectors";
import { Button } from "../../../components/button";
import { runtime } from "../../../runtime-client/runtime-store";
import { featureFlags } from "../../feature-flags";
import ViewAsButton from "../granular-access-policies/ViewAsButton.svelte";
import { useDashboardPolicyCheck } from "../granular-access-policies/useDashboardPolicyCheck";
- import { useMetricsView } from "../selectors";
import DeployDashboardCta from "./DeployDashboardCTA.svelte";
- export let metricsViewName: string;
+ export let exploreName: string;
- $: metricsViewQuery = useMetricsView($runtime.instanceId, metricsViewName);
- $: filePath = $metricsViewQuery.data?.meta?.filePaths?.[0] ?? "";
+ $: exploreQuery = useExplore($runtime.instanceId, exploreName);
+ $: filePath = $exploreQuery.data?.explore?.meta?.filePaths?.[0] ?? "";
$: dashboardPolicyCheck = useDashboardPolicyCheck(
$runtime.instanceId,
diff --git a/web-common/src/features/file-explorer/new-files.ts b/web-common/src/features/file-explorer/new-files.ts
index f7e47b4ac42..5b421bac30f 100644
--- a/web-common/src/features/file-explorer/new-files.ts
+++ b/web-common/src/features/file-explorer/new-files.ts
@@ -126,7 +126,7 @@ export function getBaseNameForNewResourceFile(
? `${baseResource.meta!.name!.name}_explore`
: ResourceKindMap[newKind].baseName;
default:
- return ResourceKindMap[newKind].baseFileName;
+ return ResourceKindMap[newKind].baseName;
}
}
diff --git a/web-local/src/routes/(viz)/+layout.svelte b/web-local/src/routes/(viz)/+layout.svelte
index a170020c5e4..578f755c91d 100644
--- a/web-local/src/routes/(viz)/+layout.svelte
+++ b/web-local/src/routes/(viz)/+layout.svelte
@@ -66,7 +66,7 @@
{#if route.id?.includes("explore") && metricsViewName}
-
+
{/if}
diff --git a/web-local/tests/dashboards/dashboard-flow-template.ts b/web-local/tests/explores/dashboard-flow-template.ts
similarity index 85%
rename from web-local/tests/dashboards/dashboard-flow-template.ts
rename to web-local/tests/explores/dashboard-flow-template.ts
index 85a4d88bbe4..f1c4b29e00f 100644
--- a/web-local/tests/dashboards/dashboard-flow-template.ts
+++ b/web-local/tests/explores/dashboard-flow-template.ts
@@ -1,5 +1,5 @@
import { expect, test } from "@playwright/test";
-import { useDashboardFlowTestSetup } from "web-local/tests/dashboards/dashboard-flow-test-setup";
+import { useDashboardFlowTestSetup } from "web-local/tests/explores/dashboard-flow-test-setup";
import { startRuntimeForEachTest } from "../utils/startRuntimeForEachTest";
test.describe("~~~~~~~~~~~~~~~~~~~~FIXME RENAME THIS~~~~~~~~~~~~~~~~~~~~~~~", () => {
diff --git a/web-local/tests/dashboards/dashboard-flow-test-setup.ts b/web-local/tests/explores/dashboard-flow-test-setup.ts
similarity index 75%
rename from web-local/tests/dashboards/dashboard-flow-test-setup.ts
rename to web-local/tests/explores/dashboard-flow-test-setup.ts
index d657bde1ab7..4e8808010f7 100644
--- a/web-local/tests/dashboards/dashboard-flow-test-setup.ts
+++ b/web-local/tests/explores/dashboard-flow-test-setup.ts
@@ -1,4 +1,4 @@
-import { createDashboardFromModel } from "web-local/tests/utils/dashboardHelpers";
+import { createMetricsViewFromModel } from "web-local/tests/utils/metricsViewHelpers";
import { createAdBidsModel } from "web-local/tests/utils/dataSpecifcHelpers";
import { test } from "../utils/test";
import { waitForFileNavEntry } from "../utils/waitHelpers";
@@ -14,7 +14,7 @@ export function useDashboardFlowTestSetup() {
`/dashboards/AdBids_model_dashboard.yaml`,
true,
),
- createDashboardFromModel(page, "/models/AdBids_model.sql"),
+ createMetricsViewFromModel(page, "/models/AdBids_model.sql"),
]);
});
}
diff --git a/web-local/tests/dashboards/dimension-and-measure-selection.spec.ts b/web-local/tests/explores/dimension-and-measure-selection.spec.ts
similarity index 95%
rename from web-local/tests/dashboards/dimension-and-measure-selection.spec.ts
rename to web-local/tests/explores/dimension-and-measure-selection.spec.ts
index 6359d29cfb6..1a24c299b34 100644
--- a/web-local/tests/dashboards/dimension-and-measure-selection.spec.ts
+++ b/web-local/tests/explores/dimension-and-measure-selection.spec.ts
@@ -1,5 +1,5 @@
import { expect } from "@playwright/test";
-import { useDashboardFlowTestSetup } from "web-local/tests/dashboards/dashboard-flow-test-setup";
+import { useDashboardFlowTestSetup } from "web-local/tests/explores/dashboard-flow-test-setup";
import { test } from "../utils/test";
import { clickMenuButton } from "../utils/commonHelpers";
diff --git a/web-local/tests/dashboards/dashboards.spec.ts b/web-local/tests/explores/explores.spec.ts
similarity index 94%
rename from web-local/tests/dashboards/dashboards.spec.ts
rename to web-local/tests/explores/explores.spec.ts
index 2b90918100c..287259e80eb 100644
--- a/web-local/tests/dashboards/dashboards.spec.ts
+++ b/web-local/tests/explores/explores.spec.ts
@@ -1,12 +1,16 @@
import { expect } from "@playwright/test";
+import {
+ createExploreFromModel,
+ createExploreFromSource,
+} from "web-local/tests/utils/exploreHelpers";
import { ResourceWatcher } from "web-local/tests/utils/ResourceWatcher";
import { updateCodeEditor, wrapRetryAssertion } from "../utils/commonHelpers";
import {
assertLeaderboards,
- createDashboardFromModel,
- createDashboardFromSource,
+ createMetricsViewFromModel,
+ createMetricsViewFromSource,
interactWithTimeRangeMenu,
-} from "../utils/dashboardHelpers";
+} from "web-local/tests/utils/metricsViewHelpers";
import {
assertAdBidsDashboard,
createAdBidsModel,
@@ -15,26 +19,38 @@ import { createSource } from "../utils/sourceHelpers";
import { test } from "../utils/test";
import { waitForFileNavEntry } from "../utils/waitHelpers";
-test.describe("dashboard", () => {
- test("Autogenerate dashboard from source", async ({ page }) => {
+test.describe("explores", () => {
+ test("Autogenerate explore from source", async ({ page }) => {
await createSource(page, "AdBids.csv", "/sources/AdBids.yaml");
- await createDashboardFromSource(page, "/sources/AdBids.yaml");
- await waitForFileNavEntry(page, `/dashboards/AdBids_dashboard.yaml`, true);
+ await createExploreFromSource(
+ page,
+ "/sources/AdBids.yaml",
+ "/metrics/AdBids_metrics.yaml",
+ );
+ await waitForFileNavEntry(
+ page,
+ "/explore-dashboards/AdBids_metrics_explore.yaml",
+ true,
+ );
await page.getByRole("button", { name: "Preview" }).click();
// Temporary timeout while the issue is looked into
await page.waitForTimeout(1000);
await assertAdBidsDashboard(page);
});
- test("Autogenerate dashboard from model", async ({ page }) => {
+ test("Autogenerate explore from model", async ({ page }) => {
await createAdBidsModel(page);
await Promise.all([
waitForFileNavEntry(
page,
- `/dashboards/AdBids_model_dashboard.yaml`,
+ "/explore-dashboards/AdBids_model_metrics_explore.yaml",
true,
),
- createDashboardFromModel(page, "/models/AdBids_model.sql"),
+ createExploreFromModel(
+ page,
+ "/models/AdBids_model.sql",
+ "/metrics/AdBids_model_metrics.yaml",
+ ),
]);
await page.getByRole("button", { name: "Preview" }).click();
@@ -69,7 +85,7 @@ test.describe("dashboard", () => {
const watcher = new ResourceWatcher(page);
await createAdBidsModel(page);
- await createDashboardFromModel(page, "/models/AdBids_model.sql");
+ await createMetricsViewFromModel(page, "/models/AdBids_model.sql");
await page.getByRole("button", { name: "Preview" }).click();
// Check the total records are 100k
diff --git a/web-local/tests/dashboards/leaderboard-and-dim-table-sort.spec.ts b/web-local/tests/explores/leaderboard-and-dim-table-sort.spec.ts
similarity index 98%
rename from web-local/tests/dashboards/leaderboard-and-dim-table-sort.spec.ts
rename to web-local/tests/explores/leaderboard-and-dim-table-sort.spec.ts
index b834f3899c0..4434163130a 100644
--- a/web-local/tests/dashboards/leaderboard-and-dim-table-sort.spec.ts
+++ b/web-local/tests/explores/leaderboard-and-dim-table-sort.spec.ts
@@ -1,5 +1,5 @@
import { expect, type Locator } from "@playwright/test";
-import { useDashboardFlowTestSetup } from "web-local/tests/dashboards/dashboard-flow-test-setup";
+import { useDashboardFlowTestSetup } from "web-local/tests/explores/dashboard-flow-test-setup";
import { test } from "../utils/test";
async function assertAAboveB(locA: Locator, locB: Locator) {
diff --git a/web-local/tests/dashboards/leaderboard-context-column.spec.ts b/web-local/tests/explores/leaderboard-context-column.spec.ts
similarity index 98%
rename from web-local/tests/dashboards/leaderboard-context-column.spec.ts
rename to web-local/tests/explores/leaderboard-context-column.spec.ts
index af3aeedfe9b..c83bc7fad33 100644
--- a/web-local/tests/dashboards/leaderboard-context-column.spec.ts
+++ b/web-local/tests/explores/leaderboard-context-column.spec.ts
@@ -1,7 +1,7 @@
import { expect } from "@playwright/test";
import { ResourceWatcher } from "web-local/tests/utils/ResourceWatcher";
import { clickMenuButton } from "../utils/commonHelpers";
-import { interactWithTimeRangeMenu } from "../utils/dashboardHelpers";
+import { interactWithTimeRangeMenu } from "web-local/tests/utils/metricsViewHelpers";
import { test } from "../utils/test";
import { useDashboardFlowTestSetup } from "./dashboard-flow-test-setup";
diff --git a/web-local/tests/dashboards/metrics-editor.spec.ts b/web-local/tests/explores/metrics-editor.spec.ts
similarity index 100%
rename from web-local/tests/dashboards/metrics-editor.spec.ts
rename to web-local/tests/explores/metrics-editor.spec.ts
diff --git a/web-local/tests/dashboards/number-formatting.spec.ts b/web-local/tests/explores/number-formatting.spec.ts
similarity index 97%
rename from web-local/tests/dashboards/number-formatting.spec.ts
rename to web-local/tests/explores/number-formatting.spec.ts
index 9151411cc19..0aea56454dc 100644
--- a/web-local/tests/dashboards/number-formatting.spec.ts
+++ b/web-local/tests/explores/number-formatting.spec.ts
@@ -1,6 +1,6 @@
-import { useDashboardFlowTestSetup } from "web-local/tests/dashboards/dashboard-flow-test-setup";
+import { useDashboardFlowTestSetup } from "web-local/tests/explores/dashboard-flow-test-setup";
import { ResourceWatcher } from "web-local/tests/utils/ResourceWatcher";
-import { interactWithTimeRangeMenu } from "../utils/dashboardHelpers";
+import { interactWithTimeRangeMenu } from "web-local/tests/utils/metricsViewHelpers";
import { expect } from "@playwright/test";
import { test } from "../utils/test";
diff --git a/web-local/tests/dashboards/time-controls-from-config.spec.ts b/web-local/tests/explores/time-controls-from-config.spec.ts
similarity index 98%
rename from web-local/tests/dashboards/time-controls-from-config.spec.ts
rename to web-local/tests/explores/time-controls-from-config.spec.ts
index fa90519143a..45c8f75ab9f 100644
--- a/web-local/tests/dashboards/time-controls-from-config.spec.ts
+++ b/web-local/tests/explores/time-controls-from-config.spec.ts
@@ -1,6 +1,6 @@
import { expect } from "@playwright/test";
-import { useDashboardFlowTestSetup } from "web-local/tests/dashboards/dashboard-flow-test-setup";
-import { interactWithTimeRangeMenu } from "web-local/tests/utils/dashboardHelpers";
+import { useDashboardFlowTestSetup } from "web-local/tests/explores/dashboard-flow-test-setup";
+import { interactWithTimeRangeMenu } from "web-local/tests/utils/metricsViewHelpers";
import { ResourceWatcher } from "web-local/tests/utils/ResourceWatcher";
import { test } from "../utils/test";
diff --git a/web-local/tests/utils/dashboardHelpers.ts b/web-local/tests/utils/dashboardHelpers.ts
deleted file mode 100644
index 8d27c6bf555..00000000000
--- a/web-local/tests/utils/dashboardHelpers.ts
+++ /dev/null
@@ -1,195 +0,0 @@
-import { expect } from "@playwright/test";
-import type { V1Expression } from "@rilldata/web-common/runtime-client";
-import type { Page, Response } from "playwright";
-import {
- clickMenuButton,
- openFileNavEntryContextMenu,
- updateCodeEditor,
- waitForValidResource,
-} from "./commonHelpers";
-
-export async function createDashboardFromSource(
- page: Page,
- sourcePath: string,
-) {
- await openFileNavEntryContextMenu(page, sourcePath);
- await clickMenuButton(page, "Generate dashboard");
-}
-
-export async function createDashboardFromModel(page: Page, modelPath: string) {
- await openFileNavEntryContextMenu(page, modelPath);
- await clickMenuButton(page, "Generate dashboard");
-}
-
-export async function assertLeaderboards(
- page: Page,
- leaderboards: Array<{
- label: string;
- values: Array;
- }>,
-) {
- for (const { label, values } of leaderboards) {
- const leaderboardBlock = page.getByRole("table", {
- name: `${label} leaderboard`,
- });
- await expect(leaderboardBlock).toBeVisible();
-
- const actualValues = await leaderboardBlock
- .locator("tr > td:nth-child(2)")
- .allInnerTexts();
- expect(actualValues).toEqual(values);
- }
-}
-
-export type RequestMatcher = (response: Response) => boolean;
-
-/**
- * Waits for a time series query to end.
- * Optionally takes a filter matcher: {@link metricsViewRequestFilterMatcher}.
- */
-export async function waitForTimeSeries(
- page: Page,
- metricsView: string,
- filterMatcher?: RequestMatcher,
-) {
- const timeSeriesUrlRegex = new RegExp(
- `/metrics-views/${metricsView}/timeseries`,
- );
- await page.waitForResponse(
- (response) =>
- timeSeriesUrlRegex.test(response.url()) &&
- (filterMatcher ? filterMatcher(response) : true),
- );
-}
-
-/**
- * Waits for a set of top list queries to end.
- * Optionally takes a filter matcher: {@link metricsViewRequestFilterMatcher}.
- */
-export async function waitForTopLists(
- page: Page,
- metricsView: string,
- dimensions: Array,
- filterMatcher?: RequestMatcher,
-) {
- const topListUrlRegex = new RegExp(`/metrics-views/${metricsView}/toplist`);
- await Promise.all(
- dimensions.map((dimension) =>
- page.waitForResponse(
- (response) =>
- topListUrlRegex.test(response.url()) &&
- response.request().postDataJSON().dimensionName === dimension &&
- (filterMatcher ? filterMatcher(response) : true),
- ),
- ),
- );
-}
-
-/**
- * Waits for a set of top list queries to end.
- * Optionally takes a filter matcher: {@link metricsViewRequestFilterMatcher}.
- */
-export async function waitForAggregationTopLists(
- page: Page,
- metricsView: string,
- dimensions: Array,
- filterMatcher?: RequestMatcher,
-) {
- const topListUrlRegex = new RegExp(
- `/metrics-views/${metricsView}/aggregation`,
- );
- await Promise.all(
- dimensions.map((dimension) =>
- page.waitForResponse(
- (response) =>
- topListUrlRegex.test(response.url()) &&
- !!response
- .request()
- .postDataJSON()
- ?.dimensions?.find((n) => n.name === dimension) &&
- (filterMatcher ? filterMatcher(response) : true),
- ),
- ),
- );
-}
-
-export type RequestMatcherFilter = { label: string; values: unknown[] };
-
-/**
- * Helper to add a request matcher to match metrics view queries with certain filter
- */
-export function metricsViewRequestFilterMatcher(
- response: Response,
- includeFilters: RequestMatcherFilter[],
- excludeFilters: RequestMatcherFilter[],
-) {
- const filterRequest = response.request().postDataJSON().where as V1Expression;
- const includeFilterRequest = new Map();
- const excludeFilterRequest = new Map();
-
- if (filterRequest?.cond?.exprs) {
- for (const expr of filterRequest.cond.exprs) {
- if (!expr.cond?.exprs?.[0]?.ident) continue;
- if (expr.cond.op === "OPERATION_IN") {
- includeFilterRequest.set(
- expr.cond.exprs[0].ident,
- expr.cond.exprs.slice(1).map((e) => e.val as string),
- );
- } else if (expr.cond.op === "OPERATION_NIN") {
- excludeFilterRequest.set(
- expr.cond.exprs[0].ident,
- expr.cond.exprs.slice(1).map((e) => e.val as string),
- );
- }
- }
- }
-
- return (
- includeFilters.every(
- ({ label, values }) =>
- includeFilterRequest
- .get(label)
- ?.every((val) => values.indexOf(val) >= 0) ?? false,
- ) &&
- excludeFilters.every(
- ({ label, values }) =>
- excludeFilterRequest
- .get(label)
- ?.every((val) => values.indexOf(val) >= 0) ?? false,
- )
- );
-}
-
-// Helper that opens the time range menu, calls your interactions, and then waits until the menu closes
-export async function interactWithTimeRangeMenu(
- page: Page,
- cb: () => void | Promise,
-) {
- // Open the menu
- await page.getByLabel("Select time range").click();
- // Run the defined interactions
- await cb();
- // Wait for menu to close
- await expect(
- page.getByRole("menu", { name: "Select time range" }),
- ).not.toBeVisible();
-}
-
-export async function waitForDashboard(page: Page) {
- return waitForValidResource(
- page,
- "AdBids_model_dashboard",
- "rill.runtime.v1.MetricsView",
- );
-}
-
-export async function updateAndWaitForDashboard(page: Page, code: string) {
- return Promise.all([
- updateCodeEditor(page, code),
- waitForValidResource(
- page,
- "AdBids_model_dashboard",
- "rill.runtime.v1.MetricsView",
- ),
- ]);
-}
diff --git a/web-local/tests/utils/dataSpecifcHelpers.ts b/web-local/tests/utils/dataSpecifcHelpers.ts
index 5f331a7816f..c221d0798f4 100644
--- a/web-local/tests/utils/dataSpecifcHelpers.ts
+++ b/web-local/tests/utils/dataSpecifcHelpers.ts
@@ -4,7 +4,7 @@ import {
waitForProfiling,
wrapRetryAssertion,
} from "./commonHelpers";
-import { assertLeaderboards } from "./dashboardHelpers";
+import { assertLeaderboards } from "web-local/tests/utils/metricsViewHelpers";
import { createModel } from "./modelHelpers";
import { uploadFile, waitForSource } from "./sourceHelpers";
diff --git a/web-local/tests/utils/exploreHelpers.ts b/web-local/tests/utils/exploreHelpers.ts
new file mode 100644
index 00000000000..b491f53a80e
--- /dev/null
+++ b/web-local/tests/utils/exploreHelpers.ts
@@ -0,0 +1,28 @@
+import type { Page } from "playwright";
+import {
+ clickMenuButton,
+ openFileNavEntryContextMenu,
+} from "web-local/tests/utils/commonHelpers";
+import { waitForFileNavEntry } from "web-local/tests/utils/waitHelpers";
+
+export async function createExploreFromSource(
+ page: Page,
+ sourcePath: string,
+ metricsViewPath: string,
+) {
+ await openFileNavEntryContextMenu(page, sourcePath);
+ await clickMenuButton(page, "Generate metrics");
+ await waitForFileNavEntry(page, metricsViewPath, true);
+ await page.getByText("Create Explore dashboard").click();
+}
+
+export async function createExploreFromModel(
+ page: Page,
+ modelPath: string,
+ metricsViewPath: string,
+) {
+ await openFileNavEntryContextMenu(page, modelPath);
+ await clickMenuButton(page, "Generate metrics");
+ await waitForFileNavEntry(page, metricsViewPath, true);
+ await page.getByText("Create Explore dashboard").click();
+}
diff --git a/web-local/tests/utils/metricsViewHelpers.ts b/web-local/tests/utils/metricsViewHelpers.ts
new file mode 100644
index 00000000000..1c9c355d497
--- /dev/null
+++ b/web-local/tests/utils/metricsViewHelpers.ts
@@ -0,0 +1,54 @@
+import { expect } from "@playwright/test";
+import type { Page } from "playwright";
+import { clickMenuButton, openFileNavEntryContextMenu } from "./commonHelpers";
+
+export async function createMetricsViewFromSource(
+ page: Page,
+ sourcePath: string,
+) {
+ await openFileNavEntryContextMenu(page, sourcePath);
+ await clickMenuButton(page, "Generate metrics");
+}
+
+export async function createMetricsViewFromModel(
+ page: Page,
+ modelPath: string,
+) {
+ await openFileNavEntryContextMenu(page, modelPath);
+ await clickMenuButton(page, "Generate metrics");
+}
+
+export async function assertLeaderboards(
+ page: Page,
+ leaderboards: Array<{
+ label: string;
+ values: Array;
+ }>,
+) {
+ for (const { label, values } of leaderboards) {
+ const leaderboardBlock = page.getByRole("table", {
+ name: `${label} leaderboard`,
+ });
+ await expect(leaderboardBlock).toBeVisible();
+
+ const actualValues = await leaderboardBlock
+ .locator("tr > td:nth-child(2)")
+ .allInnerTexts();
+ expect(actualValues).toEqual(values);
+ }
+}
+
+// Helper that opens the time range menu, calls your interactions, and then waits until the menu closes
+export async function interactWithTimeRangeMenu(
+ page: Page,
+ cb: () => void | Promise,
+) {
+ // Open the menu
+ await page.getByLabel("Select time range").click();
+ // Run the defined interactions
+ await cb();
+ // Wait for menu to close
+ await expect(
+ page.getByRole("menu", { name: "Select time range" }),
+ ).not.toBeVisible();
+}