From 225ce91fd7e74e9db798d28aad1db35271fd9d72 Mon Sep 17 00:00:00 2001 From: Sychev Andrey <44414066+SychevAndrey@users.noreply.github.com> Date: Mon, 25 May 2026 12:36:20 +0200 Subject: [PATCH] dbeaver/pro#8486 feat: add DataReadonlyReason API for data sources (#4354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * dbeaver/pro#8486 feat: add DataReadonlyReason API for data sources Previously TableStatusIndicator relied on model.isReadonly() which lost reasons. This caused the "Read-only connection" tooltip to show in grouping panel even on editable connections — the connection wasn't read-only, the presentation was. Each data source now declares its specific reason via getReadonlyReason(). UI maps the typed reason to an accurate message * dbeaver/pro#8486 refactor: remove DataReadonlyReason references and use hasFeature flag --------- Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com> --- .../plugin-data-spreadsheet-new/package.json | 1 + .../TableStatusIndicator.tsx | 38 +++++++++++++++---- .../src/locales/de.ts | 1 + .../src/locales/en.ts | 1 + .../src/locales/fr.ts | 1 + .../src/locales/it.ts | 1 + .../src/locales/ru.ts | 1 + .../src/locales/vi.ts | 1 + .../src/locales/zh.ts | 1 + .../plugin-data-spreadsheet-new/tsconfig.json | 3 ++ .../src/ResultSet/ResultSetDataSource.ts | 7 ++-- webapp/yarn.lock | 1 + 12 files changed, 47 insertions(+), 10 deletions(-) diff --git a/webapp/packages/plugin-data-spreadsheet-new/package.json b/webapp/packages/plugin-data-spreadsheet-new/package.json index bca6cf863a..2b1e55e2cd 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/package.json +++ b/webapp/packages/plugin-data-spreadsheet-new/package.json @@ -25,6 +25,7 @@ "dependencies": { "@cloudbeaver/core-blocks": "workspace:*", "@cloudbeaver/core-browser": "workspace:*", + "@cloudbeaver/core-connections": "workspace:*", "@cloudbeaver/core-data-context": "workspace:*", "@cloudbeaver/core-di": "workspace:*", "@cloudbeaver/core-dialogs": "workspace:*", diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableStatusIndicator.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableStatusIndicator.tsx index e4a5357cb8..9422a9db0b 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableStatusIndicator.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableStatusIndicator.tsx @@ -9,8 +9,9 @@ import { SqlRowIdentifierState, type SqlResultColumn } from '@cloudbeaver/core-s import { observer } from 'mobx-react-lite'; import { useContext } from 'react'; -import { IconOrImage, s, useS, useTranslate } from '@cloudbeaver/core-blocks'; -import { isResultSetDataSource } from '@cloudbeaver/plugin-data-viewer'; +import { IconOrImage, s, useResource, useS, useTranslate } from '@cloudbeaver/core-blocks'; +import { ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections'; +import { DatabaseDataFeature, isResultSetDataSource } from '@cloudbeaver/plugin-data-viewer'; import { DataGridContext } from '../DataGridContext.js'; import { TableDataContext } from '../TableDataContext.js'; @@ -20,16 +21,27 @@ import styles from './TableStatusIndicator.module.css'; export const TableStatusIndicator = observer(function TableStatusIndicator() { const dataGridContext = useContext(DataGridContext); const tableDataContext = useContext(TableDataContext); - const readOnlyConnection = dataGridContext.model.isReadonly(dataGridContext.resultIndex); const translate = useTranslate(); + + const source = dataGridContext.model.source; + const resultSetSource = isResultSetDataSource(source) ? source : null; + + /* We do NOT use `model.isReadonly()` here — + that method aggregates several unrelated reasons + and loses information about WHY editing isn't allowed. */ + const contextInfo = resultSetSource?.executionContext?.context; + const connectionKey = contextInfo ? createConnectionParam(contextInfo.projectId, contextInfo.connectionId) : null; + const connectionInfoLoader = useResource(TableStatusIndicator, ConnectionInfoResource, connectionKey); + const readOnlyConnection = connectionInfoLoader.data?.readOnly ?? false; + + const readOnlyPresentation = source.hasFeature(DatabaseDataFeature.Grouping); + const style = useS(styles); if (!tableDataContext || !dataGridContext) { return null; } - const source = dataGridContext.model.source; - const resultSetSource = isResultSetDataSource(source) ? source : null; const rowIdentifierInfo = resultSetSource?.getRowIdentifierInfo(dataGridContext.resultIndex); const hasRowIdentifier = resultSetSource?.hasElementIdentifier(dataGridContext.resultIndex); @@ -43,7 +55,10 @@ export const TableStatusIndicator = observer(function TableStatusIndicator() { const isPrimaryKey = rowIdentifierInfo?.state === SqlRowIdentifierState.PrimaryKey; const tooltipParts: string[] = []; - if (readOnlyConnection) { + // Presentation-level read-only takes precedence over connection-level read-only. + if (readOnlyPresentation) { + tooltipParts.push(translate('data_grid_table_readonly_presentation_tooltip')); + } else if (readOnlyConnection) { tooltipParts.push(translate('data_grid_table_readonly_connection_tooltip')); } @@ -59,6 +74,15 @@ export const TableStatusIndicator = observer(function TableStatusIndicator() { tooltipParts.push(translate('data_grid_table_virtual_key_tooltip')); } + const showLockIcon = readOnlyConnection || readOnlyPresentation; + + // Hide the entire indicator when there's nothing meaningful to display (Session Manager) + const hasInfo = showLockIcon || !!readOnlyStatus || hasRowIdentifier || isVirtualKey || isPrimaryKey; + + if (!hasInfo) { + return null; + } + const tooltip = tooltipParts.join('\n'); return ( @@ -66,7 +90,7 @@ export const TableStatusIndicator = observer(function TableStatusIndicator() { title={tooltip} className="tw:absolute tw:top-1/2 tw:left-1 tw:-translate-y-1/2 tw:z-1 tw:pointer-events-auto tw:flex tw:items-center tw:gap-1 tw:cursor-help" > - {readOnlyConnection && } + {showLockIcon && }
- extends DatabaseDataSource -{ +export abstract class ResultSetDataSource extends DatabaseDataSource< + TOptions, + IDatabaseResultSet +> { executionContext: IConnectionExecutionContext | null; totalCountRequestTask: ITask | null; private keepExecutionContextOnDispose: boolean; diff --git a/webapp/yarn.lock b/webapp/yarn.lock index b3d07b4a4f..eb6a7b7f12 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -2957,6 +2957,7 @@ __metadata: "@cloudbeaver/core-blocks": "workspace:*" "@cloudbeaver/core-browser": "workspace:*" "@cloudbeaver/core-cli": "workspace:*" + "@cloudbeaver/core-connections": "workspace:*" "@cloudbeaver/core-data-context": "workspace:*" "@cloudbeaver/core-di": "workspace:*" "@cloudbeaver/core-dialogs": "workspace:*"