diff --git a/crates/assets/js/admin/src/components/Table.tsx b/crates/assets/js/admin/src/components/Table.tsx
index fc9183b2..bae605fc 100644
--- a/crates/assets/js/admin/src/components/Table.tsx
+++ b/crates/assets/js/admin/src/components/Table.tsx
@@ -1,4 +1,4 @@
-import { For, Match, Show, Switch } from "solid-js";
+import { Index, For, Match, Show, Switch } from "solid-js";
import type { Accessor } from "solid-js";
import {
flexRender,
@@ -36,6 +36,7 @@ import {
TableRow,
} from "@/components/ui/table";
import { Checkbox } from "@/components/ui/checkbox";
+import { Skeleton } from "@/components/ui/skeleton";
import { createIsMobile } from "@/lib/signals";
type TableOptions = {
@@ -204,12 +205,13 @@ function omit(object: T, key: K): Omit {
export function Table(props: {
table: SolidTable;
+ loading: boolean;
onRowClick?: (idx: number, row: TData) => void;
}) {
const paginationEnabled = () => props.table.options.manualPagination ?? false;
const paginationState = () => props.table.getState().pagination;
const columns = () => props.table.options.columns;
- const numRows = () => props.table.getRowModel().rows?.length ?? 0;
+ const numRows = (): number => props.table.getRowModel().rows.length;
const enableSorting = () =>
props.table.options.manualSorting || props.table.options.enableSorting;
@@ -248,6 +250,34 @@ export function Table(props: {
+
+
+ {() => (
+
+
+ {(cell) => (
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ )}
+
+
+
0}>
{(row) => (
@@ -256,15 +286,7 @@ export function Table(props: {
- 0}>
-
-
- Loading...
-
-
-
-
-
+
Empty
diff --git a/crates/assets/js/admin/src/components/accounts/AccountsPage.tsx b/crates/assets/js/admin/src/components/accounts/AccountsPage.tsx
index 465e3c68..09e574f9 100644
--- a/crates/assets/js/admin/src/components/accounts/AccountsPage.tsx
+++ b/crates/assets/js/admin/src/components/accounts/AccountsPage.tsx
@@ -404,65 +404,75 @@ export function AccountsPage() {
- Loading
+
-
+
-
- {
- return (
- <>
-
-
-
-
- (
-
- )}
- />
- >
- );
- }}
- />
-
- {/* WARN: This might open multiple sheets or at least scrims for each row */}
- editUser() !== undefined,
- (isOpen: boolean | ((value: boolean) => boolean)) => {
- if (!isOpen) {
- setEditUser(undefined);
- }
- },
- ]}
- children={(sheet) => {
- return (
-
-
-
-
-
- );
- }}
- />
+
+ {
+ return (
+ <>
+
+
+
+
+ (
+
+ )}
+ />
+ >
+ );
+ }}
+ />
+
+ {/* WARN: This might open multiple sheets or at least scrims for each row */}
+ editUser() !== undefined,
+ (isOpen: boolean | ((value: boolean) => boolean)) => {
+ if (!isOpen) {
+ setEditUser(undefined);
+ }
+ },
+ ]}
+ children={(sheet) => {
+ return (
+
+
+
+
+
+ );
+ }}
+ />
diff --git a/crates/assets/js/admin/src/components/editor/EditorPage.tsx b/crates/assets/js/admin/src/components/editor/EditorPage.tsx
index c3005116..144ea2e4 100644
--- a/crates/assets/js/admin/src/components/editor/EditorPage.tsx
+++ b/crates/assets/js/admin/src/components/editor/EditorPage.tsx
@@ -195,7 +195,7 @@ function ResultViewImpl(props: {
-
+
);
diff --git a/crates/assets/js/admin/src/components/logs/LogsPage.tsx b/crates/assets/js/admin/src/components/logs/LogsPage.tsx
index a0d8c97a..79002a3e 100644
--- a/crates/assets/js/admin/src/components/logs/LogsPage.tsx
+++ b/crates/assets/js/admin/src/components/logs/LogsPage.tsx
@@ -315,29 +315,27 @@ export function LogsPage() {
/>
-
Loading...}>
+
Error {`${logsFetch.error}`}
-
- Loading
-
-
-
- {pagination().pageIndex === 0 && logsFetch.data!.stats && (
+
+
- )}
+
2 || status >= 400) && method = "GET"'`}
/>
-
+
@@ -445,8 +443,8 @@ const Legend = L.Control.extend({
},
});
-function WorldMap(props: { country_codes: { [key in string]?: number } }) {
- const codes = () => props.country_codes;
+function WorldMap(props: { country_codes: Stats["country_codes"] }) {
+ const codes = () => props.country_codes ?? {};
let ref: HTMLDivElement | undefined;
let map: L.Map | undefined;
@@ -547,15 +545,10 @@ function WorldMap(props: { country_codes: { [key in string]?: number } }) {
);
}
-function LogsChart(props: { stats: Stats }) {
- const stats = props.stats;
-
+function LogsChart(props: { rates: Stats["rate"] }) {
const data = (): ChartData | undefined => {
- const s = stats;
- if (!s) return;
-
- const labels = s.rate.map(([ts, _v]) => Number(ts) * 1000);
- const data = s.rate.map(([_ts, v]) => v);
+ const labels = props.rates.map(([ts, _v]) => Number(ts) * 1000);
+ const data = props.rates.map(([_ts, v]) => v);
return {
labels,
diff --git a/crates/assets/js/admin/src/components/settings/DatabaseSettings.tsx b/crates/assets/js/admin/src/components/settings/DatabaseSettings.tsx
index b458a860..356bdf04 100644
--- a/crates/assets/js/admin/src/components/settings/DatabaseSettings.tsx
+++ b/crates/assets/js/admin/src/components/settings/DatabaseSettings.tsx
@@ -184,7 +184,7 @@ function DatabaseSettingsForm(props: {
diff --git a/crates/assets/js/admin/src/components/tables/TablePane.tsx b/crates/assets/js/admin/src/components/tables/TablePane.tsx
index 80e3e597..91d8c8f1 100644
--- a/crates/assets/js/admin/src/components/tables/TablePane.tsx
+++ b/crates/assets/js/admin/src/components/tables/TablePane.tsx
@@ -3,7 +3,7 @@ import type { Signal } from "solid-js";
import { createWritableMemo } from "@solid-primitives/memo";
import { TbRefresh, TbTable, TbTrash, TbColumns } from "solid-icons/tb";
import { useSearchParams } from "@solidjs/router";
-import { useQuery, useQueryClient } from "@tanstack/solid-query";
+import { useQuery } from "@tanstack/solid-query";
import type { QueryObserverResult } from "@tanstack/solid-query";
import type {
CellContext,
@@ -64,7 +64,7 @@ import {
UploadedFiles,
} from "@/components/tables/Files";
-import { createConfigQuery, invalidateConfig } from "@/lib/api/config";
+import { createConfigQuery } from "@/lib/api/config";
import type { Record, ArrayRecord } from "@/lib/record";
import { hashSqlValue } from "@/lib/value";
import { urlSafeBase64ToUuid, toHex, safeParseInt } from "@/lib/utils";
@@ -255,12 +255,11 @@ function TableHeaderRightHandButtons(props: {
const satisfiesRecordApi = createMemo(() =>
tableOrViewSatisfiesRecordApiRequirements(props.table, props.allTables),
);
-
- const queryClient = useQueryClient();
- const config = createConfigQuery();
const hasRecordApi = () =>
hasRecordApis(config?.data?.config, selectedSchema().name);
+ const config = createConfigQuery();
+
return (
{/* Delete table button */}
@@ -275,7 +274,7 @@ function TableHeaderRightHandButtons(props: {
dry_run: null,
});
} finally {
- invalidateConfig(queryClient);
+ await config.refetch();
await props.schemaRefetch();
}
})();
@@ -380,10 +379,9 @@ function TableHeader(props: {
}) {
const allTables = createMemo(() => props.allTables.map(([t, _]) => t));
const selectedSchema = () => props.table[0];
- const type = () => tableType(selectedSchema());
const headerTitle = () => {
- switch (type()) {
+ switch (tableType(selectedSchema())) {
case "view":
return "View";
case "virtualTable":
@@ -434,34 +432,6 @@ function TableHeader(props: {
);
}
-type TableState = {
- selected: Table | View;
- response: ListRowsResponse;
-};
-
-async function buildTableState(
- selected: Table | View,
- filter: string | null,
- pageSize: number,
- pageIndex: number,
- cursor: string | null,
- sorting: SortingState,
-): Promise
{
- const response = await fetchRows(
- selected.name,
- filter,
- pageSize,
- pageIndex,
- cursor,
- formatSortingAsOrder(sorting),
- );
-
- return {
- selected,
- response,
- };
-}
-
type CellType = "UUID" | "JSON" | "File" | "File[]" | ColumnDataType;
function deriveCellType(column: Column): CellType {
@@ -483,12 +453,29 @@ function deriveCellType(column: Column): CellType {
}
function buildColumnDefs(
- tableName: QualifiedName,
+ selectedSchema: Table | View,
+ columns: Column[] | undefined,
pkColumnIndex: number,
- columns: Column[],
blobEncoding: BlobEncoding,
): ColumnDef[] {
- return columns.map((col, idx) => {
+ if (columns === undefined) {
+ // Fallback to schema (rather than response) column defintions.
+ if (tableType(selectedSchema) === "table") {
+ return (selectedSchema as Table).columns.map((c) => ({
+ id: c.name,
+ header: c.name,
+ }));
+ }
+
+ // We don't have any schema column defs. Fallback to single col.
+ return [
+ {
+ header: "",
+ },
+ ];
+ }
+
+ return columns.map((col, idx): ColumnDef => {
const fk = getForeignKey(col.options);
const notNull = isNotNull(col.options);
const type = deriveCellType(col);
@@ -505,7 +492,7 @@ function buildColumnDefs(
cell: (context) =>
renderCell(
context,
- tableName,
+ selectedSchema.name,
columns,
pkColumnIndex,
{
@@ -515,12 +502,13 @@ function buildColumnDefs(
blobEncoding,
),
accessorFn: (row: ArrayRecord) => row[idx],
- } as ColumnDef;
+ };
});
}
-function ArrayRecordTable(props: {
- state: TableState;
+function RecordTable(props: {
+ selectedSchema: Table | View;
+ records: ListRowsResponse | undefined;
pagination: SimpleSignal;
filter: SimpleSignal;
columnPinningState: Signal;
@@ -533,31 +521,31 @@ function ArrayRecordTable(props: {
new Map(),
);
- const selectedSchema = () => props.state.selected;
+ const selectedSchema = () => props.selectedSchema;
const mutable = () =>
tableType(selectedSchema()) === "table" && !hiddenTable(selectedSchema());
- const data = () => props.state.response.rows;
-
const rowsRefetch = () => props.rowsRefetch();
- const columns = (): Column[] => props.state.response.columns;
- const totalRowCount = () => props.state.response.total_row_count;
+
+ const data = () => props.records?.rows;
+ const columns = () => props.records?.columns;
+ const totalRowCount = () => props.records?.total_row_count ?? 0;
const pkColumnIndex = createMemo(
- () => findPrimaryKeyColumnIndex(columns()) ?? 0,
+ () => findPrimaryKeyColumnIndex(columns() ?? []) ?? 0,
);
const table = createMemo(() => {
- const columns = buildColumnDefs(
- selectedSchema().name,
+ const columnDefs = buildColumnDefs(
+ selectedSchema(),
+ columns(),
pkColumnIndex(),
- props.state.response.columns,
blobEncoding(),
);
return buildTable(
{
// NOTE: The cell rendering is constrolled via the columnsDefs.
- columns,
+ columns: columnDefs,
data: data(),
columnPinning: props.columnPinningState[0],
onColumnPinningChange: props.columnPinningState[1],
@@ -633,10 +621,11 @@ function ArrayRecordTable(props: {
{
- setEditRow(rowDataToRow(columns(), row));
+ setEditRow(rowDataToRow(columns() ?? [], row));
}
: undefined
}
@@ -690,7 +679,8 @@ function ArrayRecordTable(props: {
await deleteRows(
prettyFormatQualifiedName(selectedSchema().name),
{
- primary_key_column: columns()[pkColumnIndex()].name,
+ primary_key_column:
+ columns()?.[pkColumnIndex()].name ?? "??",
values: ids,
},
);
@@ -739,7 +729,7 @@ function ArrayRecordTable(props: {
-
+
@@ -751,8 +741,8 @@ function IndexTable(props: {
table: Table;
schemas: ListSchemasResponse;
schemaRefetch: () => Promise;
- hidden: boolean;
}) {
+ const hidden = () => hiddenTable(props.table);
const [editIndex, setEditIndex] = createSignal();
const [selectedIndexes, setSelectedIndexes] = createSignal(new Set());
@@ -769,7 +759,7 @@ function IndexTable(props: {
return buildTable({
columns: indexColumns,
data: indexes().map(([index, _]) => index),
- onRowSelection: props.hidden
+ onRowSelection: hidden()
? undefined
: // eslint-disable-next-line solid/reactivity
(rows: Row[], value: boolean) => {
@@ -824,8 +814,9 @@ function IndexTable(props: {
{
setEditIndex(index);
@@ -838,7 +829,7 @@ function IndexTable(props: {
}}
-
+
{(sheet) => {
@@ -938,7 +929,7 @@ function TriggerTable(props: { table: Table; schemas: ListSchemasResponse }) {
);
@@ -950,8 +941,7 @@ export function TablePane(props: {
schemaRefetch: () => Promise;
}) {
const selectedSchema = () => props.selectedTable[0];
- const type = () => tableType(selectedSchema());
- const hidden = () => hiddenTable(selectedSchema());
+ const isTable = () => tableType(selectedSchema()) === "table";
const [searchParams, setSearchParams] = useSearchParams<{
filter?: string;
@@ -966,64 +956,62 @@ export function TablePane(props: {
return [];
});
- const filter = () => searchParams.filter;
- const setFilter = (filter: string | undefined) => {
- setSearchParams({
- ...searchParams,
- filter,
- });
- };
+ const [filter, setFilter] = [
+ () => searchParams.filter,
+ (filter: string | undefined) => {
+ setSearchParams({
+ ...searchParams,
+ filter,
+ });
+ },
+ ];
- const pagination = (): PaginationState => {
- return {
- pageSize: safeParseInt(searchParams.pageSize) ?? 20,
- pageIndex: safeParseInt(searchParams.pageIndex) ?? 0,
- };
- };
- const setPagination = (s: PaginationState) => {
- setSearchParams({
- ...searchParams,
- pageSize: s.pageSize,
- pageIndex: s.pageIndex,
- });
- };
+ const [pagination, setPagination] = [
+ (): PaginationState => {
+ return {
+ pageSize: safeParseInt(searchParams.pageSize) ?? 20,
+ pageIndex: safeParseInt(searchParams.pageIndex) ?? 0,
+ };
+ },
+ (s: PaginationState) => {
+ setSearchParams({
+ ...searchParams,
+ pageSize: s.pageSize,
+ pageIndex: s.pageIndex,
+ });
+ },
+ ];
const [sorting, setSorting] = createSignal([]);
- const state: QueryObserverResult = useQuery(() => ({
+ const records: QueryObserverResult = useQuery(() => ({
queryKey: [
- "tableData",
- prettyFormatQualifiedName(props.selectedTable[0].name),
+ selectedSchema().name,
searchParams.filter,
- pagination().pageIndex,
- pagination().pageSize,
+ pagination(),
sorting(),
- ],
+ ] as ReadonlyArray,
queryFn: async ({ queryKey }) => {
- const p = pagination();
- const c = cursors();
- const s = sorting();
-
- console.debug(
- `Fetching data with key: ${queryKey}, index: ${p.pageIndex}, cursors: ${c}, sorting: ${s}`,
- );
+ console.debug(`Fetching data with key: ${queryKey}`);
try {
- const state = await buildTableState(
- props.selectedTable[0],
+ const { pageSize, pageIndex } = pagination();
+
+ const response = await fetchRows(
+ selectedSchema().name,
searchParams.filter ?? null,
- p.pageSize,
- p.pageIndex,
- c[p.pageIndex - 1],
- s,
+ pageSize,
+ pageIndex,
+ cursors()[pageIndex - 1],
+ formatSortingAsOrder(sorting()),
);
- const cursor = state.response.cursor;
- if (cursor && p.pageIndex >= c.length) {
- setCursors([...c, cursor]);
+ const newCursor = response.cursor;
+ if (newCursor && pageIndex >= cursors().length) {
+ setCursors([...cursors(), newCursor]);
}
- return state;
+ return response;
} catch (err) {
// Reset.
setSearchParams({
@@ -1037,13 +1025,7 @@ export function TablePane(props: {
},
}));
- const client = useQueryClient();
- const rowsRefetch = () => {
- // Refetches the actual table contents above.
- client.invalidateQueries({
- queryKey: ["tableData"],
- });
- };
+ const rowsRefetch = records.refetch;
const schemaRefetch = async () => {
// First re-fetch the schema then the data rows to trigger a re-render.
await props.schemaRefetch();
@@ -1063,20 +1045,31 @@ export function TablePane(props: {
-
+
- Failed to fetch rows: {`${state.error}`}
+ Failed to fetch rows: {`${records.error}`}
- Loading...
+
+
+
-
-
+
-
+
-
+