diff --git a/client/testfixture/config.textproto b/client/testfixture/config.textproto index 2652887..fd9091b 100644 --- a/client/testfixture/config.textproto +++ b/client/testfixture/config.textproto @@ -43,14 +43,6 @@ record_apis: [ schemas: [ { name: "simple_schema" - schema: - "{" - " \"type\": \"object\"," - " \"properties\": {" - " \"name\": { \"type\": \"string\" }," - " \"obj\": { \"type\": \"object\" }" - " }," - " \"required\": [\"name\"]" - "}" + schema: "{ \"type\": \"object\", \"properties\": { \"name\": { \"type\": \"string\" }, \"obj\": { \"type\": \"object\" } }, \"required\": [\"name\"]}" } ] diff --git a/trailbase-core/js/admin/src/components/FilterBar.tsx b/trailbase-core/js/admin/src/components/FilterBar.tsx index 8298e01..0ba9c4e 100644 --- a/trailbase-core/js/admin/src/components/FilterBar.tsx +++ b/trailbase-core/js/admin/src/components/FilterBar.tsx @@ -1,5 +1,3 @@ -import { createSignal } from "solid-js"; - import { Button } from "@/components/ui/button"; import { TextField, TextFieldInput } from "@/components/ui/text-field"; @@ -9,12 +7,13 @@ export function FilterBar(props: { initial?: string; onSubmit: (filter: string) => void; }) { - const [input, setInput] = createSignal(props.initial ?? ""); - + let ref: HTMLInputElement | undefined; const onSubmit = () => { - const value = input(); + const value = ref?.value; console.debug("set filter: ", value); - props.onSubmit(value); + if (value !== undefined) { + props.onSubmit(value); + } }; return ( @@ -26,12 +25,10 @@ export function FilterBar(props: { > { - const value = (e.currentTarget as HTMLInputElement).value; - setInput(value); - }} /> diff --git a/trailbase-core/js/admin/src/components/tables/CreateAlterTable.tsx b/trailbase-core/js/admin/src/components/tables/CreateAlterTable.tsx index 2fac6a4..e46f0e2 100644 --- a/trailbase-core/js/admin/src/components/tables/CreateAlterTable.tsx +++ b/trailbase-core/js/admin/src/components/tables/CreateAlterTable.tsx @@ -66,6 +66,7 @@ import { } from "@/components/FormFields"; import type { FormType, AnyFieldApi } from "@/components/FormFields"; import { SheetContainer } from "@/components/SafeSheet"; +import { invalidateConfig } from "@/lib/config"; export function CreateAlterTableForm(props: { close: () => void; @@ -108,6 +109,7 @@ export function CreateAlterTableForm(props: { } if (!dryRun) { + invalidateConfig(); props.schemaRefetch().then(() => { props.setSelected(value.name); }); diff --git a/trailbase-core/js/admin/src/components/tables/TablesPage.tsx b/trailbase-core/js/admin/src/components/tables/TablesPage.tsx index 184cb03..b0a1642 100644 --- a/trailbase-core/js/admin/src/components/tables/TablesPage.tsx +++ b/trailbase-core/js/admin/src/components/tables/TablesPage.tsx @@ -70,7 +70,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; -import { createConfigQuery } from "@/lib/config"; +import { createConfigQuery, invalidateConfig } from "@/lib/config"; import { adminFetch } from "@/lib/fetch"; import { urlSafeBase64ToUuid, showSaveFileDialog } from "@/lib/utils"; import { RecordApiConfig } from "@proto/config"; @@ -339,6 +339,8 @@ function TableHeaderRightHandButtons(props: { await dropTable({ name: table().name, }); + + invalidateConfig(); props.schemaRefetch(); }} msg="Deleting a table will irreversibly delete all the data contained. Are you sure you'd like to continue?" @@ -518,13 +520,13 @@ type TableStore = { schemas: ListSchemasResponse; // Filter & pagination - filter: string | undefined; + filter: string | null; pagination: PaginationState; }; type FetchArgs = { tableName: string; - filter: string | undefined; + filter: string | null; pageSize: number; pageIndex: number; cursors: string[]; @@ -700,7 +702,7 @@ function RowDataTable(props: { { if (value === props.state.store.filter) { refetch(); @@ -831,11 +833,11 @@ function TablePane(props: { pageSize?: string; }>(); - function newStore(): TableStore { + function newStore({ filter }: { filter: string | null }): TableStore { return { selected: props.selectedTable, schemas: props.schemas, - filter: searchParams.filter ?? "", + filter, pagination: defaultPaginationState({ // NOTE: We index has to start at 0 since we're building the list of // stable cursors as we incrementally page. @@ -847,14 +849,25 @@ function TablePane(props: { // Cursors are deliberately kept out of the store to avoid tracking. let cursors: string[] = []; - const [store, setStore] = createStore(newStore()); + const [store, setStore] = createStore( + newStore({ filter: searchParams.filter ?? null }), + ); createEffect(() => { + // When switching tables/views, recreate the state. This includes the main + // store but also the current search params and the untracked cursors. if (store.selected.name !== props.selectedTable.name) { - // Recreate the state/store when we switch tables. cursors = []; - setStore(newStore()); + + // The new table probably has different schema, thus filters must not + // carry over. + const newFilter = { filter: null }; + setSearchParams(newFilter); + + setStore(newStore(newFilter)); + return; } + // When the filter changes, we also update the search params to be in sync. setSearchParams({ filter: store.filter, }); diff --git a/trailbase-core/js/admin/src/lib/config.ts b/trailbase-core/js/admin/src/lib/config.ts index f7c4b33..991c783 100644 --- a/trailbase-core/js/admin/src/lib/config.ts +++ b/trailbase-core/js/admin/src/lib/config.ts @@ -11,7 +11,7 @@ function createClient(): QueryClient { } const queryClient = createClient(); -export async function setConfig(config: Config) { +export async function setConfig(config: Config): Promise { const data = queryClient.getQueryData(defaultKey); const hash = data?.hash; if (!hash) { @@ -24,11 +24,13 @@ export async function setConfig(config: Config) { hash, }; console.debug("Updating config:", request); - const response = await updateConfig(request); + await updateConfig(request); - queryClient.invalidateQueries(); + invalidateConfig(); +} - return response; +export function invalidateConfig() { + queryClient.invalidateQueries(); } export function createConfigQuery() { diff --git a/trailbase-core/js/admin/src/lib/schema.ts b/trailbase-core/js/admin/src/lib/schema.ts index 9f6d4f4..5d6819f 100644 --- a/trailbase-core/js/admin/src/lib/schema.ts +++ b/trailbase-core/js/admin/src/lib/schema.ts @@ -256,5 +256,5 @@ export function tableType(table: Table | View): TableType { } export function hiddenTable(table: Table | View): boolean { - return table.name.startsWith("_"); + return table.name.startsWith("_") || table.name.startsWith("sqlite_"); } diff --git a/trailbase-core/src/admin/list_logs.rs b/trailbase-core/src/admin/list_logs.rs index 61daa9e..4209bb5 100644 --- a/trailbase-core/src/admin/list_logs.rs +++ b/trailbase-core/src/admin/list_logs.rs @@ -108,7 +108,8 @@ pub async fn list_logs_handler( limit, order, .. - } = parse_query(raw_url_query.as_deref()).unwrap_or_default(); + } = parse_query(raw_url_query.as_deref()) + .map_err(|err| Error::Precondition(format!("Invalid query '{err}': {raw_url_query:?}")))?; // NOTE: We cannot use state.table_metadata() here, since we're working on the logs database. // We could cache, however this is just the admin logs handler. diff --git a/trailbase-core/src/admin/rows/list_rows.rs b/trailbase-core/src/admin/rows/list_rows.rs index de2c04c..0accabe 100644 --- a/trailbase-core/src/admin/rows/list_rows.rs +++ b/trailbase-core/src/admin/rows/list_rows.rs @@ -32,8 +32,6 @@ pub async fn list_rows_handler( Path(table_name): Path, RawQuery(raw_url_query): RawQuery, ) -> Result, Error> { - // TODO: we should probably return an error if the query parsing fails rather than quietly - // falling back to defaults. let QueryParseResult { params: filter_params, cursor, @@ -41,7 +39,8 @@ pub async fn list_rows_handler( order, offset, .. - } = parse_query(raw_url_query.as_deref()).unwrap_or_default(); + } = parse_query(raw_url_query.as_deref()) + .map_err(|err| Error::Precondition(format!("Invalid query '{err}': {raw_url_query:?}")))?; let (virtual_table, table_or_view_metadata): (bool, Arc) = { if let Some(table_metadata) = state.table_metadata().get(&table_name) { diff --git a/trailbase-core/src/admin/user/list_users.rs b/trailbase-core/src/admin/user/list_users.rs index 81e5338..2d04779 100644 --- a/trailbase-core/src/admin/user/list_users.rs +++ b/trailbase-core/src/admin/user/list_users.rs @@ -69,7 +69,8 @@ pub async fn list_users_handler( limit, order, .. - } = parse_query(raw_url_query.as_deref()).unwrap_or_default(); + } = parse_query(raw_url_query.as_deref()) + .map_err(|err| Error::Precondition(format!("Invalid query '{err}': {raw_url_query:?}")))?; let Some(table_metadata) = state.table_metadata().get(USER_TABLE) else { return Err(Error::Precondition(format!("Table {USER_TABLE} not found"))); diff --git a/trailbase-core/src/records/list_records.rs b/trailbase-core/src/records/list_records.rs index 847b019..33e295b 100644 --- a/trailbase-core/src/records/list_records.rs +++ b/trailbase-core/src/records/list_records.rs @@ -55,20 +55,16 @@ pub async fn list_records_handler( return Err(RecordError::Internal("missing pk column".into())); }; - let Ok(QueryParseResult { + let QueryParseResult { params: filter_params, cursor, limit, order, count, .. - }) = parse_query(raw_url_query.as_deref()) - else { - #[cfg(test)] - log::error!("{:?}", raw_url_query); - - return Err(RecordError::BadRequest("Bad query")); - }; + } = parse_query(raw_url_query.as_deref()).map_err(|_err| { + return RecordError::BadRequest("Invalid query"); + })?; // Where clause contains column filters and cursor depending on what's present. let WhereClause {