From 0ed923e8acc34b864cef534950494b572a047d8d Mon Sep 17 00:00:00 2001
From: Brian Holmes <120223836+briangregoryholmes@users.noreply.github.com>
Date: Tue, 15 Oct 2024 23:21:14 -0400
Subject: [PATCH] fix: alternate connector support for visual metrics (#5909)
* wip
* cleanup
* wip
* cleanup
* feedback
---
web-common/src/components/forms/Input.svelte | 35 +-
.../src/components/forms/InputLabel.svelte | 40 ++
web-common/src/components/tag/Tag.svelte | 6 +-
.../features/connectors/ConnectorEntry.svelte | 34 +-
.../connectors/ConnectorExplorer.svelte | 41 +-
.../connectors/connector-explorer-store.ts | 290 +++++++-----
.../connectors/olap/DatabaseEntry.svelte | 17 +-
.../connectors/olap/DatabaseExplorer.svelte | 4 +-
.../olap/DatabaseSchemaEntry.svelte | 25 +-
.../connectors/olap/TableEntry.svelte | 100 ++--
.../connectors/olap/TableMenuItems.svelte | 3 +-
.../AlertConfirmation.svelte | 81 ++++
.../features/visual-metrics-editing/lib.ts | 3 +
.../features/workspaces/VisualMetrics.svelte | 446 +++++++++++-------
.../src/layout/navigation/Navigation.svelte | 8 +-
15 files changed, 735 insertions(+), 398 deletions(-)
create mode 100644 web-common/src/components/forms/InputLabel.svelte
create mode 100644 web-common/src/features/visual-metrics-editing/AlertConfirmation.svelte
diff --git a/web-common/src/components/forms/Input.svelte b/web-common/src/components/forms/Input.svelte
index 3893273e293..1035c8ad7a3 100644
--- a/web-common/src/components/forms/Input.svelte
+++ b/web-common/src/components/forms/Input.svelte
@@ -2,10 +2,8 @@
import { EyeIcon, EyeOffIcon } from "lucide-svelte";
import { onMount } from "svelte";
import { slide } from "svelte/transition";
- import InfoCircle from "../icons/InfoCircle.svelte";
- import Tooltip from "../tooltip/Tooltip.svelte";
- import TooltipContent from "../tooltip/TooltipContent.svelte";
import Select from "./Select.svelte";
+ import InputLabel from "./InputLabel.svelte";
const voidFunction = () => {};
@@ -93,24 +91,9 @@
{#if label}
-
-
- {#if hint}
-
-
-
-
-
- {@html hint}
-
-
- {/if}
-
+
+
+
{/if}
{#if fields && fields?.length > 1}
@@ -203,7 +186,7 @@
{id}
bind:selectElement
bind:value
- options={options ?? []}
+ {options}
{onChange}
fontSize={14}
{truncate}
@@ -235,14 +218,6 @@
@apply flex flex-col gap-y-1 h-fit justify-center;
}
- .label-wrapper {
- @apply flex items-center gap-x-1;
- }
-
- label {
- @apply text-sm font-medium text-gray-800;
- }
-
.option-wrapper {
@apply flex h-6 text-sm w-fit mb-1 rounded-[2px];
}
diff --git a/web-common/src/components/forms/InputLabel.svelte b/web-common/src/components/forms/InputLabel.svelte
new file mode 100644
index 00000000000..bca493296ae
--- /dev/null
+++ b/web-common/src/components/forms/InputLabel.svelte
@@ -0,0 +1,40 @@
+
+
+
+
+ {#if hint}
+
+
+
+
+
+ {@html hint}
+
+
+ {/if}
+
+
+
+
diff --git a/web-common/src/components/tag/Tag.svelte b/web-common/src/components/tag/Tag.svelte
index 616a52a1e43..7aa86d69a38 100644
--- a/web-common/src/components/tag/Tag.svelte
+++ b/web-common/src/components/tag/Tag.svelte
@@ -16,6 +16,10 @@
export let height = 21;
export let text: string = "";
+ let className: string | undefined = undefined;
+
+ export { className as class };
+
function getColorClass(color: string) {
switch (color) {
case "gray":
@@ -46,7 +50,7 @@
style:height="{height}px"
class="px-2 border rounded-[20px] items-center justify-center inline-flex shrink-0 {getColorClass(
color,
- )}"
+ )} {className}"
>
{#if text !== ""}
diff --git a/web-common/src/features/connectors/ConnectorEntry.svelte b/web-common/src/features/connectors/ConnectorEntry.svelte
index 2551151578a..63cd96fe893 100644
--- a/web-common/src/features/connectors/ConnectorEntry.svelte
+++ b/web-common/src/features/connectors/ConnectorEntry.svelte
@@ -6,14 +6,16 @@
createRuntimeServiceGetInstance,
} from "../../runtime-client";
import { runtime } from "../../runtime-client/runtime-store";
- import { connectorExplorerStore } from "./connector-explorer-store";
+ import type { ConnectorExplorerStore } from "./connector-explorer-store";
import { connectorIconMapping } from "./connector-icon-mapping";
import DatabaseExplorer from "./olap/DatabaseExplorer.svelte";
export let connector: V1AnalyzedConnector;
+ export let store: ConnectorExplorerStore;
$: connectorName = connector?.name as string;
- $: expanded = connectorExplorerStore.getItem(connectorName);
+ $: expandedStore = store.getItem(connectorName);
+ $: expanded = $expandedStore;
$: ({ instanceId } = $runtime);
$: instance = createRuntimeServiceGetInstance(instanceId, {
sensitive: true,
@@ -29,30 +31,34 @@
+
{connector.name}
-
+
{#if isOlapConnector}
-
OLAP
+
OLAP
{/if}
- {#if $expanded}
-
+
+ {#if expanded}
+
{/if}
{/if}
@@ -60,13 +66,13 @@
diff --git a/web-common/src/features/connectors/connector-explorer-store.ts b/web-common/src/features/connectors/connector-explorer-store.ts
index e2e626a40f4..aa5e97f22bd 100644
--- a/web-common/src/features/connectors/connector-explorer-store.ts
+++ b/web-common/src/features/connectors/connector-explorer-store.ts
@@ -1,47 +1,67 @@
import { localStorageStore } from "@rilldata/web-common/lib/store-utils";
-import { derived, type Readable } from "svelte/store";
+import { derived, get, writable, type Writable } from "svelte/store";
-interface ConnectorExplorerState {
+type ConnectorExplorerState = {
showConnectors: boolean;
expandedItems: Record
;
-}
-
-const initialState: ConnectorExplorerState = {
- showConnectors: true,
- expandedItems: {},
};
-function createConnectorExplorerStore() {
- const { subscribe, update } = localStorageStore(
- "connector-explorer-state",
- initialState,
- );
-
- function getItemKey(
- connector: string,
- database?: string,
- schema?: string,
- ): string {
- return [connector, database, schema].filter(Boolean).join("|");
- }
-
- function getDefaultState(
- connector: string, // Included for API consistency, but not used in this function
- database?: string,
- schema?: string,
- ): boolean {
- if (schema) return false; // Database Schema
- if (database) return true; // Database
- return true; // Connector
+export class ConnectorExplorerStore {
+ allowNavigateToTable: boolean;
+ allowContextMenu: boolean;
+ allowSelectTable: boolean;
+ allowShowSchema: boolean;
+ store: Writable;
+ onToggleItem:
+ | undefined
+ | ((
+ connector: string,
+ database?: string,
+ schema?: string,
+ table?: string,
+ ) => void) = undefined;
+
+ constructor(
+ {
+ allowNavigateToTable = true,
+ allowContextMenu = true,
+ allowShowSchema = true,
+ allowSelectTable = false,
+
+ showConnectors = true,
+ expandedItems = {},
+ localStorage = true,
+ } = {},
+ onToggleItem?: (
+ connector: string,
+ database?: string,
+ schema?: string,
+ table?: string,
+ ) => void,
+ ) {
+ this.allowNavigateToTable = allowNavigateToTable;
+ this.allowContextMenu = allowContextMenu;
+ this.allowShowSchema = allowShowSchema;
+ this.allowSelectTable = allowSelectTable;
+
+ if (onToggleItem) this.onToggleItem = onToggleItem;
+
+ this.store = localStorage
+ ? localStorageStore("connector-explorer-state", {
+ showConnectors,
+ expandedItems,
+ })
+ : writable({ showConnectors, expandedItems });
}
- function createItemIfNotExists(
+ createItemIfNotExists(
connector: string,
database?: string,
schema?: string,
+ table?: string,
) {
- update((state) => {
- const key = getItemKey(connector, database, schema);
+ this.store.update((state) => {
+ const key = getItemKey(connector, database, schema, table);
if (key in state.expandedItems) return state; // Item already exists
@@ -49,89 +69,145 @@ function createConnectorExplorerStore() {
...state,
expandedItems: {
...state.expandedItems,
- [key]: getDefaultState(connector, database, schema),
+ [key]: getDefaultState(connector, database, schema, table),
},
};
});
}
- return {
- subscribe,
- toggleExplorer: () =>
- update((state) => ({ ...state, showConnectors: !state.showConnectors })),
-
- getItem: (
+ duplicateStore(
+ onToggleItem?: (
connector: string,
database?: string,
schema?: string,
- ): Readable => {
- createItemIfNotExists(connector, database, schema);
-
- const key = getItemKey(connector, database, schema);
-
- return derived({ subscribe }, ($state) => {
- return $state.expandedItems[key];
- });
- },
-
- toggleItem: (connector: string, database?: string, schema?: string) =>
- update((state) => {
- const key = getItemKey(connector, database, schema);
- const currentState =
- state.expandedItems[key] ??
- getDefaultState(connector, database, schema);
- return {
- ...state,
- expandedItems: {
- ...state.expandedItems,
- [key]: !currentState,
- },
- };
- }),
-
- // Not used yet. Currently, the reconciler does not track connector renames.
- renameItem: (
- oldConnector: string,
- newConnector: string,
- oldDatabase?: string,
- newDatabase?: string,
- oldSchema?: string,
- newSchema?: string,
- ) =>
- update((state) => {
- const oldKeyPrefix = getItemKey(oldConnector, oldDatabase, oldSchema);
- const newKeyPrefix = getItemKey(newConnector, newDatabase, newSchema);
-
- const updatedExpandedItems = Object.fromEntries(
- Object.entries(state.expandedItems).map(([key, value]) => {
- if (key.startsWith(oldKeyPrefix)) {
- const newKey = key.replace(oldKeyPrefix, newKeyPrefix);
- return [newKey, value];
- }
- return [key, value];
- }),
- );
-
- return {
- ...state,
- expandedItems: updatedExpandedItems,
- };
- }),
-
- deleteItem: (connector: string, database?: string, schema?: string) =>
- update((state) => {
- const keyPrefix = getItemKey(connector, database, schema);
- const updatedExpandedItems = Object.fromEntries(
- Object.entries(state.expandedItems).filter(
- ([key]) => !key.startsWith(keyPrefix),
- ),
- );
- return {
- ...state,
- expandedItems: updatedExpandedItems,
- };
- }),
+ table?: string,
+ ) => void | Promise,
+ ) {
+ const state = get(this.store);
+ return new ConnectorExplorerStore(
+ {
+ allowNavigateToTable: false,
+ allowContextMenu: false,
+ allowShowSchema: false,
+ allowSelectTable: true,
+ localStorage: false,
+ showConnectors: state.showConnectors,
+ expandedItems: {},
+ },
+ onToggleItem ?? this.onToggleItem,
+ );
+ }
+
+ toggleExplorer = () =>
+ this.store.update((state) => ({
+ ...state,
+ showConnectors: !state.showConnectors,
+ }));
+
+ getItem = (
+ connector: string,
+ database?: string,
+ schema?: string,
+ table?: string,
+ ) => {
+ this.createItemIfNotExists(connector, database, schema, table);
+
+ const key = getItemKey(connector, database, schema, table);
+
+ return derived(this.store, ($state) => {
+ return $state.expandedItems[key];
+ });
+ };
+
+ toggleItem = (
+ connector: string,
+ database?: string,
+ schema?: string,
+ table?: string,
+ ) => {
+ if (this.onToggleItem)
+ this.onToggleItem(connector, database, schema, table);
+
+ if (table && !this.allowShowSchema) return;
+
+ this.store.update((state) => {
+ const key = getItemKey(connector, database, schema, table);
+ const currentState =
+ state.expandedItems[key] ??
+ getDefaultState(connector, database, schema, table);
+ return {
+ ...state,
+ expandedItems: {
+ ...state.expandedItems,
+ [key]: !currentState,
+ },
+ };
+ });
};
+
+ // Not used yet. Currently, the reconciler does not track connector renames.
+ renameItem = (
+ oldConnector: string,
+ newConnector: string,
+ oldDatabase?: string,
+ newDatabase?: string,
+ oldSchema?: string,
+ newSchema?: string,
+ ) =>
+ this.store.update((state) => {
+ const oldKeyPrefix = getItemKey(oldConnector, oldDatabase, oldSchema);
+ const newKeyPrefix = getItemKey(newConnector, newDatabase, newSchema);
+
+ const updatedExpandedItems = Object.fromEntries(
+ Object.entries(state.expandedItems).map(([key, value]) => {
+ if (key.startsWith(oldKeyPrefix)) {
+ const newKey = key.replace(oldKeyPrefix, newKeyPrefix);
+ return [newKey, value];
+ }
+ return [key, value];
+ }),
+ );
+
+ return {
+ ...state,
+ expandedItems: updatedExpandedItems,
+ };
+ });
+
+ deleteItem = (connector: string, database?: string, schema?: string) =>
+ this.store.update((state) => {
+ const keyPrefix = getItemKey(connector, database, schema);
+ const updatedExpandedItems = Object.fromEntries(
+ Object.entries(state.expandedItems).filter(
+ ([key]) => !key.startsWith(keyPrefix),
+ ),
+ );
+ return {
+ ...state,
+ expandedItems: updatedExpandedItems,
+ };
+ });
+}
+
+export const connectorExplorerStore = new ConnectorExplorerStore();
+
+// Helpers
+function getItemKey(
+ connector: string,
+ database?: string,
+ schema?: string,
+ table?: string,
+): string {
+ return [connector, database, schema, table].filter(Boolean).join("|");
}
-export const connectorExplorerStore = createConnectorExplorerStore();
+function getDefaultState(
+ connector: string, // Included for API consistency, but not used in this function
+ database?: string,
+ schema?: string,
+ table?: string,
+): boolean {
+ if (schema || table) return false; // Database Schema or Table
+ if (database) return true; // Database
+ return true; // Connector
+}
diff --git a/web-common/src/features/connectors/olap/DatabaseEntry.svelte b/web-common/src/features/connectors/olap/DatabaseEntry.svelte
index eb9089eff7c..85962b8f36e 100644
--- a/web-common/src/features/connectors/olap/DatabaseEntry.svelte
+++ b/web-common/src/features/connectors/olap/DatabaseEntry.svelte
@@ -4,21 +4,24 @@
import CaretDownIcon from "../../../components/icons/CaretDownIcon.svelte";
import { LIST_SLIDE_DURATION as duration } from "../../../layout/config";
import type { V1AnalyzedConnector } from "../../../runtime-client";
- import { connectorExplorerStore } from "../connector-explorer-store";
import DatabaseSchemaEntry from "./DatabaseSchemaEntry.svelte";
import { useDatabaseSchemas } from "./selectors";
+ import type { ConnectorExplorerStore } from "../connector-explorer-store";
export let instanceId: string;
export let connector: V1AnalyzedConnector;
export let database: string;
+ export let store: ConnectorExplorerStore;
$: connectorName = connector?.name as string;
- $: expanded = connectorExplorerStore.getItem(connectorName, database);
+ $: expandedStore = store.getItem(connectorName, database);
+ $: expanded = $expandedStore;
$: databaseSchemasQuery = useDatabaseSchemas(
instanceId,
connector?.name as string,
database,
);
+
$: ({ data, error, isLoading } = $databaseSchemasQuery);
@@ -26,12 +29,11 @@
{#if database}