diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx index 54dfb92..1017e0a 100644 --- a/apps/admin/src/App.tsx +++ b/apps/admin/src/App.tsx @@ -9,7 +9,6 @@ import { } from "react-router-dom"; import { MdAdminPanelSettings, - MdAccountTree, MdCode, MdOpenInNew, MdPublic, @@ -19,7 +18,6 @@ import { AdminMergePgcPage, AdminPage, CrossmatchResultsPage, - DataCatalogPage, LoginPage, RecordCrossmatchDetailsPage, SqlQueryPage, @@ -107,9 +105,6 @@ function Layout() { - - - @@ -138,12 +133,6 @@ function App() { } /> } /> } /> - } /> - } /> - } - /> } /> } /> { - const response = await tapTables({ - client: backendClient, - query: { detail: "max" }, - }); - if (response.error) { - throw new Error(formatApiError(response.error)); - } - return response.data.data; -} - -async function fetchTableRows(tableName: string): Promise { - const response = await tapSync({ - client: backendClient, - query: { - query: `SELECT * FROM ${tableName}`, - }, - }); - if (response.error) { - throw new Error(formatApiError(response.error)); - } - return response.data.data; -} - -function findTableInfo( - schemas: TapSchemaEntry[] | undefined, - schemaName: string, - tableName: string, -): TapTableInfo | null { - const schema = schemas?.find((s) => s.schema_name === schemaName); - return schema?.tables.find((t) => t.name === tableName) ?? null; -} - -function filterSchemas( - schemas: TapSchemaEntry[] | undefined, - query: string, -): TapSchemaEntry[] { - if (!schemas?.length) { - return []; - } - const needle = query.trim().toLowerCase(); - if (!needle) { - return schemas; - } - return schemas - .map((s) => ({ - ...s, - tables: s.tables.filter((t) => { - const blob = - `${s.schema_name} ${t.name} ${t.description ?? ""}`.toLowerCase(); - return blob.includes(needle); - }), - })) - .filter((s) => s.tables.length > 0); -} - -interface SchemaSidebarProps { - schemas: TapSchemaEntry[]; - selectedSchema: string | null; - selectedTable: string | null; - onSelect: (schemaName: string, tableName: string) => void; -} - -function SchemaSidebar({ - schemas, - selectedSchema, - selectedTable, - onSelect, -}: SchemaSidebarProps): ReactElement { - return ( -
- {schemas.map((schema) => ( - -
    - {schema.tables.map((t) => { - const active = - selectedSchema === schema.schema_name && - selectedTable === t.name; - return ( -
  • - -
  • - ); - })} -
-
- ))} -
- ); -} - -function columnMetadataHint(column: TapColumnInfo): ReactElement { - return ( -
- {column.description ? {column.description} : null} -
- Type - - {column.datatype} - - Unit - - {column.unit ?? "—"} - - UCD - - {column.ucd ?? "—"} - -
-
- ); -} - -const catalogPanelClassName = - "rounded-lg border border-dashed border-border p-8 text-center"; - -function CatalogBrowsePrompt({ - onOpenSql, -}: { - onOpenSql: () => void; -}): ReactElement { - return ( -
- - Browse the data - - - Choose a table on the left to see column definitions and sample rows, or - run a custom query in the SQL editor. - - -
- ); -} - -interface TableDetailProps { - tableInfo: TapTableInfo; - syncPayload: TapSyncResponse | null; - syncLoading: boolean; - syncError: string | null; - onOpenSql: () => void; -} - -function TableDetail({ - tableInfo, - syncPayload, - syncLoading, - syncError, - onOpenSql, -}: TableDetailProps): ReactElement { - const metadataColumns = tableInfo.columns ?? []; - const syncTable = syncPayload?.resource.table; - const syncColumns = syncTable?.columns ?? []; - - const columnsForHints: TapColumnInfo[] = metadataColumns.length - ? metadataColumns - : syncColumns.map((c) => ({ - name: c.name, - datatype: c.datatype, - unit: c.unit ?? null, - })); - - const columnDefs: Column[] = columnsForHints.map((c) => ({ - slug: c.name, - hint: columnMetadataHint(c), - })); - - const rows = syncPayload ? syncPayloadToTable(syncPayload).rows : []; - - return ( -
-
-
- - {tableInfo.description ?? ( - - {tableInfo.name} - - )} - - {tableInfo.description ? ( - - {tableInfo.name} - - ) : null} -
- -
- - {syncError ? ( - - ) : syncLoading ? ( - - ) : ( - - - Sample rows - - - )} -
- ); -} - -export function DataCatalogPage(): ReactElement { - const { schemaName, tableName } = useParams<{ - schemaName?: string; - tableName?: string; - }>(); - const navigate = useNavigate(); - const [searchParams, setSearchParams] = useSearchParams(); - const isQueryMode = Boolean(useMatch("/data-catalog/query")); - const permalinkSql = searchParams.get("q"); - const [filter, setFilter] = useState(""); - const [sqlDraft, setSqlDraft] = useState(DEFAULT_SQL_EXAMPLE); - - const [sqlSidebarSelection, setSqlSidebarSelection] = useState<{ - schema: string; - table: string; - } | null>(null); - - const selectedSchema = isQueryMode - ? (sqlSidebarSelection?.schema ?? null) - : (schemaName ?? null); - const selectedTable = isQueryMode - ? (sqlSidebarSelection?.table ?? null) - : (tableName ?? null); - - useEffect(() => { - document.title = isQueryMode ? "SQL query | LEDA" : "Data catalog | LEDA"; - }, [isQueryMode]); - - useLayoutEffect(() => { - if (!isQueryMode || !permalinkSql) { - return; - } - setSqlDraft(parseSqlPermalink(permalinkSql)); - }, [isQueryMode, permalinkSql]); - - const { - data: tablesPayload, - loading: tablesLoading, - error: tablesError, - } = useDataFetching(() => fetchTablesList(), []); - - const { - data: syncPayload, - loading: syncLoading, - error: syncError, - } = useDataFetching((): Promise => { - if (!selectedSchema || !selectedTable) { - return Promise.resolve(null); - } - return fetchTableRows(selectedTable); - }, [selectedTable ?? ""]); - - const filtered = useMemo( - () => filterSchemas(tablesPayload?.schemas, filter), - [tablesPayload?.schemas, filter], - ); - - const selectedTableInfo = useMemo(() => { - if (!selectedSchema || !selectedTable) { - return null; - } - return findTableInfo(tablesPayload?.schemas, selectedSchema, selectedTable); - }, [tablesPayload?.schemas, selectedSchema, selectedTable]); - - function openSqlEditor(sql?: string): void { - if (sql) { - setSqlDraft(sql); - void navigate({ - pathname: "/data-catalog/query", - search: `?q=${encodeURIComponent(sql)}`, - }); - return; - } - void navigate({ pathname: "/data-catalog/query", search: "" }); - } - - function handleQueryRun(sql: string): void { - setSearchParams({ q: sql }, { replace: true }); - } - - function handleSelect(nextSchema: string, nextTable: string): void { - if (isQueryMode) { - setSqlSidebarSelection({ schema: nextSchema, table: nextTable }); - setSqlDraft(defaultSelectForTable(nextTable)); - return; - } - void navigate( - `/data-catalog/${encodeURIComponent(nextSchema)}/${encodeURIComponent(nextTable)}`, - ); - } - - function renderSidebarContent(): ReactElement { - if (tablesError && !tablesPayload) { - return ; - } - if (tablesLoading && !tablesPayload) { - return ; - } - if (!filtered.length) { - return ( - - {tablesPayload?.schemas.length - ? "No tables match your filter." - : "No tables returned by the API."} - - ); - } - return ( - - ); - } - - function renderDetailContent(): ReactElement { - if (isQueryMode) { - return ( - - ); - } - - if (!selectedSchema || !selectedTable) { - return openSqlEditor()} />; - } - - if (tablesError && !tablesPayload) { - return ; - } - - if (tablesLoading && !selectedTableInfo) { - return ; - } - - if (!selectedTableInfo) { - return ( - - ); - } - - return ( - openSqlEditor(defaultSelectForTable(selectedTable))} - /> - ); - } - - return ( -
-
-
-
- -
-
- {renderSidebarContent()} -
-
-
- - {renderDetailContent()} -
-
-
- ); -} diff --git a/apps/admin/src/pages/TableDetails.tsx b/apps/admin/src/pages/TableDetails.tsx index 2dd3279..7b72b2e 100644 --- a/apps/admin/src/pages/TableDetails.tsx +++ b/apps/admin/src/pages/TableDetails.tsx @@ -31,7 +31,7 @@ import { TextFilter, } from "../components/ui"; import { useDataFetching } from "@leda/lib/hooks"; -import { originalDataCatalogLink } from "@leda/lib/astronomy"; +import { sqlQueryLink } from "@leda/lib/astronomy"; import { formatCaughtError } from "@leda/lib/tap"; const DATA_TYPES: DataType[] = [ @@ -639,7 +639,7 @@ function ColumnInfo(props: ColumnInfoProps): ReactElement { title: "View table data", onClick: () => { void navigate( - originalDataCatalogLink( + sqlQueryLink( selectAllColumnsFromRawdataTable( props.tableName, selectedColumnInfo, diff --git a/apps/admin/src/pages/catalog/CatalogViewTabs.tsx b/apps/admin/src/pages/catalog/CatalogViewTabs.tsx deleted file mode 100644 index c4075a7..0000000 --- a/apps/admin/src/pages/catalog/CatalogViewTabs.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { ReactElement } from "react"; -import { NavLink } from "react-router-dom"; -import classNames from "classnames"; - -function catalogTabClassName({ isActive }: { isActive: boolean }): string { - return classNames( - "px-3 py-2 text-sm font-medium border-b-2 -mb-px transition-colors", - isActive - ? "border-accent text-primary" - : "border-transparent text-muted hover:text-primary hover:border-border", - ); -} - -export function CatalogViewTabs(): ReactElement { - return ( - - ); -} diff --git a/apps/admin/src/pages/index.ts b/apps/admin/src/pages/index.ts index b16979b..c10c4af 100644 --- a/apps/admin/src/pages/index.ts +++ b/apps/admin/src/pages/index.ts @@ -4,6 +4,5 @@ export { AdminMergePgcPage } from "./AdminMergePgc"; export { CrossmatchResultsPage } from "./CrossmatchResults"; export { LoginPage } from "./Login"; export { RecordCrossmatchDetailsPage } from "./RecordCrossmatchDetails"; -export { DataCatalogPage } from "./DataCatalog"; export { TablesPage } from "./Tables"; export { TableDetailsPage } from "./TableDetails"; diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index e366642..0159b6e 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -80,12 +80,6 @@ function App() { path="/records/:recordId/crossmatch" element={} /> - } /> - } /> - } - />
- + diff --git a/packages/lib/src/astronomy/index.ts b/packages/lib/src/astronomy/index.ts index d641fd5..7e27a52 100644 --- a/packages/lib/src/astronomy/index.ts +++ b/packages/lib/src/astronomy/index.ts @@ -1,2 +1,2 @@ export { decomposeDec, decomposeRa, pad2 } from "./sexagesimal"; -export { getSourceLink, originalDataCatalogLink } from "./links"; +export { getSourceLink, sqlQueryLink } from "./links"; diff --git a/packages/lib/src/astronomy/links.ts b/packages/lib/src/astronomy/links.ts index f5f98b2..bcec31b 100644 --- a/packages/lib/src/astronomy/links.ts +++ b/packages/lib/src/astronomy/links.ts @@ -2,6 +2,6 @@ export function getSourceLink(bibcode: string): string { return `https://ui.adsabs.harvard.edu/abs/${bibcode}/abstract`; } -export function originalDataCatalogLink(sql: string): string { - return `/data-catalog/query?q=${encodeURIComponent(sql)}`; +export function sqlQueryLink(sql: string): string { + return `/sql?q=${encodeURIComponent(sql)}`; } diff --git a/packages/lib/src/tap.ts b/packages/lib/src/tap.ts index bb0bd4d..68dddb4 100644 --- a/packages/lib/src/tap.ts +++ b/packages/lib/src/tap.ts @@ -106,10 +106,6 @@ export function syncPayloadToTable(payload: TapSyncResponse): TapTableData { return { columns, rows }; } -export function defaultSelectForTable(tableName: string, limit = 25): string { - return `SELECT * FROM ${tableName} LIMIT ${limit}`; -} - export function parseSqlPermalink(raw: string): string { const trimmed = raw.trim(); if (