diff --git a/mathesar/views.py b/mathesar/views.py index f005822e72..7d5c140955 100644 --- a/mathesar/views.py +++ b/mathesar/views.py @@ -8,6 +8,7 @@ from rest_framework.response import Response from mathesar.rpc.databases.configured import list_ as databases_list +from mathesar.rpc.explorations import list_ as explorations_list from mathesar.rpc.schemas import list_ as schemas_list from mathesar.rpc.servers.configured import list_ as get_servers_list from mathesar.rpc.tables import list_with_metadata as tables_list @@ -44,9 +45,8 @@ def get_table_list(request, database_id, schema_oid): return [] -def get_queries_list(request, schema_id): - # TODO_BETA: Fill this method - return [] +def get_queries_list(request, database_id, schema_id): + return explorations_list(request=request, database_id=database_id, schema_oid=schema_id) def get_ui_type_list(request, database_id): @@ -104,7 +104,7 @@ def get_common_data(request, database_id=None, schema_id=None): return { **_get_base_data_all_routes(request, database_id, schema_id), 'tables': get_table_list(request, database_id, schema_id), - 'queries': get_queries_list(request, schema_id), + 'queries': get_queries_list(request, database_id, schema_id), 'routing_context': 'normal', } diff --git a/mathesar_ui/src/api/rpc/explorations.ts b/mathesar_ui/src/api/rpc/explorations.ts index 636cb66515..dae99561bb 100644 --- a/mathesar_ui/src/api/rpc/explorations.ts +++ b/mathesar_ui/src/api/rpc/explorations.ts @@ -78,11 +78,14 @@ export type QueryInstanceTransformation = | QueryInstanceHideTransformation | QueryInstanceSortTransformation; -export interface SavedExploration { - id: number; +/** The data an exploration contains when the data explorer opens */ +export interface InitialExploration { database_id: number; - name: string; - description?: string; + schema_oid: number; +} + +/** The data needed to run an exploration without it being saved */ +export interface AnonymousExploration extends InitialExploration { base_table_oid: number; initial_columns: InitialColumn[]; transformations?: QueryInstanceTransformation[]; @@ -90,11 +93,38 @@ export interface SavedExploration { display_options?: unknown[]; } -export type UnsavedExploration = Partial & { - database_id: number; -}; +export interface AddableExploration extends AnonymousExploration { + name: string; + description?: string; +} -export type AnonymousExploration = Omit; +export interface SavedExploration extends AddableExploration { + id: number; +} + +export type MaybeSavedExploration = Partial & + InitialExploration; + +export function explorationIsAddable( + e: MaybeSavedExploration, +): e is MaybeSavedExploration & AddableExploration { + return ( + 'name' in e && + e.name !== '' && + e.name !== undefined && + 'base_table_oid' in e && + e.base_table_oid !== undefined && + 'initial_columns' in e && + e.initial_columns !== undefined && + e.initial_columns.length > 0 + ); +} + +export function explorationIsSaved( + e: MaybeSavedExploration, +): e is SavedExploration { + return explorationIsAddable(e) && 'id' in e && e.id !== undefined; +} export interface ExplorationRunParams { exploration_def: AnonymousExploration; @@ -164,17 +194,23 @@ export interface ExplorationResult { } export const explorations = { - list: rpcMethodTypeContainer<{ database_id: number }, SavedExploration[]>(), + list: rpcMethodTypeContainer< + { database_id: number; schema_oid: number }, + SavedExploration[] + >(), get: rpcMethodTypeContainer<{ exploration_id: number }, SavedExploration>(), - add: rpcMethodTypeContainer(), + add: rpcMethodTypeContainer< + { exploration_def: AddableExploration }, + SavedExploration + >(), delete: rpcMethodTypeContainer<{ exploration_id: number }, void>(), replace: rpcMethodTypeContainer< { new_exploration: SavedExploration }, - void + SavedExploration >(), run: rpcMethodTypeContainer(), diff --git a/mathesar_ui/src/pages/schema/SchemaOverview.svelte b/mathesar_ui/src/pages/schema/SchemaOverview.svelte index f254d002fd..c33ad6abe7 100644 --- a/mathesar_ui/src/pages/schema/SchemaOverview.svelte +++ b/mathesar_ui/src/pages/schema/SchemaOverview.svelte @@ -10,7 +10,7 @@ import type { Schema } from '@mathesar/models/Schema'; import type { Table } from '@mathesar/models/Table'; import { getDataExplorerPageUrl } from '@mathesar/routes/urls'; - import { refetchQueriesForSchema } from '@mathesar/stores/queries'; + import { refetchExplorationsForSchema } from '@mathesar/stores/queries'; import { refetchTablesForSchema } from '@mathesar/stores/tables'; import { AnchorButton, Button } from '@mathesar-component-library'; @@ -88,7 +88,7 @@
{ - await refetchQueriesForSchema(schema.oid); + await refetchExplorationsForSchema(schema); }} label={$_('retry')} icon={iconRefresh} diff --git a/mathesar_ui/src/routes/DataExplorerRoute.svelte b/mathesar_ui/src/routes/DataExplorerRoute.svelte index bd6db3d9dc..905defdf1a 100644 --- a/mathesar_ui/src/routes/DataExplorerRoute.svelte +++ b/mathesar_ui/src/routes/DataExplorerRoute.svelte @@ -4,8 +4,8 @@ import { router } from 'tinro'; import type { + MaybeSavedExploration, SavedExploration, - UnsavedExploration, } from '@mathesar/api/rpc/explorations'; import type { CancellablePromise } from '@mathesar/component-library'; import AppendBreadcrumb from '@mathesar/components/breadcrumb/AppendBreadcrumb.svelte'; @@ -19,7 +19,7 @@ getExplorationEditorPageUrl, } from '@mathesar/routes/urls'; import { abstractTypesMap } from '@mathesar/stores/abstract-types'; - import { getQuery } from '@mathesar/stores/queries'; + import { getExploration } from '@mathesar/stores/queries'; import { QueryManager, QueryModel, @@ -36,7 +36,7 @@ let queryLoadPromise: CancellablePromise; let query: Readable = readable(undefined); - function createQueryManager(queryInstance: UnsavedExploration) { + function createQueryManager(queryInstance: MaybeSavedExploration) { queryManager?.destroy(); queryManager = new QueryManager({ query: new QueryModel(queryInstance), @@ -80,6 +80,7 @@ router.location.hash.clear(); createQueryManager({ database_id: database.id, + schema_oid: schema.oid, ...(newQueryModel ?? {}), }); return; @@ -88,7 +89,7 @@ console.error('Unable to create query model from hash', hash); } } - createQueryManager({ database_id: database.id }); + createQueryManager({ database_id: database.id, schema_oid: schema.oid }); } async function loadSavedQuery(_queryId: number) { @@ -103,7 +104,7 @@ } queryLoadPromise?.cancel(); - queryLoadPromise = getQuery(_queryId); + queryLoadPromise = getExploration(_queryId); try { const queryInstance = await queryLoadPromise; createQueryManager(queryInstance); diff --git a/mathesar_ui/src/stores/queries.ts b/mathesar_ui/src/stores/queries.ts index 0b5d541b2a..65ed5910a3 100644 --- a/mathesar_ui/src/stores/queries.ts +++ b/mathesar_ui/src/stores/queries.ts @@ -41,27 +41,19 @@ import { writable, } from 'svelte/store'; -import { - type PaginatedResponse, - type RequestStatus, - addQueryParamsToUrl, - deleteAPI, - getAPI, - postAPI, - putAPI, -} from '@mathesar/api/rest/utils/requestUtils'; +import type { RequestStatus } from '@mathesar/api/rest/utils/requestUtils'; +import { api } from '@mathesar/api/rpc'; import type { + AddableExploration, ExplorationResult, SavedExploration, - UnsavedExploration, } from '@mathesar/api/rpc/explorations'; import type { Schema } from '@mathesar/models/Schema'; import CacheManager from '@mathesar/utils/CacheManager'; import { preloadCommonData } from '@mathesar/utils/preloadData'; -import type { SHARED_LINK_UUID_QUERY_PARAM } from '@mathesar/utils/shares'; -import { CancellablePromise } from '@mathesar-component-library'; +import type { CancellablePromise } from '@mathesar-component-library'; -import { currentSchemaId } from './schemas'; +import { currentSchema } from './schemas'; const commonData = preloadCommonData(); @@ -79,7 +71,7 @@ const schemasCacheManager = new CacheManager< const requestMap: Map< Schema['oid'], - CancellablePromise> + CancellablePromise > = new Map(); function sortedQueryEntries( @@ -121,12 +113,13 @@ function findSchemaStoreForQuery(id: SavedExploration['id']) { ); } -export async function refetchQueriesForSchema( - schemaId: Schema['oid'], -): Promise { - const store = schemasCacheManager.get(schemaId); +export async function refetchExplorationsForSchema(schema: { + oid: number; + database: { id: number }; +}): Promise { + const store = schemasCacheManager.get(schema.oid); if (!store) { - console.error(`Queries store for schema: ${schemaId} not found.`); + console.error(`Queries store for schema: ${schema.oid} not found.`); return undefined; } @@ -136,17 +129,18 @@ export async function refetchQueriesForSchema( requestStatus: { state: 'processing' }, })); - requestMap.get(schemaId)?.cancel(); - - const queriesRequest = getAPI>( - `/api/db/v0/queries/?schema=${schemaId}&limit=500`, - ); - requestMap.set(schemaId, queriesRequest); + requestMap.get(schema.oid)?.cancel(); - const response = await queriesRequest; - const queriesResult = response.results || []; + const queriesRequest = api.explorations + .list({ + database_id: schema.database.id, + schema_oid: schema.oid, + }) + .run(); + requestMap.set(schema.oid, queriesRequest); - const schemaQueriesStore = setSchemaQueriesStore(schemaId, queriesResult); + const queriesResult = await queriesRequest; + const schemaQueriesStore = setSchemaQueriesStore(schema.oid, queriesResult); return get(schemaQueriesStore); } catch (err) { @@ -165,40 +159,41 @@ export async function refetchQueriesForSchema( let preload = true; -export function getQueriesStoreForSchema( - schemaId: Schema['oid'], -): Writable { - let store = schemasCacheManager.get(schemaId); +export function getExplorationsStoreForSchema(schema: { + oid: number; + database: { id: number }; +}): Writable { + let store = schemasCacheManager.get(schema.oid); if (!store) { store = writable({ - schemaId, + schemaId: schema.oid, requestStatus: { state: 'processing' }, data: new Map(), }); - schemasCacheManager.set(schemaId, store); - if (preload && commonData.current_schema === schemaId) { - store = setSchemaQueriesStore(schemaId, commonData.queries ?? []); + schemasCacheManager.set(schema.oid, store); + if (preload && commonData.current_schema === schema.oid) { + store = setSchemaQueriesStore(schema.oid, commonData.queries ?? []); } else { - void refetchQueriesForSchema(schemaId); + void refetchExplorationsForSchema(schema); } preload = false; } else if (get(store).requestStatus.state === 'failure') { - void refetchQueriesForSchema(schemaId); + void refetchExplorationsForSchema(schema); } return store; } export const queries: Readable> = - derived(currentSchemaId, ($currentSchemaId, set) => { + derived(currentSchema, ($currentSchema, set) => { let unsubscribe: Unsubscriber; - if (!$currentSchemaId) { + if (!$currentSchema) { set({ requestStatus: { state: 'success' }, data: new Map(), }); } else { - const store = getQueriesStoreForSchema($currentSchemaId); + const store = getExplorationsStoreForSchema($currentSchema); unsubscribe = store.subscribe((dbSchemasData) => { set(dbSchemasData); }); @@ -209,99 +204,71 @@ export const queries: Readable> = }; }); -export function createQuery( - newQuery: UnsavedExploration, -): CancellablePromise { - const promise = postAPI( - '/api/db/v0/queries/', - newQuery, - ); - void promise.then((instance) => { - void refetchQueriesForSchema(instance.schema); - return instance; +export function addExploration( + exploration: AddableExploration, +): CancellablePromise { + const promise = api.explorations.add({ exploration_def: exploration }).run(); + void promise.then((savedExploration) => { + void refetchExplorationsForSchema({ + oid: savedExploration.schema_oid, + database: { id: exploration.database_id }, + }); + return savedExploration; }); return promise; } -export function putQuery( - query: SavedExploration, +export function replaceExploration( + exploration: SavedExploration, ): CancellablePromise { - const promise = putAPI( - `/api/db/v0/queries/${query.id}/`, - query, - ); - void promise.then((result) => { - // TODO: Get schemaId as a query property - const schemaId = get(currentSchemaId); - if (schemaId) { - const store = getQueriesStoreForSchema(schemaId); - get(store).data.set(query.id, result); - setSchemaQueriesStore(schemaId, [...get(store).data.values()]); - } + const promise = api.explorations + .replace({ new_exploration: exploration }) + .run(); + + void promise.then((newlySavedExploration) => { + const schemaId = newlySavedExploration.schema_oid; + const store = getExplorationsStoreForSchema({ + oid: schemaId, + database: { id: newlySavedExploration.database_id }, + }); + get(store).data.set(exploration.id, newlySavedExploration); + setSchemaQueriesStore(schemaId, [...get(store).data.values()]); return undefined; }); + return promise; } -export function getQuery( - queryId: SavedExploration['id'], +export function getExploration( + id: SavedExploration['id'], ): CancellablePromise { - // TODO: Get schemaId as a query property - const schemaId = get(currentSchemaId); - let innerRequest: CancellablePromise; - if (schemaId) { - return new CancellablePromise( - (resolve, reject) => { - const store = getQueriesStoreForSchema(schemaId); - const storeSubstance = get(store); - const queryResponse = storeSubstance.data.get(queryId); - if (queryResponse) { - resolve(queryResponse); - return; - } - if (storeSubstance.requestStatus.state !== 'success') { - innerRequest = getAPI( - `/api/db/v0/queries/${queryId}/`, - ); - void innerRequest.then( - (result) => resolve(result), - (reason) => reject(reason), - ); - } else { - reject(new Error('Query not found')); - } - }, - () => { - innerRequest?.cancel(); - }, - ); - } - return new CancellablePromise((resolve) => resolve()); + return api.explorations.get({ exploration_id: id }).run(); } -export function fetchQueryResults( - queryId: number, - params?: { +export function runSavedExploration( + id: number, + params: { limit: number; offset: number; - [SHARED_LINK_UUID_QUERY_PARAM]?: string; }, ): CancellablePromise { - const url = addQueryParamsToUrl( - `/api/db/v0/queries/${queryId}/results/`, - params, - ); - return getAPI(url); + return api.explorations + .run_saved({ + exploration_id: id, + limit: params.limit, + offset: params.offset, + }) + .run(); } -export function deleteQuery(queryId: number): CancellablePromise { - const promise = deleteAPI(`/api/db/v0/queries/${queryId}/`); +export function deleteExploration(id: number): CancellablePromise { + const promise = api.explorations.delete({ exploration_id: id }).run(); void promise.then(() => { - const store = findSchemaStoreForQuery(queryId); + const store = findSchemaStoreForQuery(id); if (store) { store.update((storeData) => { - storeData.data.delete(queryId); + storeData.data.delete(id); return { ...storeData, data: new Map(storeData.data) }; }); } diff --git a/mathesar_ui/src/systems/data-explorer/QueryListEntry.ts b/mathesar_ui/src/systems/data-explorer/QueryListEntry.ts index eb7e13f2bc..30aff750f2 100644 --- a/mathesar_ui/src/systems/data-explorer/QueryListEntry.ts +++ b/mathesar_ui/src/systems/data-explorer/QueryListEntry.ts @@ -1,7 +1,7 @@ -import type { UnsavedExploration } from '@mathesar/api/rpc/explorations'; +import type { MaybeSavedExploration } from '@mathesar/api/rpc/explorations'; export default class QueryListEntry { - queryJSON: UnsavedExploration; + queryJSON: MaybeSavedExploration; isValid: boolean; @@ -9,7 +9,7 @@ export default class QueryListEntry { prev: QueryListEntry | undefined = undefined; - constructor(queryJSON: UnsavedExploration, isValid: boolean) { + constructor(queryJSON: MaybeSavedExploration, isValid: boolean) { this.queryJSON = queryJSON; this.isValid = isValid; } diff --git a/mathesar_ui/src/systems/data-explorer/QueryManager.ts b/mathesar_ui/src/systems/data-explorer/QueryManager.ts index 73c990f0bb..4dcac13942 100644 --- a/mathesar_ui/src/systems/data-explorer/QueryManager.ts +++ b/mathesar_ui/src/systems/data-explorer/QueryManager.ts @@ -3,13 +3,15 @@ import { get, writable } from 'svelte/store'; import { _ } from 'svelte-i18n'; import type { RequestStatus } from '@mathesar/api/rest/utils/requestUtils'; -import type { - ExplorationResult, - SavedExploration, +import { + type ExplorationResult, + type SavedExploration, + explorationIsAddable, + explorationIsSaved, } from '@mathesar/api/rpc/explorations'; import type { AbstractTypesMap } from '@mathesar/stores/abstract-types/types'; import { currentDatabase } from '@mathesar/stores/databases'; -import { createQuery, putQuery } from '@mathesar/stores/queries'; +import { addExploration, replaceExploration } from '@mathesar/stores/queries'; import CacheManager from '@mathesar/utils/CacheManager'; import type { CancellablePromise } from '@mathesar-component-library'; @@ -78,7 +80,11 @@ export default class QueryManager extends QueryRunner { abstractTypeMap, onRunWithObject: (response: ExplorationResult) => { this.checkAndUpdateSummarizationAfterRun( - new QueryModel({ database_id: query.database_id, ...response.query }), + new QueryModel({ + database_id: query.database_id, + schema_oid: query.schema_oid, + ...response.query, + }), ); }, }); @@ -285,7 +291,8 @@ export default class QueryManager extends QueryRunner { * @throws Error if unable to save */ async save(): Promise { - const queryJSON = this.getQueryModel().toJson(); + const maybeSavedExploration = + this.getQueryModel().toMaybeSavedExploration(); this.state.update((_state) => ({ ..._state, saveState: { state: 'processing' }, @@ -293,11 +300,12 @@ export default class QueryManager extends QueryRunner { try { this.querySavePromise?.cancel(); // TODO: Check for latest validation status here - if (queryJSON.id !== undefined) { - // TODO: Figure out a better way to help TS identify this as a saved instance - this.querySavePromise = putQuery(queryJSON as SavedExploration); + if (explorationIsSaved(maybeSavedExploration)) { + this.querySavePromise = replaceExploration(maybeSavedExploration); + } else if (explorationIsAddable(maybeSavedExploration)) { + this.querySavePromise = addExploration(maybeSavedExploration); } else { - this.querySavePromise = createQuery(queryJSON); + throw new Error(get(_)('error_saving_query')); } const result = await this.querySavePromise; this.query.update((qr) => qr.withId(result.id).model); diff --git a/mathesar_ui/src/systems/data-explorer/QueryModel.ts b/mathesar_ui/src/systems/data-explorer/QueryModel.ts index 5dcec3652a..1ba26eaa30 100644 --- a/mathesar_ui/src/systems/data-explorer/QueryModel.ts +++ b/mathesar_ui/src/systems/data-explorer/QueryModel.ts @@ -1,9 +1,9 @@ import type { AnonymousExploration, InitialColumn, + MaybeSavedExploration, QueryInstanceTransformation, SavedExploration, - UnsavedExploration, } from '@mathesar/api/rpc/explorations'; import { assertExhaustive } from '@mathesar-component-library'; @@ -22,7 +22,7 @@ export interface QueryModelUpdateDiff { | 'initialColumnName' | 'transformations' | 'initialColumnsAndTransformations'; - diff: Partial; + diff: Partial; } export type QueryTransformationModel = @@ -61,15 +61,17 @@ function validate( } export default class QueryModel { - readonly database_id: UnsavedExploration['database_id']; + readonly database_id: MaybeSavedExploration['database_id']; - readonly base_table_oid: UnsavedExploration['base_table_oid']; + readonly schema_oid: MaybeSavedExploration['schema_oid']; - readonly id: UnsavedExploration['id']; + readonly base_table_oid: MaybeSavedExploration['base_table_oid']; - readonly name: UnsavedExploration['name']; + readonly id: MaybeSavedExploration['id']; - readonly description: UnsavedExploration['description']; + readonly name: MaybeSavedExploration['name']; + + readonly description: MaybeSavedExploration['description']; readonly initial_columns: InitialColumn[]; @@ -81,8 +83,9 @@ export default class QueryModel { readonly isRunnable: boolean; - constructor(model: UnsavedExploration | QueryModel) { + constructor(model: MaybeSavedExploration | QueryModel) { this.database_id = model.database_id; + this.schema_oid = model.schema_oid; this.base_table_oid = model.base_table_oid; this.id = model.id; this.name = model.name; @@ -108,6 +111,7 @@ export default class QueryModel { withBaseTable(base_table?: number): QueryModelUpdateDiff { const model = new QueryModel({ database_id: this.database_id, + schema_oid: this.schema_oid, base_table_oid: base_table, id: this.id, name: this.name, @@ -235,7 +239,7 @@ export default class QueryModel { model, type: 'transformations', diff: { - transformations: model.toJson().transformations, + transformations: model.toMaybeSavedExploration().transformations, }, }; } @@ -279,7 +283,7 @@ export default class QueryModel { model, type: 'transformations', diff: { - transformations: model.toJson().transformations, + transformations: model.toMaybeSavedExploration().transformations, }, }; } @@ -298,7 +302,7 @@ export default class QueryModel { model, type: 'transformations', diff: { - transformations: model.toJson().transformations, + transformations: model.toMaybeSavedExploration().transformations, }, }; } @@ -382,7 +386,7 @@ export default class QueryModel { type: 'initialColumnsAndTransformations', diff: { initial_columns: initialColumns, - transformations: model.toJson().transformations, + transformations: model.toMaybeSavedExploration().transformations, }, }; } @@ -447,6 +451,7 @@ export default class QueryModel { : this.transformationModels.filter((transform) => transform.isValid()); return { database_id: this.database_id, + schema_oid: this.schema_oid, base_table_oid: this.base_table_oid, initial_columns: this.initial_columns, transformations: transformations.map((entry) => entry.toJson()), @@ -458,9 +463,10 @@ export default class QueryModel { return this.initial_columns.filter((entry) => entry.attnum === id).length; } - toJson(): UnsavedExploration { + toMaybeSavedExploration(): MaybeSavedExploration { return { database_id: this.database_id, + schema_oid: this.schema_oid, id: this.id, name: this.name, description: this.description, diff --git a/mathesar_ui/src/systems/data-explorer/QueryRunner.ts b/mathesar_ui/src/systems/data-explorer/QueryRunner.ts index e115876d59..388bd31e57 100644 --- a/mathesar_ui/src/systems/data-explorer/QueryRunner.ts +++ b/mathesar_ui/src/systems/data-explorer/QueryRunner.ts @@ -13,7 +13,7 @@ import Plane from '@mathesar/components/sheet/selection/Plane'; import Series from '@mathesar/components/sheet/selection/Series'; import SheetSelectionStore from '@mathesar/components/sheet/selection/SheetSelectionStore'; import type { AbstractTypesMap } from '@mathesar/stores/abstract-types/types'; -import { fetchQueryResults } from '@mathesar/stores/queries'; +import { runSavedExploration } from '@mathesar/stores/queries'; import Pagination from '@mathesar/utils/Pagination'; import type { ShareConsumer } from '@mathesar/utils/shares'; import { CancellablePromise, ImmutableMap } from '@mathesar-component-library'; @@ -188,7 +188,7 @@ export default class QueryRunner { }); return undefined; } - this.runPromise = fetchQueryResults(queryModel.id, { + this.runPromise = runSavedExploration(queryModel.id, { ...paginationParams, ...this.shareConsumer?.getQueryParams(), }); diff --git a/mathesar_ui/src/systems/data-explorer/QueryUndoRedoManager.ts b/mathesar_ui/src/systems/data-explorer/QueryUndoRedoManager.ts index 06587809e3..6aa0103bee 100644 --- a/mathesar_ui/src/systems/data-explorer/QueryUndoRedoManager.ts +++ b/mathesar_ui/src/systems/data-explorer/QueryUndoRedoManager.ts @@ -1,4 +1,4 @@ -import type { UnsavedExploration } from '@mathesar/api/rpc/explorations'; +import type { MaybeSavedExploration } from '@mathesar/api/rpc/explorations'; import QueryListEntry from './QueryListEntry'; import QueryModel from './QueryModel'; @@ -10,8 +10,8 @@ export default class QueryUndoRedoManager { if (queryInfo) { const { query, isValid } = queryInfo; const json = JSON.parse( - JSON.stringify(query.toJson()), - ) as UnsavedExploration; + JSON.stringify(query.toMaybeSavedExploration()), + ) as MaybeSavedExploration; this.current = new QueryListEntry(json, isValid); } } @@ -21,8 +21,8 @@ export default class QueryUndoRedoManager { this.current.next.prev = undefined; } const json = JSON.parse( - JSON.stringify(query.toJson()), - ) as UnsavedExploration; + JSON.stringify(query.toMaybeSavedExploration()), + ) as MaybeSavedExploration; const newNode = new QueryListEntry(json, isValid); if (this.current && !this.current.isValid) { newNode.prev = this.current.prev; diff --git a/mathesar_ui/src/systems/data-explorer/exploration-inspector/ExplorationTab.svelte b/mathesar_ui/src/systems/data-explorer/exploration-inspector/ExplorationTab.svelte index bca709cdf0..667ed13964 100644 --- a/mathesar_ui/src/systems/data-explorer/exploration-inspector/ExplorationTab.svelte +++ b/mathesar_ui/src/systems/data-explorer/exploration-inspector/ExplorationTab.svelte @@ -7,7 +7,11 @@ import FormField from '@mathesar/components/FormField.svelte'; import { iconDeleteMajor } from '@mathesar/icons'; import { confirmDelete } from '@mathesar/stores/confirmation'; - import { deleteQuery, putQuery, queries } from '@mathesar/stores/queries'; + import { + deleteExploration, + queries, + replaceExploration, + } from '@mathesar/stores/queries'; import { toast } from '@mathesar/stores/toast'; import { getAvailableName } from '@mathesar/utils/db'; import { @@ -74,7 +78,9 @@ .withName(name) .model.withDescription(description).model; // TODO: Write better utility methods to identify saved instances - await putQuery(updatedQuery.toJson() as SavedExploration); + await replaceExploration( + updatedQuery.toMaybeSavedExploration() as SavedExploration, + ); query.set(updatedQuery); } catch (err) { const message = @@ -89,7 +95,7 @@ void confirmDelete({ identifierType: 'Exploration', onProceed: async () => { - await deleteQuery(queryId); + await deleteExploration(queryId); dispatch('delete'); }, }); diff --git a/mathesar_ui/src/systems/data-explorer/urlSerializationUtils.ts b/mathesar_ui/src/systems/data-explorer/urlSerializationUtils.ts index 6d1c0151ec..7564f05683 100644 --- a/mathesar_ui/src/systems/data-explorer/urlSerializationUtils.ts +++ b/mathesar_ui/src/systems/data-explorer/urlSerializationUtils.ts @@ -1,7 +1,7 @@ import type { Column } from '@mathesar/api/rpc/columns'; import type { + MaybeSavedExploration, QueryInstanceSummarizationTransformation, - UnsavedExploration, } from '@mathesar/api/rpc/explorations'; import type { Table } from '@mathesar/models/Table'; import { getDataExplorerPageUrl } from '@mathesar/routes/urls'; @@ -13,6 +13,7 @@ type BaseTable = Pick; interface TerseSummarization { databaseId: number; + schemaOid: number; baseTable: BaseTable; columns: TerseSummarizedColumn[]; terseGrouping: TerseGrouping; @@ -43,6 +44,7 @@ class Streamline { static terseSummarization(t: TerseSummarization): TerseSummarization { return { databaseId: t.databaseId, + schemaOid: t.schemaOid, baseTable: Streamline.baseTable(t.baseTable), columns: t.columns.map((c) => Streamline.terseSummarizedColumn(c)), terseGrouping: t.terseGrouping, @@ -62,6 +64,7 @@ export function createDataExplorerUrlToExploreATable( const dataExplorerRouteUrl = getDataExplorerPageUrl(databaseId, schemaId); const tableInformationHash = buildTableInformationHash({ databaseId, + schemaOid: schemaId, baseTable, columns: [], terseGrouping: [], @@ -91,7 +94,7 @@ export function constructDataExplorerUrlToSummarizeFromGroup( export function constructQueryModelFromHash( hash: string, -): UnsavedExploration | undefined { +): MaybeSavedExploration | undefined { const terseSummarization = JSON.parse( Url64.decode(hash), ) as Partial; @@ -102,16 +105,23 @@ export function constructQueryModelFromHash( if (!terseSummarization.baseTable) { return undefined; } + if (!terseSummarization.schemaOid) { + return undefined; + } - const { baseTable, databaseId } = terseSummarization; - let initialColumns: UnsavedExploration['initial_columns'] = []; - let transformations: UnsavedExploration['transformations'] = []; + const { baseTable, databaseId, schemaOid } = terseSummarization; + let initialColumns: MaybeSavedExploration['initial_columns'] = []; + let transformations: MaybeSavedExploration['transformations'] = []; if ( !terseSummarization.terseGrouping?.length || !terseSummarization.columns ) { - return { database_id: databaseId, base_table_oid: baseTable.oid }; + return { + database_id: databaseId, + schema_oid: schemaOid, + base_table_oid: baseTable.oid, + }; } const columnMap = new Map( @@ -125,7 +135,11 @@ export function constructQueryModelFromHash( .filter((entry): entry is TerseSummarizedColumn => entry !== undefined); if (groupingColumns.length === 0) { - return { database_id: databaseId, base_table_oid: baseTable.oid }; + return { + database_id: databaseId, + schema_oid: schemaOid, + base_table_oid: baseTable.oid, + }; } const baseGroupingColumn = groupingColumns[0]; @@ -182,6 +196,7 @@ export function constructQueryModelFromHash( return { database_id: databaseId, + schema_oid: schemaOid, base_table_oid: baseTable.oid, initial_columns: initialColumns, transformations, diff --git a/mathesar_ui/src/systems/table-view/table-inspector/table/TableActions.svelte b/mathesar_ui/src/systems/table-view/table-inspector/table/TableActions.svelte index db2cfef1a3..3873c6e059 100644 --- a/mathesar_ui/src/systems/table-view/table-inspector/table/TableActions.svelte +++ b/mathesar_ui/src/systems/table-view/table-inspector/table/TableActions.svelte @@ -37,6 +37,7 @@ table.schema.oid, { databaseId: table.schema.database.id, + schemaOid: table.schema.oid, baseTable: table, columns: $columns, terseGrouping: $grouping.terse(),