diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPresentation.tsx b/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPresentation.tsx index f2ba43b907..24e43ad34b 100644 --- a/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPresentation.tsx +++ b/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPresentation.tsx @@ -5,12 +5,12 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ +import { observable } from 'mobx'; import { observer } from 'mobx-react-lite'; import { s, useS, useTranslate } from '@cloudbeaver/core-blocks'; -import { useTabLocalState } from '@cloudbeaver/core-ui'; import { CaptureViewScope } from '@cloudbeaver/core-view'; -import { type DataPresentationComponent, isResultSetDataModel, TableViewerLoader } from '@cloudbeaver/plugin-data-viewer'; +import { DatabaseMetadataAction, type DataPresentationComponent, isResultSetDataModel, TableViewerLoader } from '@cloudbeaver/plugin-data-viewer'; import { DEFAULT_GROUPING_QUERY_OPERATION } from './DEFAULT_GROUPING_QUERY_OPERATION.js'; import styles from './DVResultSetGroupingPresentation.module.css'; @@ -27,13 +27,19 @@ export const DVResultSetGroupingPresentation: DataPresentationComponent = observ if (!isResultSetDataModel(originalModel)) { throw new Error('DVResultSetGroupingPresentation can only be used with ResultSetDataSource'); } - const state = useTabLocalState(() => ({ - presentationId: '', - valuePresentationId: null, - columns: [], - functions: [DEFAULT_GROUPING_QUERY_OPERATION], - showDuplicatesOnly: false, - })); + const metadataAction = originalModel.source.getAction(resultIndex, DatabaseMetadataAction); + + const state = metadataAction.get(`grouping-panel-${originalModel.id}`, () => + observable({ + presentationId: '', + valuePresentationId: null, + columns: [], + functions: [DEFAULT_GROUPING_QUERY_OPERATION], + showDuplicatesOnly: false, + modelId: '', + }), + ); + const style = useS(styles); const translate = useTranslate(); diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/IDVResultSetGroupingPresentationState.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/IDVResultSetGroupingPresentationState.ts index d82ad11182..64da201e06 100644 --- a/webapp/packages/plugin-data-viewer-result-set-grouping/src/IDVResultSetGroupingPresentationState.ts +++ b/webapp/packages/plugin-data-viewer-result-set-grouping/src/IDVResultSetGroupingPresentationState.ts @@ -10,4 +10,5 @@ import type { IGroupingQueryState } from './IGroupingQueryState.js'; export interface IDVResultSetGroupingPresentationState extends IGroupingQueryState { presentationId: string; valuePresentationId: string | null; + modelId: string; } diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDataModel.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDataModel.ts index d2afd05501..455e127836 100644 --- a/webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDataModel.ts +++ b/webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDataModel.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ import { reaction } from 'mobx'; -import { useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import { useObjectRef, useResource } from '@cloudbeaver/core-blocks'; import { ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections'; @@ -24,6 +24,7 @@ import { } from '@cloudbeaver/plugin-data-viewer'; import { GroupingDataSource } from './GroupingDataSource.js'; +import type { IDVResultSetGroupingPresentationState } from './IDVResultSetGroupingPresentationState.js'; import type { IGroupingQueryState } from './IGroupingQueryState.js'; export interface IGroupingDataModel { @@ -33,7 +34,7 @@ export interface IGroupingDataModel { export function useGroupingDataModel( sourceModel: IDatabaseDataModel, sourceResultIndex: number, - state: IGroupingQueryState, + state: IGroupingQueryState & IDVResultSetGroupingPresentationState, ): IGroupingDataModel { const tableViewerStorageService = useService(TableViewerStorageService); const serviceProvider = useService(IServiceProvider); @@ -50,10 +51,22 @@ export function useGroupingDataModel( const model = useObjectRef( () => { + if (tableViewerStorageService.has(state.modelId)) { + const model = tableViewerStorageService.get(state.modelId) as IDatabaseDataModel; + return { + source: model.source, + model, + dispose() { + this.model.dispose(); + tableViewerStorageService.remove(state.modelId); + }, + }; + } const source = new GroupingDataSource(serviceProvider, graphQLService, asyncTaskInfoService); source.setKeepExecutionContextOnDispose(true); const model = tableViewerStorageService.add(new DatabaseDataModel(source)); + state.modelId = model.id; model.setAccess(DatabaseDataAccessMode.Readonly).setCountGain(dataViewerSettingsService.getDefaultRowsCount()).setSlice(0); @@ -70,6 +83,12 @@ export function useGroupingDataModel( ['dispose'], ); + const prevStateRef = useRef({ + columns: state.columns, + functions: state.functions, + sourceResultId: sourceModel.source.getResult(sourceResultIndex)?.id, + }); + useEffect(() => { sourceModel.onDispose.addHandler(model.dispose); return () => { @@ -90,6 +109,14 @@ export function useGroupingDataModel( }; }, async ({ columns, functions, sourceResultId }) => { + const prevState = prevStateRef.current; + + if (columns == prevState.columns && functions == prevState.functions && sourceResultId == prevState.sourceResultId) { + return; + } + + prevStateRef.current = { columns, functions, sourceResultId }; + if (columns.length !== 0 && functions.length !== 0 && sourceResultId) { const executionContext = sourceModel.source.executionContext; model.source.setExecutionContext(executionContext).setSupportedDataFormats(connectionInfo?.supportedDataFormats ?? []); @@ -129,7 +156,5 @@ export function useGroupingDataModel( return sub; }, [state, sourceModel, sourceResultIndex]); - useEffect(() => model.dispose, []); - return model; }