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 &&