diff --git a/runtime/feature_flags.go b/runtime/feature_flags.go
index 7448dfc3eb3..0dcd46bf01f 100644
--- a/runtime/feature_flags.go
+++ b/runtime/feature_flags.go
@@ -58,6 +58,9 @@ var defaultFeatureFlags = map[string]string{
"deploy": "true",
// Controls if the developer agent tool is available.
"developer_agent": "true",
+ // Controls visibility of the SQL query editor in Rill Cloud.
+ // Set to "true" in rill.yaml to enable.
+ "query_editor": "false",
// Controls if the dashboard state is persisted when navigating to a different dashboard.
"sticky_dashboard_state": "false",
}
diff --git a/runtime/feature_flags_test.go b/runtime/feature_flags_test.go
index f492fa5c0d1..6be4c54932a 100644
--- a/runtime/feature_flags_test.go
+++ b/runtime/feature_flags_test.go
@@ -42,6 +42,7 @@ func Test_ResolveFeatureFlags(t *testing.T) {
"chatCharts": true,
"deploy": true,
"developerAgent": true,
+ "queryEditor": false,
"stickyDashboardState": false,
},
},
@@ -66,6 +67,7 @@ func Test_ResolveFeatureFlags(t *testing.T) {
"chatCharts": true,
"deploy": true,
"developerAgent": true,
+ "queryEditor": false,
"stickyDashboardState": false,
},
},
@@ -90,6 +92,7 @@ func Test_ResolveFeatureFlags(t *testing.T) {
"chatCharts": true,
"deploy": true,
"developerAgent": true,
+ "queryEditor": false,
"stickyDashboardState": false,
},
},
@@ -114,6 +117,7 @@ func Test_ResolveFeatureFlags(t *testing.T) {
"chatCharts": true,
"deploy": true,
"developerAgent": true,
+ "queryEditor": false,
"stickyDashboardState": false,
},
},
diff --git a/web-admin/src/features/navigation/TopNavigationBar.svelte b/web-admin/src/features/navigation/TopNavigationBar.svelte
index fbe7460e134..92ad5deacc2 100644
--- a/web-admin/src/features/navigation/TopNavigationBar.svelte
+++ b/web-admin/src/features/navigation/TopNavigationBar.svelte
@@ -40,6 +40,7 @@
isOrganizationPage,
isProjectPage,
isPublicURLPage,
+ isQueryPage,
} from "./nav-utils";
export let createMagicAuthTokens: boolean;
@@ -77,6 +78,7 @@
$: onCanvasDashboardPage = isCanvasDashboardPage($page);
$: onPublicURLPage = isPublicURLPage($page);
$: onOrgPage = isOrganizationPage($page);
+ $: onQueryPage = isQueryPage($page);
// When "View As" is active, fetch deployment credentials for the mocked user.
// TanStack Query deduplicates by query key, so if the project layout already
@@ -352,6 +354,10 @@
{/if}
+
+ {#if onQueryPage && $dashboardChat}
+
+ {/if}
{/key}
{/if}
diff --git a/web-admin/src/features/navigation/nav-utils.ts b/web-admin/src/features/navigation/nav-utils.ts
index 52f283f4472..31191c9790b 100644
--- a/web-admin/src/features/navigation/nav-utils.ts
+++ b/web-admin/src/features/navigation/nav-utils.ts
@@ -41,6 +41,10 @@ export function isCanvasDashboardPage(page: Page): boolean {
return page.route.id === "/[organization]/[project]/canvas/[dashboard]";
}
+export function isQueryPage(page: Page): boolean {
+ return page.route.id === "/[organization]/[project]/-/query";
+}
+
/**
* Returns true if the page is any kind of dashboard page (either a Metrics Explorer or a Custom Dashboard).
*/
diff --git a/web-admin/src/features/projects/ProjectTabs.svelte b/web-admin/src/features/projects/ProjectTabs.svelte
index dca951e792e..d8ff7b06c45 100644
--- a/web-admin/src/features/projects/ProjectTabs.svelte
+++ b/web-admin/src/features/projects/ProjectTabs.svelte
@@ -12,7 +12,7 @@
export let project: string;
export let pathname: string;
- const { chat, reports, alerts } = featureFlags;
+ const { chat, queryEditor, reports, alerts } = featureFlags;
$: tabs = [
{
@@ -33,7 +33,7 @@
{
route: `/${organization}/${project}/-/query`,
label: "Query",
- hasPermission: false,
+ hasPermission: $queryEditor,
},
{
route: `/${organization}/${project}/-/reports`,
diff --git a/web-admin/src/routes/[organization]/[project]/-/query/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/query/+page.svelte
new file mode 100644
index 00000000000..8d46a9a9451
--- /dev/null
+++ b/web-admin/src/routes/[organization]/[project]/-/query/+page.svelte
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ {#if $dashboardChat && $chatOpen}
+
+ {/if}
+
diff --git a/web-common/src/features/connectors/explorer/TableEntry.svelte b/web-common/src/features/connectors/explorer/TableEntry.svelte
index f0621c2d1bc..b7f27a75107 100644
--- a/web-common/src/features/connectors/explorer/TableEntry.svelte
+++ b/web-common/src/features/connectors/explorer/TableEntry.svelte
@@ -32,7 +32,12 @@
$: expandedStore = store.getItem(connector, database, databaseSchema, table);
$: showSchema = $expandedStore;
- const { allowContextMenu, allowNavigateToTable, allowShowSchema } = store;
+ const {
+ allowContextMenu,
+ allowNavigateToTable,
+ allowShowSchema,
+ onInsertTable,
+ } = store;
$: isModelingSupportedForConnector = useIsModelingSupportedForConnector(
client,
@@ -94,6 +99,18 @@
+ {#if onInsertTable}
+
+ {/if}
+
{#if allowContextMenu && (showGenerateMetricsAndDashboard || isModelingSupported || showGenerateModel)}
@@ -161,4 +178,19 @@
.selected:hover {
@apply bg-gray-200;
}
+
+ .insert-button {
+ @apply hidden flex-none items-center justify-center;
+ @apply w-5 h-5 rounded text-xs font-semibold;
+ @apply text-fg-secondary bg-transparent;
+ }
+
+ .table-entry-header:hover .insert-button,
+ .open .insert-button {
+ @apply flex;
+ }
+
+ .insert-button:hover {
+ @apply text-fg-primary bg-gray-200;
+ }
diff --git a/web-common/src/features/connectors/explorer/TableSchema.svelte b/web-common/src/features/connectors/explorer/TableSchema.svelte
index 1b96278185a..fadc8f5736a 100644
--- a/web-common/src/features/connectors/explorer/TableSchema.svelte
+++ b/web-common/src/features/connectors/explorer/TableSchema.svelte
@@ -2,6 +2,7 @@
import Tooltip from "../../../components/tooltip/Tooltip.svelte";
import TooltipContent from "../../../components/tooltip/TooltipContent.svelte";
import { extractErrorMessage } from "../../../lib/errors";
+ import { prettyPrintType } from "../../query/query-utils";
import { useGetTable } from "../selectors";
import { useRuntimeClient } from "../../../runtime-client/v2";
@@ -31,12 +32,6 @@
$: error = $newTableQuery?.error;
$: isError = !!$newTableQuery?.error;
$: isLoading = $newTableQuery?.isLoading;
-
- function prettyPrintType(type: string) {
- // Remove CODE_ prefix and normalize unsupported types to just "UNKNOWN"
- const normalized = type.replace(/^CODE_/, "");
- return normalized.startsWith("UNKNOWN(") ? "UNKNOWN" : normalized;
- }
diff --git a/web-common/src/features/connectors/explorer/connector-explorer-store.ts b/web-common/src/features/connectors/explorer/connector-explorer-store.ts
index aa5e97f22bd..68cf7fae775 100644
--- a/web-common/src/features/connectors/explorer/connector-explorer-store.ts
+++ b/web-common/src/features/connectors/explorer/connector-explorer-store.ts
@@ -21,6 +21,17 @@ export class ConnectorExplorerStore {
table?: string,
) => void) = undefined;
+ /** Optional callback shown as a "+" button on table rows */
+ onInsertTable:
+ | undefined
+ | ((
+ driver: string,
+ connector: string,
+ database: string,
+ schema: string,
+ table: string,
+ ) => void) = undefined;
+
constructor(
{
allowNavigateToTable = true,
@@ -32,19 +43,29 @@ export class ConnectorExplorerStore {
expandedItems = {},
localStorage = true,
} = {},
- onToggleItem?: (
- connector: string,
- database?: string,
- schema?: string,
- table?: string,
- ) => void,
+ callbacks?: {
+ onToggleItem?: (
+ connector: string,
+ database?: string,
+ schema?: string,
+ table?: string,
+ ) => void;
+ onInsertTable?: (
+ driver: string,
+ 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;
+ if (callbacks?.onToggleItem) this.onToggleItem = callbacks.onToggleItem;
+ if (callbacks?.onInsertTable) this.onInsertTable = callbacks.onInsertTable;
this.store = localStorage
? localStorageStore("connector-explorer-state", {
@@ -94,7 +115,7 @@ export class ConnectorExplorerStore {
showConnectors: state.showConnectors,
expandedItems: {},
},
- onToggleItem ?? this.onToggleItem,
+ { onToggleItem: onToggleItem ?? this.onToggleItem },
);
}
diff --git a/web-common/src/features/feature-flags.ts b/web-common/src/features/feature-flags.ts
index 4d0b5a2f06d..f74c2b8d98e 100644
--- a/web-common/src/features/feature-flags.ts
+++ b/web-common/src/features/feature-flags.ts
@@ -63,6 +63,7 @@ class FeatureFlags {
dashboardChat = new FeatureFlag("user", false);
developerChat = new FeatureFlag("user", false);
deploy = new FeatureFlag("user", true);
+ queryEditor = new FeatureFlag("user", false);
stickyDashboardState = new FeatureFlag("user", false);
private flagsUnsub?: () => void;
diff --git a/web-common/src/features/query/ConnectorSelector.svelte b/web-common/src/features/query/ConnectorSelector.svelte
new file mode 100644
index 00000000000..c85d0958800
--- /dev/null
+++ b/web-common/src/features/query/ConnectorSelector.svelte
@@ -0,0 +1,50 @@
+
+
+