diff --git a/admin/database/postgres/migrations/0045.sql b/admin/database/postgres/migrations/0045.sql index 96af8687ac1..0692eda768d 100644 --- a/admin/database/postgres/migrations/0045.sql +++ b/admin/database/postgres/migrations/0045.sql @@ -9,4 +9,4 @@ CREATE TABLE billing_issues ( created_on TIMESTAMPTZ NOT NULL DEFAULT now(), FOREIGN KEY (org_id) REFERENCES orgs (id) ON DELETE CASCADE, CONSTRAINT billing_issues_org_id_type_unique UNIQUE (org_id, type) -); +); \ No newline at end of file diff --git a/admin/database/postgres/migrations/0046.sql b/admin/database/postgres/migrations/0046.sql index a99789779c7..28dc2d34d97 100644 --- a/admin/database/postgres/migrations/0046.sql +++ b/admin/database/postgres/migrations/0046.sql @@ -1 +1 @@ -ALTER TABLE magic_auth_tokens ADD COLUMN title TEXT NOT NULL DEFAULT ''; \ No newline at end of file +ALTER TABLE magic_auth_tokens ADD COLUMN title TEXT NOT NULL DEFAULT ''; diff --git a/admin/database/postgres/postgres.go b/admin/database/postgres/postgres.go index be436647727..d8983eadf23 100644 --- a/admin/database/postgres/postgres.go +++ b/admin/database/postgres/postgres.go @@ -2163,8 +2163,8 @@ func (c *connection) decryptMap(m map[string]string, encKeyID string) (map[strin // magicAuthTokenDTO wraps database.MagicAuthToken, using the pgtype package to handly types that pgx can't read directly into their native Go types. type magicAuthTokenDTO struct { *database.MagicAuthToken - Attributes pgtype.JSON `db:"attributes"` - MetricsViewFields pgtype.TextArray `db:"metrics_view_fields"` + Attributes pgtype.JSON `db:"attributes"` + Fields pgtype.TextArray `db:"fields"` } func (c *connection) magicAuthTokenFromDTO(dto *magicAuthTokenDTO, fetchSecret bool) (*database.MagicAuthToken, error) { @@ -2172,7 +2172,7 @@ func (c *connection) magicAuthTokenFromDTO(dto *magicAuthTokenDTO, fetchSecret b if err != nil { return nil, err } - err = dto.MetricsViewFields.AssignTo(&dto.MagicAuthToken.Fields) + err = dto.Fields.AssignTo(&dto.MagicAuthToken.Fields) if err != nil { return nil, err } @@ -2193,8 +2193,8 @@ func (c *connection) magicAuthTokenFromDTO(dto *magicAuthTokenDTO, fetchSecret b // magicAuthTokenWithUserDTO wraps database.MagicAuthTokenWithUser, using the pgtype package to handly types that pgx can't read directly into their native Go types. type magicAuthTokenWithUserDTO struct { *database.MagicAuthTokenWithUser - Attributes pgtype.JSON `db:"attributes"` - MetricsViewFields pgtype.TextArray `db:"metrics_view_fields"` + Attributes pgtype.JSON `db:"attributes"` + Fields pgtype.TextArray `db:"fields"` } func (c *connection) magicAuthTokenWithUserFromDTO(dto *magicAuthTokenWithUserDTO) (*database.MagicAuthTokenWithUser, error) { @@ -2202,7 +2202,7 @@ func (c *connection) magicAuthTokenWithUserFromDTO(dto *magicAuthTokenWithUserDT if err != nil { return nil, err } - err = dto.MetricsViewFields.AssignTo(&dto.MagicAuthToken.Fields) + err = dto.Fields.AssignTo(&dto.MagicAuthToken.Fields) if err != nil { return nil, err } diff --git a/scripts/tsc-with-whitelist.sh b/scripts/tsc-with-whitelist.sh index c085fed1fb8..e3b6a22c69b 100755 --- a/scripts/tsc-with-whitelist.sh +++ b/scripts/tsc-with-whitelist.sh @@ -17,8 +17,9 @@ web-admin/src/features/scheduled-reports/selectors.ts: error TS2345 web-admin/src/features/view-as-user/clearViewedAsUser.ts: error TS18047 web-admin/src/features/view-as-user/clearViewedAsUser.ts: error TS2322 web-admin/src/features/view-as-user/setViewedAsUser.ts: error TS2322 -web-admin/src/routes/[organization]/[project]/-/dashboards/+page.ts: error TS2307 web-admin/src/features/projects/github/GithubData.ts: error TS2769 +web-admin/src/routes/[organization]/[project]/[dashboard]/+page.ts: error TS2307 +web-admin/src/routes/[organization]/[project]/-/dashboards/+page.ts: error TS2307 web-common/src/components/button-group/ButtonGroup.spec.ts: error TS2345 web-common/src/components/data-graphic/actions/mouse-position-to-domain-action-factory.ts: error TS2322 web-common/src/components/data-graphic/actions/outline.ts: error TS18047 @@ -97,7 +98,7 @@ web-common/src/features/metrics-views/errors.ts: error TS2322 web-common/src/features/metrics-views/errors.ts: error TS2345 web-common/src/features/metrics-views/metrics-internal-store.ts: error TS18048 web-common/src/features/metrics-views/metrics-internal-store.ts: error TS2345 -web-common/src/features/metrics-views/workspace/editor/create-placeholder.ts: error TS2322 +web-common/src/features/metrics-views/editor/create-placeholder.ts: error TS2322 web-common/src/features/models/selectors.ts: error TS18048 web-common/src/features/models/selectors.ts: error TS2345 web-common/src/features/models/utils/embedded.ts: error TS18048 @@ -142,6 +143,7 @@ web-common/src/runtime-client/invalidation.ts: error TS18048 web-common/src/runtime-client/invalidation.ts: error TS2345 web-common/src/runtime-client/watch-request-client.ts: error TS2322 web-common/vite.config.ts: error TS2339 +web-local/src/routes//dashboard/[name]/+page.ts: error TS2307 " # Run TypeScript compiler and find all distinct error per file diff --git a/web-admin/src/features/alerts/CreateAlert.svelte b/web-admin/src/features/alerts/CreateAlert.svelte index 299fabdfb24..3d428cf33bb 100644 --- a/web-admin/src/features/alerts/CreateAlert.svelte +++ b/web-admin/src/features/alerts/CreateAlert.svelte @@ -1,16 +1,16 @@ @@ -113,7 +114,7 @@ - +
diff --git a/web-admin/src/features/bookmarks/BookmarkFiltersFormSection.svelte b/web-admin/src/features/bookmarks/BookmarkFiltersFormSection.svelte index f8e7ec98d12..f995128825a 100644 --- a/web-admin/src/features/bookmarks/BookmarkFiltersFormSection.svelte +++ b/web-admin/src/features/bookmarks/BookmarkFiltersFormSection.svelte @@ -1,18 +1,18 @@ @@ -22,9 +22,9 @@ title="Filters" > diff --git a/web-admin/src/features/bookmarks/BookmarkTimeRangeSwitch.svelte b/web-admin/src/features/bookmarks/BookmarkTimeRangeSwitch.svelte index bb755d86a04..86d2686f49e 100644 --- a/web-admin/src/features/bookmarks/BookmarkTimeRangeSwitch.svelte +++ b/web-admin/src/features/bookmarks/BookmarkTimeRangeSwitch.svelte @@ -6,6 +6,7 @@ import { runtime } from "@rilldata/web-common/runtime-client/runtime-store.js"; export let metricsViewName: string; + export let exploreName: string; export let checked: boolean; const queryClient = useQueryClient(); @@ -13,6 +14,7 @@ queryClient, $runtime?.instanceId, metricsViewName, + exploreName, ); diff --git a/web-admin/src/features/bookmarks/Bookmarks.svelte b/web-admin/src/features/bookmarks/Bookmarks.svelte index bf12d462ad5..37b78beded2 100644 --- a/web-admin/src/features/bookmarks/Bookmarks.svelte +++ b/web-admin/src/features/bookmarks/Bookmarks.svelte @@ -4,40 +4,42 @@ createAdminServiceRemoveBookmark, getAdminServiceListBookmarksQueryKey, } from "@rilldata/web-admin/client"; + import BookmarkDialog from "@rilldata/web-admin/features/bookmarks/BookmarkDialog.svelte"; + import BookmarksContent from "@rilldata/web-admin/features/bookmarks/BookmarksDropdownMenuContent.svelte"; + import { createBookmarkApplier } from "@rilldata/web-admin/features/bookmarks/applyBookmark"; + import { createHomeBookmarkModifier } from "@rilldata/web-admin/features/bookmarks/createOrUpdateHomeBookmark"; + import { getBookmarkDataForDashboard } from "@rilldata/web-admin/features/bookmarks/getBookmarkDataForDashboard"; + import type { BookmarkEntry } from "@rilldata/web-admin/features/bookmarks/selectors"; import { useProjectId } from "@rilldata/web-admin/features/projects/selectors"; import { Button } from "@rilldata/web-common/components/button"; import { DropdownMenu, DropdownMenuTrigger, } from "@rilldata/web-common/components/dropdown-menu"; - import { createBookmarkApplier } from "@rilldata/web-admin/features/bookmarks/applyBookmark"; - import BookmarksContent from "@rilldata/web-admin/features/bookmarks/BookmarksDropdownMenuContent.svelte"; - import BookmarkDialog from "@rilldata/web-admin/features/bookmarks/BookmarkDialog.svelte"; - import { createHomeBookmarkModifier } from "@rilldata/web-admin/features/bookmarks/createOrUpdateHomeBookmark"; - import { getBookmarkDataForDashboard } from "@rilldata/web-admin/features/bookmarks/getBookmarkDataForDashboard"; - import type { BookmarkEntry } from "@rilldata/web-admin/features/bookmarks/selectors"; - import { useDashboardStore } from "@rilldata/web-common/features/dashboards/stores/dashboard-stores"; + import { useExploreStore } from "@rilldata/web-common/features/dashboards/stores/dashboard-stores"; import { ResourceKind } from "@rilldata/web-common/features/entity-management/resource-selectors"; + import { eventBus } from "@rilldata/web-common/lib/event-bus/event-bus"; import { runtime } from "@rilldata/web-common/runtime-client/runtime-store"; import { useQueryClient } from "@tanstack/svelte-query"; import { BookmarkIcon } from "lucide-svelte"; - import { eventBus } from "@rilldata/web-common/lib/event-bus/event-bus"; export let metricsViewName: string; + export let exploreName: string; let showDialog = false; let bookmark: BookmarkEntry | null = null; - $: bookmarkApplier = createBookmarkApplier( - $runtime?.instanceId, - metricsViewName, - ); + $: bookmarkApplier = createBookmarkApplier($runtime?.instanceId, exploreName); - $: dashboardStore = useDashboardStore(metricsViewName); + $: exploreStore = useExploreStore(exploreName); $: projectId = useProjectId($page.params.organization, $page.params.project); const queryClient = useQueryClient(); - $: homeBookmarkModifier = createHomeBookmarkModifier($runtime?.instanceId); + $: homeBookmarkModifier = createHomeBookmarkModifier( + $runtime?.instanceId, + metricsViewName, + exploreName, + ); const bookmarkDeleter = createAdminServiceRemoveBookmark(); function selectBookmark(bookmark: BookmarkEntry) { @@ -45,15 +47,15 @@ } async function createHomeBookmark() { - await homeBookmarkModifier(getBookmarkDataForDashboard($dashboardStore)); + await homeBookmarkModifier(getBookmarkDataForDashboard($exploreStore)); eventBus.emit("notification", { message: "Home bookmark created", }); return queryClient.refetchQueries( getAdminServiceListBookmarksQueryKey({ projectId: $projectId.data ?? "", - resourceKind: ResourceKind.MetricsView, - resourceName: metricsViewName, + resourceKind: ResourceKind.Explore, + resourceName: exploreName, }), ); } @@ -98,6 +100,7 @@ }} on:select={({ detail }) => selectBookmark(detail)} {metricsViewName} + {exploreName} /> @@ -105,6 +108,7 @@ { showDialog = false; bookmark = null; diff --git a/web-admin/src/features/bookmarks/BookmarksDropdownMenuContent.svelte b/web-admin/src/features/bookmarks/BookmarksDropdownMenuContent.svelte index 1959e67d9c3..ffad6412388 100644 --- a/web-admin/src/features/bookmarks/BookmarksDropdownMenuContent.svelte +++ b/web-admin/src/features/bookmarks/BookmarksDropdownMenuContent.svelte @@ -21,6 +21,7 @@ import HomeBookmarkPlus from "@rilldata/web-common/components/icons/HomeBookmarkPlus.svelte"; export let metricsViewName: string; + export let exploreName: string; const dispatch = createEventDispatcher(); const queryClient = useQueryClient(); @@ -36,6 +37,7 @@ organization, project, metricsViewName, + exploreName, ); $: filteredBookmarks = searchBookmarks($bookmarks.data, searchText); diff --git a/web-admin/src/features/bookmarks/applyBookmark.ts b/web-admin/src/features/bookmarks/applyBookmark.ts index de23149485b..4f6cca66162 100644 --- a/web-admin/src/features/bookmarks/applyBookmark.ts +++ b/web-admin/src/features/bookmarks/applyBookmark.ts @@ -1,33 +1,32 @@ import type { V1Bookmark } from "@rilldata/web-admin/client"; -import { useMetricsView } from "@rilldata/web-common/features/dashboards/selectors"; import { metricsExplorerStore } from "@rilldata/web-common/features/dashboards/stores/dashboard-stores"; +import { useExploreValidSpec } from "@rilldata/web-common/features/explores/selectors"; import { createQueryServiceMetricsViewSchema } from "@rilldata/web-common/runtime-client"; import { get } from "svelte/store"; -export function createBookmarkApplier( - instanceId: string, - metricsViewName: string, -) { - const metricsViewSpec = useMetricsView(instanceId, metricsViewName); +export function createBookmarkApplier(instanceId: string, exploreName: string) { + const validExploreSpec = useExploreValidSpec(instanceId, exploreName); const metricsSchema = createQueryServiceMetricsViewSchema( instanceId, - metricsViewName, + exploreName, ); return (bookmark: V1Bookmark) => { - const metricsViewSpecResp = get(metricsViewSpec); + const validExploreSpecResp = get(validExploreSpec); const metricsSchemaResp = get(metricsSchema); if ( !bookmark.data || - !metricsViewSpecResp.data || + !validExploreSpecResp.data?.metricsView || + !validExploreSpecResp.data?.explore || !metricsSchemaResp.data?.schema ) { return; } metricsExplorerStore.syncFromUrl( - metricsViewName, + exploreName, decodeURIComponent(bookmark.data), - metricsViewSpecResp.data, + validExploreSpecResp.data.metricsView, + validExploreSpecResp.data.explore, metricsSchemaResp.data.schema, ); }; diff --git a/web-admin/src/features/bookmarks/createOrUpdateHomeBookmark.ts b/web-admin/src/features/bookmarks/createOrUpdateHomeBookmark.ts index 19b9842a4f6..8b6f1a6968c 100644 --- a/web-admin/src/features/bookmarks/createOrUpdateHomeBookmark.ts +++ b/web-admin/src/features/bookmarks/createOrUpdateHomeBookmark.ts @@ -9,7 +9,11 @@ import { ResourceKind } from "@rilldata/web-common/features/entity-management/re import { useQueryClient } from "@tanstack/svelte-query"; import { get } from "svelte/store"; -export function createHomeBookmarkModifier(instanceId: string) { +export function createHomeBookmarkModifier( + instanceId: string, + metricsViewName: string, + exploreName: string, +) { const bookmarkCreator = createAdminServiceCreateBookmark(); const bookmarkUpdater = createAdminServiceUpdateBookmark(); const projectIdRes = useProjectId( @@ -21,7 +25,8 @@ export function createHomeBookmarkModifier(instanceId: string) { instanceId, get(page).params.organization, get(page).params.project, - get(page).params.dashboard, + metricsViewName, + exploreName, ); return (data: string) => { @@ -48,8 +53,8 @@ export function createHomeBookmarkModifier(instanceId: string) { displayName: "Home", description: "Main view For this dashboard", projectId: projectId.data, - resourceKind: ResourceKind.MetricsView, - resourceName: get(page).params.dashboard, + resourceKind: ResourceKind.Explore, + resourceName: exploreName, shared: true, default: true, data, diff --git a/web-admin/src/features/bookmarks/selectors.ts b/web-admin/src/features/bookmarks/selectors.ts index f0d372311b2..731bb7f65b9 100644 --- a/web-admin/src/features/bookmarks/selectors.ts +++ b/web-admin/src/features/bookmarks/selectors.ts @@ -6,17 +6,16 @@ import { import { useProjectId } from "@rilldata/web-admin/features/projects/selectors"; import type { CompoundQueryResult } from "@rilldata/web-common/features/compound-query-result"; import { getDashboardStateFromUrl } from "@rilldata/web-common/features/dashboards/proto-state/fromProto"; -import { - useMetricsView, - useMetricsViewTimeRange, -} from "@rilldata/web-common/features/dashboards/selectors"; -import { useDashboardStore } from "@rilldata/web-common/features/dashboards/stores/dashboard-stores"; +import { useMetricsViewTimeRange } from "@rilldata/web-common/features/dashboards/selectors"; +import { useExploreStore } from "@rilldata/web-common/features/dashboards/stores/dashboard-stores"; import { timeControlStateSelector } from "@rilldata/web-common/features/dashboards/time-controls/time-control-store"; import { ResourceKind } from "@rilldata/web-common/features/entity-management/resource-selectors"; +import { useExploreValidSpec } from "@rilldata/web-common/features/explores/selectors"; import { prettyFormatTimeRange } from "@rilldata/web-common/lib/time/ranges"; import { TimeRangePreset } from "@rilldata/web-common/lib/time/types"; import { createQueryServiceMetricsViewSchema, + type V1ExploreSpec, type V1MetricsViewSpec, type V1StructType, } from "@rilldata/web-common/runtime-client"; @@ -41,27 +40,29 @@ export function getBookmarks( orgName: string, projectName: string, metricsViewName: string, + exploreName: string, ): CreateQueryResult { return derived( [ useProjectId(orgName, projectName), - useMetricsView(instanceId, metricsViewName), + useExploreValidSpec(instanceId, exploreName), createQueryServiceMetricsViewSchema(instanceId, metricsViewName), createAdminServiceGetCurrentUser(), ], - ([projectId, metricsViewResp, schemaResp, userResp], set) => + ([projectId, validSpec, schemaResp, userResp], set) => createAdminServiceListBookmarks( { projectId: projectId.data, - resourceKind: ResourceKind.MetricsView, - resourceName: metricsViewName, + resourceKind: ResourceKind.Explore, + resourceName: exploreName, }, { query: { enabled: !!projectId?.data && !!metricsViewName && - !metricsViewResp.isFetching && + !!exploreName && + !validSpec.isFetching && !schemaResp.isFetching && userResp.isSuccess && !!userResp.data.user, @@ -74,8 +75,9 @@ export function getBookmarks( resp.bookmarks?.forEach((bookmarkResource) => { const bookmark = parseBookmarkEntry( bookmarkResource, - metricsViewResp.data as V1MetricsViewSpec, - schemaResp.data?.schema as V1StructType, + validSpec.data?.metricsView ?? {}, + validSpec.data?.explore ?? {}, + schemaResp.data?.schema ?? {}, ); if (bookmarkResource.default) { bookmarks.home = bookmark; @@ -116,6 +118,7 @@ export function getHomeBookmarkData( orgName: string, projectName: string, metricsViewName: string, + exploreName: string, ): CompoundQueryResult { return derived( getBookmarks( @@ -124,6 +127,7 @@ export function getHomeBookmarkData( orgName, projectName, metricsViewName, + exploreName, ), (bookmarks) => { if (bookmarks.isFetching || !bookmarks.data) { @@ -150,18 +154,20 @@ export function getPrettySelectedTimeRange( queryClient: QueryClient, instanceId: string, metricsViewName: string, + exploreName: string, ): Readable { return derived( [ - useMetricsView(instanceId, metricsViewName), + useExploreValidSpec(instanceId, exploreName), useMetricsViewTimeRange(instanceId, metricsViewName, { query: { queryClient }, }), - useDashboardStore(metricsViewName), + useExploreStore(metricsViewName), ], - ([metricViewSpec, timeRangeSummary, metricsExplorerEntity]) => { + ([validSpec, timeRangeSummary, metricsExplorerEntity]) => { const timeRangeState = timeControlStateSelector([ - metricViewSpec, + validSpec.data?.metricsView ?? {}, + validSpec.data?.explore ?? {}, timeRangeSummary, metricsExplorerEntity, ]); @@ -179,11 +185,13 @@ export function getPrettySelectedTimeRange( function parseBookmarkEntry( bookmarkResource: V1Bookmark, metricsViewSpec: V1MetricsViewSpec, + exploreSpec: V1ExploreSpec, schema: V1StructType, ): BookmarkEntry { const metricsEntity = getDashboardStateFromUrl( bookmarkResource.data ?? "", metricsViewSpec, + exploreSpec, schema, ); return { diff --git a/web-admin/src/features/dashboards/DashboardBookmarksStateProvider.svelte b/web-admin/src/features/dashboards/DashboardBookmarksStateProvider.svelte index e6670b124ae..b41c995f27e 100644 --- a/web-admin/src/features/dashboards/DashboardBookmarksStateProvider.svelte +++ b/web-admin/src/features/dashboards/DashboardBookmarksStateProvider.svelte @@ -5,20 +5,22 @@ import { getStateManagers } from "@rilldata/web-common/features/dashboards/state-managers/state-managers"; import { createDashboardStateSync } from "@rilldata/web-common/features/dashboards/stores/syncDashboardState"; import { initLocalUserPreferenceStore } from "@rilldata/web-common/features/dashboards/user-preferences"; - import { runtime } from "@rilldata/web-common/runtime-client/runtime-store"; import Spinner from "@rilldata/web-common/features/entity-management/Spinner.svelte"; import { EntityStatus } from "@rilldata/web-common/features/entity-management/types"; + import { runtime } from "@rilldata/web-common/runtime-client/runtime-store"; - export let metricViewName: string; + export let metricsViewName: string; + export let exploreName: string; - $: initLocalUserPreferenceStore(metricViewName); + $: initLocalUserPreferenceStore(exploreName); const queryClient = useQueryClient(); const homeBookmark = getHomeBookmarkData( queryClient, $runtime?.instanceId, $page.params.organization, $page.params.project, - metricViewName, + metricsViewName, + exploreName, ); $: dashboardStoreReady = createDashboardStateSync( diff --git a/web-admin/src/features/dashboards/listing/DashboardsTable.svelte b/web-admin/src/features/dashboards/listing/DashboardsTable.svelte index bbd8bbc98dc..bb8c033059c 100644 --- a/web-admin/src/features/dashboards/listing/DashboardsTable.svelte +++ b/web-admin/src/features/dashboards/listing/DashboardsTable.svelte @@ -1,7 +1,7 @@ @@ -36,7 +36,7 @@ >
{#if isMetricsExplorer} - + {:else} {/if} diff --git a/web-admin/src/features/dashboards/listing/DashboardsTableHeader.svelte b/web-admin/src/features/dashboards/listing/DashboardsTableHeader.svelte index 7a40769f088..aca8a0b73ff 100644 --- a/web-admin/src/features/dashboards/listing/DashboardsTableHeader.svelte +++ b/web-admin/src/features/dashboards/listing/DashboardsTableHeader.svelte @@ -1,5 +1,5 @@
- +
{ @@ -47,19 +48,36 @@ export interface DashboardResource { function getDashboardRefreshedOn( dashboard: V1Resource, - allResources: V1Resource[], + allResources: Map, ): string | undefined { if (!dashboard) return undefined; - const refName = dashboard.meta.refs[0]; - const refTable = allResources.find( - (r) => r.meta?.name?.name === refName?.name, + const metricsViewRefName = dashboard.meta.refs[0]; + const refTable = allResources.get( + `${metricsViewRefName?.kind}_${metricsViewRefName?.name}`, ); return ( refTable?.model?.state.refreshedOn || refTable?.source?.state.refreshedOn ); } +function getExploreRefreshedOn( + explore: V1Resource, + allResources: Map, +): string | undefined { + if (!explore) return undefined; + + // 1st get the metrics view for the explore + const exploreRefName = explore.meta.refs[0]; + const metricsView = allResources.get( + `${exploreRefName?.kind}_${exploreRefName?.name}`, + ); + if (!metricsView) return undefined; + + // next get the referenced table resource + return getDashboardRefreshedOn(metricsView, allResources); +} + // This iteration of `useDashboards` returns the above `DashboardResource` type, which includes `refreshedOn` export function useDashboardsV2( instanceId: string, @@ -67,15 +85,31 @@ export function useDashboardsV2( return createRuntimeServiceListResources(instanceId, undefined, { query: { select: (data) => { - // Filter for Metrics Explorers and Custom Dashboards - const resources = data.resources.filter( - (res) => res.metricsView || res.canvas, + // create a map since we are potentially looking up twice per explore + const allResources = getMapFromArray( + data.resources, + (r) => `${r.meta.name.kind}_${r.meta.name.name}`, ); - // Add `refreshedOn` to each resource - return resources.map((resource) => { - const refreshedOn = getDashboardRefreshedOn(resource, data.resources); - return { resource, refreshedOn }; - }); + const allDashboards: DashboardResource[] = []; + // filter canvas dashboards + const canvasDashboards = data.resources.filter((res) => res.canvas); + allDashboards.push( + ...canvasDashboards.map((resource) => { + // Add `refreshedOn` to each resource + const refreshedOn = getDashboardRefreshedOn(resource, allResources); + return { resource, refreshedOn }; + }), + ); + // filter explores + const explores = data.resources.filter((res) => res.explore); + allDashboards.push( + ...explores.map((resource) => { + // Add `refreshedOn` to each resource + const refreshedOn = getExploreRefreshedOn(resource, allResources); + return { resource, refreshedOn }; + }), + ); + return allDashboards; }, }, }); @@ -92,11 +126,22 @@ export function useDashboardV2( select: (data) => { if (!name) return; - const dashboard = data.resources.find( + const resource = data.resources.find( (res) => res.meta.name.name.toLowerCase() === name.toLowerCase(), ); - const refreshedOn = getDashboardRefreshedOn(dashboard, data.resources); - return { resource: dashboard, refreshedOn }; + // create a map since we are potentially looking up twice per explore + const allResources = getMapFromArray( + data.resources, + (r) => `${r.meta.name.kind}_${r.meta.name.name}`, + ); + + if (resource.canvas) { + const refreshedOn = getDashboardRefreshedOn(resource, allResources); + return { resource, refreshedOn }; + } + + const refreshedOn = getExploreRefreshedOn(resource, allResources); + return { resource, refreshedOn }; }, }, }); diff --git a/web-admin/src/features/dashboards/query-mappers/getDashboardFromAggregationRequest.ts b/web-admin/src/features/dashboards/query-mappers/getDashboardFromAggregationRequest.ts index e0f1d9c4352..c46174a728b 100644 --- a/web-admin/src/features/dashboards/query-mappers/getDashboardFromAggregationRequest.ts +++ b/web-admin/src/features/dashboards/query-mappers/getDashboardFromAggregationRequest.ts @@ -26,6 +26,7 @@ import { DashboardState_ActivePage } from "@rilldata/web-common/proto/gen/rill/u import { getQueryServiceMetricsViewSchemaQueryKey, queryServiceMetricsViewSchema, + type V1ExploreSpec, type V1Expression, type V1MetricsViewAggregationRequest, type V1MetricsViewSpec, @@ -40,6 +41,7 @@ export async function getDashboardFromAggregationRequest({ timeRangeSummary, executionTime, metricsView, + explore, annotations, }: QueryMapperArgs) { let loadedFromState = false; @@ -49,6 +51,7 @@ export async function getDashboardFromAggregationRequest({ instanceId, dashboard, metricsView, + explore, annotations["web_open_state"], ); loadedFromState = true; @@ -166,6 +169,7 @@ async function mergeDashboardFromUrlState( instanceId: string, dashboard: MetricsExplorerEntity, metricsViewSpec: V1MetricsViewSpec, + exploreSpec: V1ExploreSpec, urlState: string, ) { const schemaResp = await queryClient.fetchQuery({ @@ -180,6 +184,7 @@ async function mergeDashboardFromUrlState( const parsedDashboard = getDashboardStateFromUrl( urlState, metricsViewSpec, + exploreSpec, schemaResp.schema, ); for (const k in parsedDashboard) { diff --git a/web-admin/src/features/dashboards/query-mappers/mapQueryToDashboard.ts b/web-admin/src/features/dashboards/query-mappers/mapQueryToDashboard.ts index 16874fc7212..dc743b9f6e5 100644 --- a/web-admin/src/features/dashboards/query-mappers/mapQueryToDashboard.ts +++ b/web-admin/src/features/dashboards/query-mappers/mapQueryToDashboard.ts @@ -6,10 +6,10 @@ import type { } from "@rilldata/web-admin/features/dashboards/query-mappers/types"; import type { CompoundQueryResult } from "@rilldata/web-common/features/compound-query-result"; import { getProtoFromDashboardState } from "@rilldata/web-common/features/dashboards/proto-state/toProto"; -import { useMetricsView } from "@rilldata/web-common/features/dashboards/selectors"; import { getDefaultMetricsExplorerEntity } from "@rilldata/web-common/features/dashboards/stores/dashboard-store-defaults"; import type { MetricsExplorerEntity } from "@rilldata/web-common/features/dashboards/stores/metrics-explorer-entity"; import { initLocalUserPreferenceStore } from "@rilldata/web-common/features/dashboards/user-preferences"; +import { useExploreValidSpec } from "@rilldata/web-common/features/explores/selectors"; import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient"; import { createQueryServiceMetricsViewTimeRange, @@ -21,7 +21,7 @@ import { derived, get, readable } from "svelte/store"; type DashboardStateForQuery = { state?: string; - metricsView?: string; + exploreName?: string; }; /** @@ -29,6 +29,7 @@ type DashboardStateForQuery = { * Used to show the relevant dashboard for a report/alert. */ export function mapQueryToDashboard( + exploreName: string, queryName: string | undefined, queryArgsJson: string | undefined, executionTime: string | undefined, @@ -76,12 +77,14 @@ export function mapQueryToDashboard( "Failed to find metrics view name. Please check the format of the report.", }); } + // backwards compatibility for older alerts created on metrics explore directly + if (!exploreName) exploreName = metricsViewName; const instanceId = get(runtime).instanceId; return derived( [ - useMetricsView(instanceId, metricsViewName), + useExploreValidSpec(instanceId, exploreName), // TODO: handle non-timestamp dashboards createQueryServiceMetricsViewTimeRange( get(runtime).instanceId, @@ -89,8 +92,12 @@ export function mapQueryToDashboard( {}, ), ], - ([metricsViewResource, timeRangeSummary], set) => { - if (!metricsViewResource.data || !timeRangeSummary.data) { + ([validSpecResp, timeRangeSummary], set) => { + if ( + !validSpecResp.data?.metricsView || + !validSpecResp.data?.explore || + !timeRangeSummary.data + ) { set({ isFetching: true, error: "", @@ -98,21 +105,23 @@ export function mapQueryToDashboard( return; } - if (metricsViewResource.error || timeRangeSummary.error) { + if (validSpecResp.error || timeRangeSummary.error) { // error state set({ isFetching: false, error: - metricsViewResource.error?.message ?? - timeRangeSummary.error?.message, + validSpecResp.error?.message ?? timeRangeSummary.error?.message, }); return; } + const { metricsView, explore } = validSpecResp.data; + initLocalUserPreferenceStore(metricsViewName); const defaultDashboard = getDefaultMetricsExplorerEntity( metricsViewName, - metricsViewResource.data, + metricsView, + explore, timeRangeSummary.data, ); getDashboardState({ @@ -120,7 +129,8 @@ export function mapQueryToDashboard( instanceId, dashboard: defaultDashboard, req, - metricsView: metricsViewResource.data, + metricsView, + explore, timeRangeSummary: timeRangeSummary.data.timeRangeSummary, executionTime, annotations, @@ -131,7 +141,7 @@ export function mapQueryToDashboard( error: "", data: { state: getProtoFromDashboardState(newDashboard), - metricsView: metricsViewName, + exploreName, }, }); }) diff --git a/web-admin/src/features/dashboards/query-mappers/types.ts b/web-admin/src/features/dashboards/query-mappers/types.ts index 549c85cd662..4e1c329752b 100644 --- a/web-admin/src/features/dashboards/query-mappers/types.ts +++ b/web-admin/src/features/dashboards/query-mappers/types.ts @@ -1,5 +1,6 @@ import type { MetricsExplorerEntity } from "@rilldata/web-common/features/dashboards/stores/metrics-explorer-entity"; import type { + V1ExploreSpec, V1MetricsViewAggregationRequest, V1MetricsViewComparisonRequest, V1MetricsViewRowsRequest, @@ -23,6 +24,7 @@ export type QueryMapperArgs = { dashboard: MetricsExplorerEntity; req: R; metricsView: V1MetricsViewSpec; + explore: V1ExploreSpec; timeRangeSummary: V1TimeRangeSummary; executionTime: string; annotations: Record; diff --git a/web-admin/src/features/dashboards/query-mappers/utils.ts b/web-admin/src/features/dashboards/query-mappers/utils.ts index 8e497ae911b..861f607abac 100644 --- a/web-admin/src/features/dashboards/query-mappers/utils.ts +++ b/web-admin/src/features/dashboards/query-mappers/utils.ts @@ -201,3 +201,23 @@ export async function convertExprToToplist( toplist.data.map((t) => t[dimensionName]), ); } + +const ExploreNameRegex = /\/explore\/((?:\w|-)+)/; +export function getExploreName(webOpenPath: string) { + const matches = ExploreNameRegex.exec(webOpenPath); + if (!matches || matches.length < 1) return ""; + return matches[1]; +} + +export function getExplorePageUrl( + curPageUrl: URL, + organization: string, + project: string, + exploreName: string, + state: string, +) { + const url = new URL(`${curPageUrl.protocol}//${curPageUrl.host}`); + url.pathname = `/${organization}/${project}/explore/${exploreName}`; + url.searchParams.set("state", state); + return url.toString(); +} diff --git a/web-admin/src/features/embeds/CanvasDashboardEmbed.svelte b/web-admin/src/features/embeds/CanvasEmbed.svelte similarity index 100% rename from web-admin/src/features/embeds/CanvasDashboardEmbed.svelte rename to web-admin/src/features/embeds/CanvasEmbed.svelte diff --git a/web-admin/src/features/embeds/ExploreEmbed.svelte b/web-admin/src/features/embeds/ExploreEmbed.svelte new file mode 100644 index 00000000000..a3a1a6d0578 --- /dev/null +++ b/web-admin/src/features/embeds/ExploreEmbed.svelte @@ -0,0 +1,51 @@ + + +{#if isSuccess} + {#if isExploreErrored} +
Explore Error
+ {:else if data} + {#key exploreName} + + + + + + + + + + {/key} + {/if} +{/if} diff --git a/web-admin/src/features/embeds/MetricsExplorerEmbed.svelte b/web-admin/src/features/embeds/MetricsExplorerEmbed.svelte deleted file mode 100644 index 0954f5772b8..00000000000 --- a/web-admin/src/features/embeds/MetricsExplorerEmbed.svelte +++ /dev/null @@ -1,47 +0,0 @@ - - -{#if $dashboard.isSuccess} - {#if isDashboardErrored} -
Dashboard Error
- {:else} - {#key dashboardName} - - - - - - - - - - {/key} - {/if} -{/if} diff --git a/web-admin/src/features/embeds/UnsupportedKind.svelte b/web-admin/src/features/embeds/UnsupportedKind.svelte new file mode 100644 index 00000000000..99d6dc869b1 --- /dev/null +++ b/web-admin/src/features/embeds/UnsupportedKind.svelte @@ -0,0 +1,7 @@ +
+
+ Embedding for this resource type is currently unavailable. +
+
diff --git a/web-admin/src/features/navigation/TopNavigationBar.svelte b/web-admin/src/features/navigation/TopNavigationBar.svelte index 4ba2d93ff80..674024a1742 100644 --- a/web-admin/src/features/navigation/TopNavigationBar.svelte +++ b/web-admin/src/features/navigation/TopNavigationBar.svelte @@ -7,8 +7,8 @@ import Breadcrumbs from "@rilldata/web-common/components/navigation/breadcrumbs/Breadcrumbs.svelte"; import type { PathOption } from "@rilldata/web-common/components/navigation/breadcrumbs/types"; import GlobalDimensionSearch from "@rilldata/web-common/features/dashboards/dimension-search/GlobalDimensionSearch.svelte"; - import { useDashboard } from "@rilldata/web-common/features/dashboards/selectors"; import StateManagersProvider from "@rilldata/web-common/features/dashboards/state-managers/StateManagersProvider.svelte"; + import { useExplore } from "@rilldata/web-common/features/explores/selectors"; import { runtime } from "@rilldata/web-common/runtime-client/runtime-store"; import { createAdminServiceGetCurrentUser, @@ -25,7 +25,7 @@ import { useDashboardsV2 } from "../dashboards/listing/selectors"; import PageTitle from "../public-urls/PageTitle.svelte"; import { createAdminServiceGetMagicAuthToken } from "../public-urls/get-magic-auth-token"; - import { usePublicURLMetricsView } from "../public-urls/selectors"; + import { usePublicURLExplore } from "../public-urls/selectors"; import { useReports } from "../scheduled-reports/selectors"; import { isMetricsExplorerPage, @@ -103,13 +103,13 @@ $: visualizationPaths = visualizations.reduce((map, { resource }) => { const name = resource.meta.name.name; - const isMetricsExplorer = !!resource?.metricsView; + const isMetricsExplorer = !!resource?.explore; return map.set(name.toLowerCase(), { label: (isMetricsExplorer - ? resource?.metricsView?.spec?.title + ? resource?.explore?.spec?.title : resource?.canvas?.spec?.title) || name, - section: isMetricsExplorer ? undefined : "-/dashboards", + section: isMetricsExplorer ? "explore" : "-/dashboards", }); }, new Map()); @@ -136,25 +136,26 @@ report ? reportPaths : alert ? alertPaths : null, ]; - $: dashboardQuery = useDashboard(instanceId, dashboardParam, { + $: dashboardQuery = useExplore(instanceId, dashboardParam, { enabled: !!instanceId && onMetricsExplorerPage, }); - $: isDashboardValid = !!$dashboardQuery.data?.metricsView?.state?.validSpec; + $: exploreSpec = $dashboardQuery.data?.explore?.explore?.state?.validSpec; + $: isDashboardValid = !!exploreSpec; - // Public URLs do not have the metrics view name in the URL. However, the magic token's metadata includes the metrics view name. + // Public URLs do not have the resource name in the URL. However, the magic token's metadata includes the resource name. $: tokenQuery = createAdminServiceGetMagicAuthToken(token); $: dashboard = onPublicURLPage - ? $tokenQuery?.data?.token?.metricsView + ? $tokenQuery?.data?.token?.resourceName : dashboardParam; // If on a Public URL, get the dashboard title - $: metricsViewQuery = usePublicURLMetricsView( + $: exploreQuery = usePublicURLExplore( instanceId, - $tokenQuery?.data?.token?.metricsView, + $tokenQuery?.data?.token?.resourceName, onPublicURLPage, ); $: publicURLDashboardTitle = - $metricsViewQuery.data?.metricsView?.spec?.title ?? dashboard ?? ""; + $exploreQuery.data?.explore?.spec?.title ?? dashboard ?? ""; $: currentPath = [organization, project, dashboard, report || alert]; @@ -185,17 +186,25 @@ {/if} {#if (onMetricsExplorerPage && isDashboardValid) || onPublicURLPage} - {#key dashboard} - - - - {#if $user.isSuccess && $user.data.user && !onPublicURLPage} - - - - {/if} - - {/key} + {#if exploreSpec} + {#key dashboard} + + + + {#if $user.isSuccess && $user.data.user && !onPublicURLPage} + + + + {/if} + + {/key} + {/if} {/if} {#if $user.isSuccess} {#if $user.data && $user.data.user} diff --git a/web-admin/src/features/navigation/nav-utils.ts b/web-admin/src/features/navigation/nav-utils.ts index 15dc524d067..715d8bb8b3e 100644 --- a/web-admin/src/features/navigation/nav-utils.ts +++ b/web-admin/src/features/navigation/nav-utils.ts @@ -27,7 +27,7 @@ export function withinProject(page: Page): boolean { export function isMetricsExplorerPage(page: Page): boolean { return ( - page.route.id === "/[organization]/[project]/[dashboard]" || + page.route.id === "/[organization]/[project]/explore/[dashboard]" || page.route.id === "/-/embed" ); } diff --git a/web-admin/src/features/public-urls/CreatePublicURLForm.svelte b/web-admin/src/features/public-urls/CreatePublicURLForm.svelte index 648be5f863e..507cb939648 100644 --- a/web-admin/src/features/public-urls/CreatePublicURLForm.svelte +++ b/web-admin/src/features/public-urls/CreatePublicURLForm.svelte @@ -4,53 +4,53 @@ createAdminServiceIssueMagicAuthToken, getAdminServiceListMagicAuthTokensQueryKey, } from "@rilldata/web-admin/client"; - import { Button } from "@rilldata/web-common/components/button"; + import { Button, IconButton } from "@rilldata/web-common/components/button"; + import Calendar from "@rilldata/web-common/components/date-picker/Calendar.svelte"; + import Input from "@rilldata/web-common/components/forms/Input.svelte"; import Label from "@rilldata/web-common/components/forms/Label.svelte"; import Switch from "@rilldata/web-common/components/forms/Switch.svelte"; + import { Divider } from "@rilldata/web-common/components/menu"; + import { + Popover, + PopoverContent, + PopoverTrigger, + } from "@rilldata/web-common/components/popover"; import FilterChipsReadOnly from "@rilldata/web-common/features/dashboards/filters/FilterChipsReadOnly.svelte"; import { getStateManagers } from "@rilldata/web-common/features/dashboards/state-managers/state-managers"; + import { ResourceKind } from "@rilldata/web-common/features/entity-management/resource-selectors"; import { copyToClipboard } from "@rilldata/web-common/lib/actions/copy-to-clipboard"; import type { HTTPError } from "@rilldata/web-common/runtime-client/fetchWrapper"; + import { useQueryClient } from "@tanstack/svelte-query"; + import { Pencil } from "lucide-svelte"; + import { DateTime } from "luxon"; import { defaults, superForm } from "sveltekit-superforms"; import { yup } from "sveltekit-superforms/adapters"; import { object, string } from "yup"; - import { useQueryClient } from "@tanstack/svelte-query"; import { convertDateToMinutes, - getMetricsViewFields, + getExploreFields, getSanitizedDashboardStateParam, hasDashboardDimensionThresholdFilter, hasDashboardWhereFilter, } from "./form-utils"; - import { Divider } from "@rilldata/web-common/components/menu"; - import Input from "@rilldata/web-common/components/forms/Input.svelte"; - import { - Popover, - PopoverContent, - PopoverTrigger, - } from "@rilldata/web-common/components/popover"; - import { Pencil } from "lucide-svelte"; - import { IconButton } from "@rilldata/web-common/components/button"; - import Calendar from "@rilldata/web-common/components/date-picker/Calendar.svelte"; - import { DateTime } from "luxon"; const queryClient = useQueryClient(); const StateManagers = getStateManagers(); const { dashboardStore, - metricsViewName, + exploreName, selectors: { measures: { visibleMeasures }, dimensions: { visibleDimensions }, }, } = StateManagers; - $: ({ organization, project } = $page.params); + $: ({ organization, project, dashboard } = $page.params); $: isTitleEmpty = $form.title.trim() === ""; - $: metricsViewFields = getMetricsViewFields( + $: exploreFields = getExploreFields( $dashboardStore, $visibleDimensions, $visibleMeasures, @@ -58,7 +58,7 @@ $: sanitizedState = getSanitizedDashboardStateParam( $dashboardStore, - metricsViewFields, + exploreFields, ); let token: string; @@ -92,11 +92,10 @@ organization, project, data: { - metricsView: $metricsViewName, - metricsViewFilter: hasWhereFilter - ? $dashboardStore.whereFilter - : undefined, - metricsViewFields, + resourceType: ResourceKind.Explore as string, + resourceName: dashboard, + filter: hasWhereFilter ? $dashboardStore.whereFilter : undefined, + fields: exploreFields, ttlMinutes: setExpiration ? convertDateToMinutes(values.expiresAt).toString() : undefined, @@ -232,7 +231,7 @@

data?.resource, - enabled: !!instanceId && !!metricsViewName && enabled, + enabled: !!instanceId && !!exploreName && enabled, }, }, ); diff --git a/web-admin/src/features/scheduled-reports/metadata/ReportMetadata.svelte b/web-admin/src/features/scheduled-reports/metadata/ReportMetadata.svelte index ba703c40668..feec8947ed3 100644 --- a/web-admin/src/features/scheduled-reports/metadata/ReportMetadata.svelte +++ b/web-admin/src/features/scheduled-reports/metadata/ReportMetadata.svelte @@ -5,7 +5,7 @@ import IconButton from "@rilldata/web-common/components/button/IconButton.svelte"; import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu"; import ThreeDot from "@rilldata/web-common/components/icons/ThreeDot.svelte"; - import { useDashboard } from "@rilldata/web-common/features/dashboards/selectors"; + import { useExploreValidSpec } from "@rilldata/web-common/features/explores/selectors"; import CreateScheduledReportDialog from "@rilldata/web-common/features/scheduled-reports/ScheduledReportDialog.svelte"; import { getRuntimeServiceListResourcesQueryKey } from "@rilldata/web-common/runtime-client"; import { runtime } from "@rilldata/web-common/runtime-client/runtime-store"; @@ -36,9 +36,8 @@ // Get dashboard $: dashboardName = useReportDashboardName($runtime.instanceId, report); - $: dashboard = useDashboard($runtime.instanceId, $dashboardName.data); - $: dashboardTitle = - $dashboard.data?.metricsView.spec.title || $dashboardName.data; + $: dashboard = useExploreValidSpec($runtime.instanceId, $dashboardName.data); + $: dashboardTitle = $dashboard.data?.explore?.title || $dashboardName.data; // Get human-readable frequency $: humanReadableFrequency = @@ -140,7 +139,7 @@
Dashboard - {dashboardTitle} @@ -177,5 +176,6 @@ {/if} diff --git a/web-admin/src/routes/-/embed/+page.svelte b/web-admin/src/routes/-/embed/+page.svelte index 19cc6623286..63c98d031ab 100644 --- a/web-admin/src/routes/-/embed/+page.svelte +++ b/web-admin/src/routes/-/embed/+page.svelte @@ -2,15 +2,17 @@ import { page } from "$app/stores"; import ContentContainer from "@rilldata/web-admin/components/layout/ContentContainer.svelte"; import DashboardsTable from "@rilldata/web-admin/features/dashboards/listing/DashboardsTable.svelte"; - import CanvasDashboardEmbed from "@rilldata/web-admin/features/embeds/CanvasDashboardEmbed.svelte"; - import MetricsExplorerEmbed from "@rilldata/web-admin/features/embeds/MetricsExplorerEmbed.svelte"; + import CanvasEmbed from "@rilldata/web-admin/features/embeds/CanvasEmbed.svelte"; + import ExploreEmbed from "@rilldata/web-admin/features/embeds/ExploreEmbed.svelte"; import TopNavigationBarEmbed from "@rilldata/web-admin/features/embeds/TopNavigationBarEmbed.svelte"; + import UnsupportedKind from "@rilldata/web-admin/features/embeds/UnsupportedKind.svelte"; import { ResourceKind } from "@rilldata/web-common/features/entity-management/resource-selectors"; import type { V1ResourceName } from "@rilldata/web-common/runtime-client"; const instanceId = $page.url.searchParams.get("instance_id"); const initialResourceName = $page.url.searchParams.get("resource"); - const initialResourceKind = $page.url.searchParams.get("kind"); + const initialResourceType = + $page.url.searchParams.get("type") ?? $page.url.searchParams.get("kind"); // "kind" is for backwards compatibility const navigation = $page.url.searchParams.get("navigation"); // Ignoring state and theme params for now // const state = $page.url.searchParams.get("state"); @@ -18,10 +20,10 @@ // Manage active resource let activeResource: V1ResourceName | null = null; - if (initialResourceName && initialResourceKind) { + if (initialResourceName && initialResourceType) { activeResource = { name: initialResourceName, - kind: initialResourceKind, + kind: initialResourceType, }; } @@ -59,8 +61,10 @@ {/if} {/if} -{#if activeResource?.kind === ResourceKind.MetricsView.toString()} - +{#if activeResource?.kind === ResourceKind.Explore.toString()} + {:else if activeResource?.kind === ResourceKind.Canvas.toString()} - + +{:else} + {/if} diff --git a/web-admin/src/routes/[organization]/[project]/-/alerts/[alert]/open/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/alerts/[alert]/open/+page.svelte index c38eb72797e..5b957edd53a 100644 --- a/web-admin/src/routes/[organization]/[project]/-/alerts/[alert]/open/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/alerts/[alert]/open/+page.svelte @@ -3,6 +3,10 @@ import { page } from "$app/stores"; import { useAlert } from "@rilldata/web-admin/features/alerts/selectors"; import { mapQueryToDashboard } from "@rilldata/web-admin/features/dashboards/query-mappers/mapQueryToDashboard"; + import { + getExploreName, + getExplorePageUrl, + } from "@rilldata/web-admin/features/dashboards/query-mappers/utils.js"; import CtaButton from "@rilldata/web-common/components/calls-to-action/CTAButton.svelte"; import CtaContentContainer from "@rilldata/web-common/components/calls-to-action/CTAContentContainer.svelte"; import CtaLayoutContainer from "@rilldata/web-common/components/calls-to-action/CTALayoutContainer.svelte"; @@ -17,6 +21,9 @@ $: executionTime = $page.url.searchParams.get("execution_time"); $: alert = useAlert($runtime.instanceId, alertId); + $: exploreName = getExploreName( + $alert.data?.resource?.alert?.spec?.annotations?.web_open_path, + ); let dashboardStateForAlert: ReturnType; $: queryName = @@ -28,6 +35,7 @@ $alert.data?.resource?.alert?.spec?.queryArgsJson ?? ""; $: dashboardStateForAlert = mapQueryToDashboard( + exploreName, queryName, queryArgsJson, executionTime, @@ -39,8 +47,14 @@ } $: if ($dashboardStateForAlert.data) { - goto( - `/${organization}/${project}/${$dashboardStateForAlert.data.metricsView}?state=${encodeURIComponent($dashboardStateForAlert.data.state)}`, + void goto( + getExplorePageUrl( + $page.url, + organization, + project, + $dashboardStateForAlert.data.exploreName, + $dashboardStateForAlert.data.state, + ), ); } diff --git a/web-admin/src/routes/[organization]/[project]/-/reports/[report]/open/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/reports/[report]/open/+page.svelte index a72a679082d..af8ab1f76b5 100644 --- a/web-admin/src/routes/[organization]/[project]/-/reports/[report]/open/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/reports/[report]/open/+page.svelte @@ -2,6 +2,10 @@ import { goto } from "$app/navigation"; import { page } from "$app/stores"; import { mapQueryToDashboard } from "@rilldata/web-admin/features/dashboards/query-mappers/mapQueryToDashboard"; + import { + getExploreName, + getExplorePageUrl, + } from "@rilldata/web-admin/features/dashboards/query-mappers/utils"; import { useReport } from "@rilldata/web-admin/features/scheduled-reports/selectors"; import CtaButton from "@rilldata/web-common/components/calls-to-action/CTAButton.svelte"; import CtaContentContainer from "@rilldata/web-common/components/calls-to-action/CTAContentContainer.svelte"; @@ -17,9 +21,13 @@ $: executionTime = $page.url.searchParams.get("execution_time"); $: report = useReport($runtime.instanceId, reportId); + $: exploreName = getExploreName( + $report.data?.resource?.report?.spec?.annotations?.web_open_path, + ); let dashboardStateForReport: ReturnType; $: dashboardStateForReport = mapQueryToDashboard( + exploreName, $report.data?.resource?.report?.spec?.queryName, $report.data?.resource?.report?.spec?.queryArgsJson, executionTime, @@ -27,8 +35,14 @@ ); $: if ($dashboardStateForReport.data) { - goto( - `/${organization}/${project}/${$dashboardStateForReport.data.metricsView}?state=${encodeURIComponent($dashboardStateForReport.data.state)}`, + void goto( + getExplorePageUrl( + $page.url, + organization, + project, + $dashboardStateForReport.data.exploreName, + $dashboardStateForReport.data.state, + ), ); } diff --git a/web-admin/src/routes/[organization]/[project]/-/settings/public-urls/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/settings/public-urls/+page.svelte index d7ffff7dfe6..df767eb829b 100644 --- a/web-admin/src/routes/[organization]/[project]/-/settings/public-urls/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/settings/public-urls/+page.svelte @@ -1,18 +1,18 @@ -{#key metricsView} - - - - - - - - - +{#key resourceName} + {#if explore?.metricsView} + + + + + + + + + + {/if} {/key} diff --git a/web-admin/src/routes/[organization]/[project]/[dashboard]/+page.ts b/web-admin/src/routes/[organization]/[project]/[dashboard]/+page.ts new file mode 100644 index 00000000000..5fa55f18498 --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/[dashboard]/+page.ts @@ -0,0 +1,13 @@ +import { redirect } from "@sveltejs/kit"; +import type { PageLoad } from "./$types"; + +/** + * Redirect `/[organization]/[project]/[dashboard]` to `/[organization]/[project]/explore/[dashboard]`. + * Maintains backwards compatibility with legacy URLs. + */ +export const load: PageLoad = ({ params }) => { + throw redirect( + 307, + `/${params.organization}/${params.project}/explore/${params.dashboard}`, + ); +}; diff --git a/web-admin/src/routes/[organization]/[project]/[dashboard]/+page.svelte b/web-admin/src/routes/[organization]/[project]/explore/[dashboard]/+page.svelte similarity index 73% rename from web-admin/src/routes/[organization]/[project]/[dashboard]/+page.svelte rename to web-admin/src/routes/[organization]/[project]/explore/[dashboard]/+page.svelte index deb8e73a131..c7fc94cad90 100644 --- a/web-admin/src/routes/[organization]/[project]/[dashboard]/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/explore/[dashboard]/+page.svelte @@ -5,15 +5,15 @@ import DashboardBookmarksStateProvider from "@rilldata/web-admin/features/dashboards/DashboardBookmarksStateProvider.svelte"; import DashboardBuilding from "@rilldata/web-admin/features/dashboards/DashboardBuilding.svelte"; import DashboardErrored from "@rilldata/web-admin/features/dashboards/DashboardErrored.svelte"; + import { errorStore } from "@rilldata/web-admin/features/errors/error-store"; import { viewAsUserStore } from "@rilldata/web-admin/features/view-as-user/viewAsUserStore"; import { Dashboard } from "@rilldata/web-common/features/dashboards"; import DashboardThemeProvider from "@rilldata/web-common/features/dashboards/DashboardThemeProvider.svelte"; import DashboardURLStateProvider from "@rilldata/web-common/features/dashboards/proto-state/DashboardURLStateProvider.svelte"; - import { useDashboard } from "@rilldata/web-common/features/dashboards/selectors"; import StateManagersProvider from "@rilldata/web-common/features/dashboards/state-managers/StateManagersProvider.svelte"; import DashboardStateProvider from "@rilldata/web-common/features/dashboards/stores/DashboardStateProvider.svelte"; + import { useExplore } from "@rilldata/web-common/features/explores/selectors"; import { runtime } from "@rilldata/web-common/runtime-client/runtime-store"; - import { errorStore } from "../../../../features/errors/error-store"; const user = createAdminServiceGetCurrentUser(); @@ -26,10 +26,10 @@ $: ({ organization: orgName, project: projectName, - dashboard: dashboardName, + dashboard: exploreName, } = $page.params); - $: dashboard = useDashboard(instanceId, dashboardName, { + $: explore = useExplore(instanceId, exploreName, { refetchInterval: () => { if (isDashboardReconcilingForFirstTime) { return PollIntervalWhenDashboardFirstReconciling; @@ -42,19 +42,20 @@ }); $: isDashboardNotFound = - !$dashboard.data && - $dashboard.isError && - $dashboard.error?.response?.status === 404; + !$explore.data && + $explore.isError && + $explore.error?.response?.status === 404; + // TODO: should these be checking metricsView or explore? $: isDashboardReconcilingForFirstTime = - $dashboard?.data?.metricsView?.state?.validSpec === null && - !$dashboard?.data?.meta?.reconcileError; + $explore?.data?.metricsView?.metricsView?.state?.validSpec === null && + !$explore?.data?.metricsView?.meta?.reconcileError; // We check for metricsView.state.validSpec instead of meta.reconcileError. validSpec persists // from previous valid dashboards, allowing display even when the current dashboard spec is invalid // and a meta.reconcileError exists. $: isDashboardErrored = - $dashboard?.data?.metricsView?.state?.validSpec === null && - !!$dashboard?.data?.meta?.reconcileError; - $: metricViewName = $dashboard.data?.meta.name.name; + $explore?.data?.metricsView?.metricsView?.state?.validSpec === null && + !!$explore?.data?.metricsView?.meta?.reconcileError; + $: metricsViewName = $explore.data?.metricsView?.meta?.name?.name; // If no dashboard is found, show a 404 page $: if (isDashboardNotFound) { @@ -74,30 +75,30 @@ - {dashboardName} - Rill + {exploreName} - Rill -{#if $dashboard.isSuccess} +{#if $explore.isSuccess} {#if isDashboardReconcilingForFirstTime} {:else if isDashboardErrored} - {:else if metricViewName} - {#key metricViewName} - + {:else if metricsViewName} + {#key metricsViewName} + {#if $user.isSuccess && $user.data.user} - - + + - + {:else} - - + + - + diff --git a/web-common/src/components/dropdown-menu/DropdownMenuItem.svelte b/web-common/src/components/dropdown-menu/DropdownMenuItem.svelte index 220add1404e..bd1e7b0a161 100644 --- a/web-common/src/components/dropdown-menu/DropdownMenuItem.svelte +++ b/web-common/src/components/dropdown-menu/DropdownMenuItem.svelte @@ -13,19 +13,22 @@ let className: $$Props["class"] = undefined; export { className as class }; + export let href: $$Props["href"] = undefined; export let inset: $$Props["inset"] = undefined; export let type: $$Props["type"] = "default"; diff --git a/web-common/src/components/dropdown-menu/__stories__/DropdownMenu.stories.svelte b/web-common/src/components/dropdown-menu/__stories__/DropdownMenu.stories.svelte index da5e40ef487..0c5aab4bfac 100644 --- a/web-common/src/components/dropdown-menu/__stories__/DropdownMenu.stories.svelte +++ b/web-common/src/components/dropdown-menu/__stories__/DropdownMenu.stories.svelte @@ -1,10 +1,9 @@ @@ -34,7 +33,7 @@ Option 1 - + Option 2 diff --git a/web-common/src/components/icons/EditIcon.svelte b/web-common/src/components/icons/EditIcon.svelte index 65cf5869f68..ebbf65f515a 100644 --- a/web-common/src/components/icons/EditIcon.svelte +++ b/web-common/src/components/icons/EditIcon.svelte @@ -7,7 +7,7 @@ + export let size = "1em"; + export let color = "currentColor"; + export let className = ""; + + + + + + + + diff --git a/web-common/src/features/alerts/BaseAlertForm.svelte b/web-common/src/features/alerts/BaseAlertForm.svelte index 766cf5cb7af..49c41454eb8 100644 --- a/web-common/src/features/alerts/BaseAlertForm.svelte +++ b/web-common/src/features/alerts/BaseAlertForm.svelte @@ -5,7 +5,7 @@ generateAlertName, getTouched, } from "@rilldata/web-common/features/alerts/utils"; - import { useMetricsView } from "@rilldata/web-common/features/dashboards/selectors"; + import { useMetricsViewValidSpec } from "@rilldata/web-common/features/dashboards/selectors"; import { runtime } from "@rilldata/web-common/runtime-client/runtime-store"; import { X } from "lucide-svelte"; import { createEventDispatcher } from "svelte"; @@ -15,9 +15,9 @@ import AlertDialogDataTab from "./data-tab/AlertDialogDataTab.svelte"; import AlertDialogDeliveryTab from "./delivery-tab/AlertDialogDeliveryTab.svelte"; import { - type AlertFormValues, - checkIsTabValid, FieldsByTab, + checkIsTabValid, + type AlertFormValues, } from "./form-utils"; export let formState: ReturnType>; @@ -44,7 +44,10 @@ let currentTabIndex = 0; $: metricsViewName = $form["metricsViewName"]; // memoise to avoid rerenders - $: metricsView = useMetricsView($runtime.instanceId, metricsViewName); + $: metricsView = useMetricsViewValidSpec( + $runtime.instanceId, + metricsViewName, + ); function handleCancel() { if (getTouched($touched)) { diff --git a/web-common/src/features/alerts/CreateAlertForm.svelte b/web-common/src/features/alerts/CreateAlertForm.svelte index c8e20d2e7b1..dfc6ca02fbb 100644 --- a/web-common/src/features/alerts/CreateAlertForm.svelte +++ b/web-common/src/features/alerts/CreateAlertForm.svelte @@ -38,6 +38,7 @@ const { metricsViewName, + exploreName, dashboardStore, selectors: { timeRangeSelectors: { timeControlsState }, @@ -98,6 +99,7 @@ // The remaining fields are not editable in the form, but it's helpful to have access to them throughout the alert dialog // Also, in the future, they might even be editable. metricsViewName: $metricsViewName, + exploreName: $exploreName, whereFilter: $dashboardStore.whereFilter, dimensionThresholdFilters: $dashboardStore.dimensionThresholdFilters, timeRange: timeRange @@ -138,6 +140,7 @@ : undefined, renotify: !!values.snooze, renotifyAfterSeconds: values.snooze ? Number(values.snooze) : 0, + webOpenPath: `/explore/${$exploreName}`, }, }, }); diff --git a/web-common/src/features/alerts/EditAlertForm.svelte b/web-common/src/features/alerts/EditAlertForm.svelte index 860b41a3545..fe52b8f65f5 100644 --- a/web-common/src/features/alerts/EditAlertForm.svelte +++ b/web-common/src/features/alerts/EditAlertForm.svelte @@ -1,18 +1,19 @@
- Generate dashboard + Generate metrics {#if $ai} with AI diff --git a/web-common/src/features/connectors/olap/TableWorkspaceHeader.svelte b/web-common/src/features/connectors/olap/TableWorkspaceHeader.svelte index 62f3549fd9f..b9636d1395f 100644 --- a/web-common/src/features/connectors/olap/TableWorkspaceHeader.svelte +++ b/web-common/src/features/connectors/olap/TableWorkspaceHeader.svelte @@ -12,7 +12,7 @@ import { MetricsEventSpace } from "../../../metrics/service/MetricsTypes"; import { runtime } from "../../../runtime-client/runtime-store"; import { featureFlags } from "../../feature-flags"; - import { useCreateDashboardFromTableUIAction } from "../../metrics-views/ai-generation/generateMetricsView"; + import { useCreateMetricsViewFromTableUIAction } from "../../metrics-views/ai-generation/generateMetricsView"; export let connector: string; export let database: string = ""; @@ -21,13 +21,13 @@ const { ai } = featureFlags; - $: createDashboardFromTable = useCreateDashboardFromTableUIAction( + $: createMetricsViewFromTable = useCreateMetricsViewFromTableUIAction( $runtime.instanceId, connector, database, databaseSchema, table, - "dashboards", + "metrics", BehaviourEventMedium.Button, MetricsEventSpace.RightPanel, ); @@ -47,12 +47,12 @@ {@const collapse = isHeaderWidthSmall(headerWidth)} -