dbeaver/pro#8486 feat: add DataReadonlyReason API for data sources (#4354)

* 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>
This commit is contained in:
Sychev Andrey
2026-05-25 12:36:20 +02:00
committed by GitHub
co-authored by Evgenia
parent f7bdd80e9f
commit 225ce91fd7
12 changed files with 47 additions and 10 deletions
@@ -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:*",
@@ -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 && <IconOrImage icon="/icons/lock.png" className="tw:w-2.5 tw:cursor-help" />}
{showLockIcon && <IconOrImage icon="/icons/lock.png" className="tw:w-2.5 tw:cursor-help" />}
<div
className={s(
style,
@@ -25,6 +25,7 @@ export default [
['data_grid_table_context_menu_filter_dialog_title', 'Wert bearbeiten'],
['data_grid_table_index_column_tooltip', 'Wählen ganze Tabelle aus'],
['data_grid_table_readonly_connection_tooltip', 'Schreibgeschützte Verbindung'],
['data_grid_table_readonly_presentation_tooltip', 'Die aktive Darstellung unterstützt keine Datenbearbeitung'],
['data_grid_table_no_key_found_tooltip', 'Kein eindeutiger Schlüssel gefunden. Datenänderung nicht möglich.'],
['data_grid_table_virtual_key_tooltip', 'Virtueller Schlüssel'],
['data_grid_table_key_column_tooltip', 'Schlüsselspalte'],
@@ -31,6 +31,7 @@ export default [
['data_grid_table_context_menu_save_value_error', 'Failed to save value'],
['data_grid_table_index_column_tooltip', 'Select whole table'],
['data_grid_table_readonly_connection_tooltip', 'Read-only connection'],
['data_grid_table_readonly_presentation_tooltip', 'Read-only: Active presentation does not support data editing'],
['data_grid_table_no_key_found_tooltip', 'No unique key was found. Data modification is not possible.'],
['data_grid_table_virtual_key_tooltip', 'Virtual key'],
['data_grid_table_key_column_tooltip', 'Key column'],
@@ -34,6 +34,7 @@ export default [
['data_grid_table_virtual_key_tooltip', 'Clé virtuelle'],
['data_grid_table_key_column_tooltip', 'Colonne clé'],
['data_grid_table_readonly_connection_tooltip', 'Connexion en lecture seule'],
['data_grid_table_readonly_presentation_tooltip', 'La présentation active ne prend pas en charge la modification des données'],
['plugin_data_spreadsheet_new_settings_disable', 'Désactiver la présentation de la table'],
['plugin_data_spreadsheet_new_settings_description_label', 'Show columns description'],
['plugin_data_spreadsheet_new_settings_description_label_description', 'Description will be shown under the column names in the table header'],
@@ -30,6 +30,7 @@ export default [
['data_grid_table_context_menu_save_value_error', 'Failed to save value'],
['data_grid_table_index_column_tooltip', 'Seleziona tutta la tabella'],
['data_grid_table_readonly_connection_tooltip', 'Connessione in sola lettura'],
['data_grid_table_readonly_presentation_tooltip', 'La presentazione attiva non supporta la modifica dei dati'],
['data_grid_table_no_key_found_tooltip', 'Nessuna chiave univoca è stata trovata. La modifica dei dati non è possibile.'],
['data_grid_table_virtual_key_tooltip', 'Chiave virtuale'],
['data_grid_table_key_column_tooltip', 'Colonna chiave'],
@@ -31,6 +31,7 @@ export default [
['data_grid_table_context_menu_save_value_error', 'Не удалось сохранить значение'],
['data_grid_table_index_column_tooltip', 'Выбрать всю таблицу'],
['data_grid_table_readonly_connection_tooltip', 'Подключение только для чтения'],
['data_grid_table_readonly_presentation_tooltip', 'Активное представление не поддерживает редактирование данных'],
['data_grid_table_no_key_found_tooltip', 'Не найден уникальный ключ. Изменение данных невозможно.'],
['data_grid_table_virtual_key_tooltip', 'Виртуальный ключ'],
['data_grid_table_key_column_tooltip', 'Ключевая колонка'],
@@ -31,6 +31,7 @@ export default [
['data_grid_table_context_menu_save_value_error', 'Không thể lưu giá trị'],
['data_grid_table_index_column_tooltip', 'Chọn toàn bộ bảng'],
['data_grid_table_readonly_connection_tooltip', 'Kết nối chỉ đọc'],
['data_grid_table_readonly_presentation_tooltip', 'Trình bày đang dùng không hỗ trợ chỉnh sửa dữ liệu'],
['data_grid_table_no_key_found_tooltip', 'Không tìm thấy khóa duy nhất. Không thể sửa đổi dữ liệu.'],
['data_grid_table_virtual_key_tooltip', 'Khóa ảo'],
['data_grid_table_key_column_tooltip', 'Cột khóa'],
@@ -34,6 +34,7 @@ export default [
['data_grid_table_virtual_key_tooltip', '虚拟键'],
['data_grid_table_key_column_tooltip', '键列'],
['data_grid_table_readonly_connection_tooltip', '只读连接'],
['data_grid_table_readonly_presentation_tooltip', '当前展示形式不支持数据编辑'],
['plugin_data_spreadsheet_new_settings_disable', '禁用表显示'],
['plugin_data_spreadsheet_new_settings_description_label', '显示列描述'],
['plugin_data_spreadsheet_new_settings_description_label_description', '描述将显示在表头的列名下方'],
@@ -31,6 +31,9 @@
{
"path": "../core-cli"
},
{
"path": "../core-connections"
},
{
"path": "../core-data-context"
},
@@ -43,9 +43,10 @@ export interface IRowIdentifierInfo {
identifier: SqlRowIdentifier | null;
}
export abstract class ResultSetDataSource<TOptions extends IDatabaseDataOptions = IDatabaseDataOptions>
extends DatabaseDataSource<TOptions, IDatabaseResultSet>
{
export abstract class ResultSetDataSource<TOptions extends IDatabaseDataOptions = IDatabaseDataOptions> extends DatabaseDataSource<
TOptions,
IDatabaseResultSet
> {
executionContext: IConnectionExecutionContext | null;
totalCountRequestTask: ITask<number> | null;
private keepExecutionContextOnDispose: boolean;
+1
View File
@@ -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:*"