+ );
+}
diff --git a/src/lib/tap.ts b/src/lib/tap.ts
new file mode 100644
index 0000000..0c35340
--- /dev/null
+++ b/src/lib/tap.ts
@@ -0,0 +1,63 @@
+import { tapSync } from "../clients/backend/sdk.gen";
+import type {
+ TapSyncResponse,
+ ValidationError,
+} from "../clients/backend/types.gen";
+import { backendClient } from "../clients/config";
+import type { CellPrimitive, Column } from "../components/ui/CommonTable";
+
+export const DEFAULT_SQL_EXAMPLE =
+ "SELECT * FROM layer2.designations WHERE pgc = 67872";
+
+export function formatApiError(error: unknown): string {
+ const detail = (error as { detail?: ValidationError[] }).detail;
+ if (detail?.length) {
+ return detail.map((e) => e.msg).join(", ");
+ }
+ return JSON.stringify(error);
+}
+
+export async function executeSqlQuery(sql: string): Promise {
+ const response = await tapSync({
+ client: backendClient,
+ query: { query: sql },
+ });
+ if (response.error) {
+ throw new Error(formatApiError(response.error));
+ }
+ if (!response.data?.data) {
+ throw new Error("No data received from server");
+ }
+ return response.data.data;
+}
+
+export function cellValue(value: unknown): CellPrimitive {
+ if (value === null || value === undefined) {
+ return "—";
+ }
+ if (typeof value === "number") {
+ return value;
+ }
+ return String(value);
+}
+
+export function syncPayloadToTable(payload: TapSyncResponse): {
+ columns: Column[];
+ rows: Record[];
+} {
+ const syncTable = payload.resource.table;
+ const syncColumns = syncTable.columns;
+ const columns: Column[] = syncColumns.map((c) => ({ name: c.name }));
+ const rows = (syncTable.data ?? []).map((row) => {
+ const out: Record = {};
+ for (let i = 0; i < syncColumns.length; i++) {
+ out[syncColumns[i].name] = cellValue(row[i]);
+ }
+ return out;
+ });
+ return { columns, rows };
+}
+
+export function defaultSelectForTable(tableName: string, limit = 25): string {
+ return `SELECT * FROM ${tableName} LIMIT ${limit}`;
+}
diff --git a/src/pages/DataCatalog.tsx b/src/pages/DataCatalog.tsx
index 4ae87d4..6170163 100644
--- a/src/pages/DataCatalog.tsx
+++ b/src/pages/DataCatalog.tsx
@@ -1,5 +1,5 @@
import { ReactElement, useEffect, useMemo, useState } from "react";
-import { useNavigate, useParams } from "react-router-dom";
+import { useMatch, useNavigate, useParams } from "react-router-dom";
import { tapSync, tapTables } from "../clients/backend/sdk.gen";
import type {
ListTapTablesResponse,
@@ -7,7 +7,6 @@ import type {
TapSchemaEntry,
TapSyncResponse,
TapTableInfo,
- ValidationError,
} from "../clients/backend/types.gen";
import { backendClient } from "../clients/config";
import { isLoggedIn } from "../auth/token";
@@ -22,15 +21,16 @@ import {
import { TextFilter } from "../components/core/TextFilter";
import { Accordion } from "../components/core/Accordion";
import { Text } from "../components/core/Text";
+import { Button } from "../components/core/Button";
import classNames from "classnames";
-
-function formatApiError(error: unknown): string {
- const detail = (error as { detail?: ValidationError[] }).detail;
- if (detail?.length) {
- return detail.map((e) => e.msg).join(", ");
- }
- return JSON.stringify(error);
-}
+import { CatalogViewTabs } from "../components/catalog/CatalogViewTabs";
+import { CatalogSqlPanel } from "../components/catalog/CatalogSqlPanel";
+import {
+ cellValue,
+ DEFAULT_SQL_EXAMPLE,
+ defaultSelectForTable,
+ formatApiError,
+} from "../lib/tap";
async function fetchTablesList(): Promise {
const response = await tapTables({
@@ -94,16 +94,6 @@ function filterSchemas(
.filter((s) => s.tables.length > 0);
}
-function cellValue(value: unknown): CellPrimitive {
- if (value === null || value === undefined) {
- return "—";
- }
- if (typeof value === "number") {
- return value;
- }
- return String(value);
-}
-
interface SchemaSidebarProps {
schemas: TapSchemaEntry[];
selectedSchema: string | null;
@@ -196,15 +186,23 @@ function columnMetadataHint(column: TapColumnInfo): ReactElement {
const catalogPanelClassName =
"rounded-lg border border-dashed border-border p-8 text-center";
-function CatalogBrowsePrompt(): ReactElement {
+function CatalogBrowsePrompt({
+ onOpenSql,
+}: {
+ onOpenSql: () => void;
+}): ReactElement {
return (
Browse the data
- Choose a table on the left to see column definitions and sample rows
+ Choose a table on the left to see column definitions and sample rows, or
+ run a custom query in the SQL editor.
+