Merge branch 'devel' of https://github.com/dbeaver/cloudbeaver into devel

This commit is contained in:
Wroud
2022-06-07 23:12:09 +03:00
18 changed files with 233 additions and 218 deletions
@@ -1,5 +1,6 @@
source.. = src/
output.. = target/classes/
bin.includes = .,\
META-INF/,\
plugin.xml
META-INF/,\
plugin.xml,\
OSGI-INF/
@@ -38,6 +38,7 @@ mutation getSqlExecuteTaskResults(
rows
singleEntity
hasMoreData
hasRowIdentifier
}
}
}
@@ -24,6 +24,7 @@ mutation updateResultsDataBatch(
rows
singleEntity
hasMoreData
hasRowIdentifier
}
}
}
+5 -2
View File
@@ -1308,6 +1308,7 @@ export interface SqlResultRow {
export interface SqlResultSet {
columns?: Maybe<Array<Maybe<SqlResultColumn>>>;
hasMoreData: Scalars['Boolean'];
hasRowIdentifier: Scalars['Boolean'];
id: Scalars['ID'];
rows?: Maybe<Array<Maybe<Array<Maybe<Scalars['Object']>>>>>;
singleEntity: Scalars['Boolean'];
@@ -2095,7 +2096,7 @@ export type GetSqlExecuteTaskResultsMutationVariables = Exact<{
}>;
export type GetSqlExecuteTaskResultsMutation = { result: { duration: number; statusMessage?: string; filterText?: string; results: Array<{ title?: string; updateRowCount?: number; sourceQuery?: string; dataFormat?: ResultDataFormat; resultSet?: { id: string; rows?: Array<Array<any>>; singleEntity: boolean; hasMoreData: boolean; columns?: Array<{ dataKind?: string; entityName?: string; fullTypeName?: string; icon?: string; label?: string; maxLength?: number; name?: string; position: number; precision?: number; required: boolean; readOnly: boolean; readOnlyStatus?: string; scale?: number; typeName?: string; supportedOperations: Array<{ id: string; expression: string; argumentCount?: number }> }> } }> } };
export type GetSqlExecuteTaskResultsMutation = { result: { duration: number; statusMessage?: string; filterText?: string; results: Array<{ title?: string; updateRowCount?: number; sourceQuery?: string; dataFormat?: ResultDataFormat; resultSet?: { id: string; rows?: Array<Array<any>>; singleEntity: boolean; hasMoreData: boolean; hasRowIdentifier: boolean; columns?: Array<{ dataKind?: string; entityName?: string; fullTypeName?: string; icon?: string; label?: string; maxLength?: number; name?: string; position: number; precision?: number; required: boolean; readOnly: boolean; readOnlyStatus?: string; scale?: number; typeName?: string; supportedOperations: Array<{ id: string; expression: string; argumentCount?: number }> }> } }> } };
export type GetSqlExecutionPlanResultMutationVariables = Exact<{
taskId: Scalars['ID'];
@@ -2114,7 +2115,7 @@ export type UpdateResultsDataBatchMutationVariables = Exact<{
}>;
export type UpdateResultsDataBatchMutation = { result: { duration: number; filterText?: string; results: Array<{ updateRowCount?: number; resultSet?: { id: string; rows?: Array<Array<any>>; singleEntity: boolean; hasMoreData: boolean } }> } };
export type UpdateResultsDataBatchMutation = { result: { duration: number; filterText?: string; results: Array<{ updateRowCount?: number; resultSet?: { id: string; rows?: Array<Array<any>>; singleEntity: boolean; hasMoreData: boolean; hasRowIdentifier: boolean } }> } };
export type UpdateResultsDataBatchScriptMutationVariables = Exact<{
connectionId: Scalars['ID'];
@@ -3383,6 +3384,7 @@ export const GetSqlExecuteTaskResultsDocument = `
rows
singleEntity
hasMoreData
hasRowIdentifier
}
}
}
@@ -3434,6 +3436,7 @@ export const UpdateResultsDataBatchDocument = `
rows
singleEntity
hasMoreData
hasRowIdentifier
}
}
}
@@ -8,7 +8,7 @@
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { ResultSetDataKeysUtils } from '@cloudbeaver/plugin-data-viewer';
import { ResultSetDataContentAction, ResultSetDataKeysUtils } from '@cloudbeaver/plugin-data-viewer';
import { DataGridContextMenuService } from './DataGridContextMenuService';
@@ -37,21 +37,26 @@ export class DataGridContextMenuSaveContentService {
return context.contextType === DataGridContextMenuService.cellContext;
},
onClick: async context => {
const content = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataContentAction);
try {
await context.data.model.source.dataManager.downloadFileData(context.data.key, context.data.resultIndex);
await content.downloadFileData(context.data.key);
} catch (exception: any) {
this.notificationService.logException(exception, 'data_grid_table_context_menu_save_value_error');
}
},
isHidden: context => !context.data.model.source.dataManager.isContent(
context.data.key,
context.data.resultIndex
),
isDisabled: context => context.data.model.isLoading() || (
!!context.data.model.source.dataManager.activeElement && ResultSetDataKeysUtils.isElementsKeyEqual(
context.data.key, context.data.model.source.dataManager.activeElement
)
),
isHidden: context => {
const content = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataContentAction);
return !content.isDownloadable(context.data.key);
},
isDisabled: context => {
const content = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataContentAction);
return context.data.model.isLoading() || (
!!content.activeElement && ResultSetDataKeysUtils.isElementsKeyEqual(
context.data.key, content.activeElement
)
);
},
}
);
}
@@ -8,7 +8,6 @@
import { computed, makeObservable, observable } from 'mobx';
import type { QuotasService } from '@cloudbeaver/core-app';
import type { ConnectionExecutionContextService, IConnectionExecutionContext, IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
import type { IServiceInjector } from '@cloudbeaver/core-di';
import type { ITask } from '@cloudbeaver/core-executor';
@@ -16,9 +15,7 @@ import { AsyncTaskInfoService, GraphQLService, ResultDataFormat, SqlExecuteInfo,
import { DocumentEditAction } from './DatabaseDataModel/Actions/Document/DocumentEditAction';
import { ResultSetEditAction } from './DatabaseDataModel/Actions/ResultSet/ResultSetEditAction';
import { DatabaseDataManager } from './DatabaseDataModel/DatabaseDataManager';
import { DatabaseDataSource } from './DatabaseDataModel/DatabaseDataSource';
import type { IDatabaseDataManager } from './DatabaseDataModel/IDatabaseDataManager';
import type { IDatabaseDataOptions } from './DatabaseDataModel/IDatabaseDataOptions';
import type { IDatabaseResultSet } from './DatabaseDataModel/IDatabaseResultSet';
@@ -28,7 +25,6 @@ export interface IDataContainerOptions extends IDatabaseDataOptions {
export class ContainerDataSource extends DatabaseDataSource<IDataContainerOptions, IDatabaseResultSet> {
currentTask: ITask<SqlExecuteInfo> | null;
dataManager: IDatabaseDataManager;
get canCancel(): boolean {
return this.currentTask?.cancellable || false;
@@ -39,11 +35,9 @@ export class ContainerDataSource extends DatabaseDataSource<IDataContainerOption
private readonly graphQLService: GraphQLService,
private readonly asyncTaskInfoService: AsyncTaskInfoService,
private readonly connectionExecutionContextService: ConnectionExecutionContextService,
private readonly quotasService: QuotasService,
) {
super(serviceInjector);
this.dataManager = new DatabaseDataManager(this.graphQLService, this.quotasService, this);
this.currentTask = null;
this.executionContext = null;
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { EObjectFeature, NavNode, QuotasService } from '@cloudbeaver/core-app';
import { EObjectFeature, NavNode } from '@cloudbeaver/core-app';
import { ConnectionExecutionContextService, Connection } from '@cloudbeaver/core-connections';
import { App, injectable } from '@cloudbeaver/core-di';
import { AsyncTaskInfoService, GraphQLService } from '@cloudbeaver/core-sdk';
@@ -29,7 +29,6 @@ export class DataViewerTableService {
private readonly asyncTaskInfoService: AsyncTaskInfoService,
private readonly connectionExecutionContextService: ConnectionExecutionContextService,
private readonly dataViewerService: DataViewerService,
private readonly quotasService: QuotasService
) { }
has(tableId: string): boolean {
@@ -58,7 +57,6 @@ export class DataViewerTableService {
this.graphQLService,
this.asyncTaskInfoService,
this.connectionExecutionContextService,
this.quotasService
);
source
@@ -0,0 +1,21 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { IResultSetContentValue } from './IResultSetContentValue';
import type { IResultSetElementKey } from './IResultSetDataKey';
export interface IResultSetDataContentAction {
activeElement: IResultSetElementKey | null;
isContentTruncated: (content: IResultSetContentValue) => boolean;
isDownloadable: (element: IResultSetElementKey) => boolean;
getFileDataUrl: (element: IResultSetElementKey) => Promise<string>;
resolveFileDataUrl: (element: IResultSetElementKey) => Promise<string>;
retrieveFileDataUrlFromCache: (element: IResultSetElementKey) => string | undefined;
downloadFileData: (element: IResultSetElementKey) => Promise<void>;
clearCache: () => void;
}
@@ -0,0 +1,153 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { makeObservable, observable } from 'mobx';
import { QuotasService } from '@cloudbeaver/core-app';
import { GraphQLService, ResultDataFormat } from '@cloudbeaver/core-sdk';
import { download, GlobalConstants } from '@cloudbeaver/core-utils';
import { DatabaseDataAction } from '../../DatabaseDataAction';
import type { IDatabaseDataSource } from '../../IDatabaseDataSource';
import type { IDatabaseResultSet } from '../../IDatabaseResultSet';
import { databaseDataAction } from '../DatabaseDataActionDecorator';
import type { IResultSetContentValue } from './IResultSetContentValue';
import type { IResultSetDataContentAction } from './IResultSetDataContentAction';
import type { IResultSetElementKey } from './IResultSetDataKey';
import { isResultSetContentValue } from './isResultSetContentValue';
import { ResultSetDataAction } from './ResultSetDataAction';
import { ResultSetDataKeysUtils } from './ResultSetDataKeysUtils';
import type { IResultSetValue } from './ResultSetFormatAction';
import { ResultSetViewAction } from './ResultSetViewAction';
const RESULT_VALUE_PATH = 'sql-result-value';
@databaseDataAction()
export class ResultSetDataContentAction extends DatabaseDataAction<any, IDatabaseResultSet>
implements IResultSetDataContentAction {
static dataFormat = [ResultDataFormat.Resultset];
private readonly view: ResultSetViewAction;
private readonly data: ResultSetDataAction;
private readonly graphQLService: GraphQLService;
private readonly quotasService: QuotasService;
private readonly cache: Map<string, string>;
activeElement: IResultSetElementKey | null;
constructor(
source: IDatabaseDataSource<any, IDatabaseResultSet>,
result: IDatabaseResultSet,
view: ResultSetViewAction,
data: ResultSetDataAction,
graphQLService: GraphQLService,
quotasService: QuotasService,
) {
super(source, result);
this.view = view;
this.data = data;
this.graphQLService = graphQLService;
this.quotasService = quotasService;
this.cache = new Map();
this.activeElement = null;
makeObservable<this, 'cache'>(this, {
cache: observable,
activeElement: observable.ref,
});
}
isContentTruncated(content: IResultSetContentValue) {
return (content.contentLength ?? 0) > this.quotasService.getQuota('sqlBinaryPreviewMaxLength');
}
isDownloadable(element: IResultSetElementKey) {
const cellValue = this.view.getCellValue(element);
return !!this.result.data?.hasRowIdentifier && isResultSetContentValue(cellValue);
}
async getFileDataUrl(element: IResultSetElementKey) {
const column = this.data.getColumn(element.column);
const row = this.data.getRowValue(element.row);
if (!row || !column) {
throw new Error('Failed to get value metadata information');
}
const url = await this.source.runTask(
async () => {
try {
this.activeElement = element;
const fileName = await this.loadFileName(this.result, column.position, row);
return this.generateFileDataUrl(fileName);
} finally {
this.activeElement = null;
}
}
);
return url;
}
async resolveFileDataUrl(element: IResultSetElementKey) {
const cache = this.retrieveFileDataUrlFromCache(element);
if (cache) {
return cache;
}
const url = await this.getFileDataUrl(element);
this.cache.set(this.getHash(element), url);
return url;
}
retrieveFileDataUrlFromCache(element: IResultSetElementKey) {
const hash = this.getHash(element);
return this.cache.get(hash);
}
async downloadFileData(element: IResultSetElementKey) {
const url = await this.getFileDataUrl(element);
download(url);
}
clearCache() {
this.cache.clear();
}
private generateFileDataUrl(fileName: string) {
return `${GlobalConstants.serviceURI}/${RESULT_VALUE_PATH}/${fileName}`;
}
private getHash(element: IResultSetElementKey) {
return ResultSetDataKeysUtils.serializeElementKey(element);
}
private async loadFileName(result: IDatabaseResultSet, columnIndex: number, row: IResultSetValue[]) {
if (!result.id) {
throw new Error("Result's id must be provided");
}
const response = await this.graphQLService.sdk.getResultsetDataURL({
resultsId: result.id,
connectionId: result.connectionId,
contextId: result.contextId,
lobColumnIndex: columnIndex,
row: {
data: row,
},
});
return response.url;
}
}
@@ -1,134 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { makeObservable, observable } from 'mobx';
import type { QuotasService } from '@cloudbeaver/core-app';
import type { GraphQLService } from '@cloudbeaver/core-sdk';
import { download, GlobalConstants } from '@cloudbeaver/core-utils';
import type { IResultSetContentValue } from './Actions/ResultSet/IResultSetContentValue';
import type { IResultSetElementKey } from './Actions/ResultSet/IResultSetDataKey';
import { isResultSetContentValue } from './Actions/ResultSet/isResultSetContentValue';
import { ResultSetDataAction } from './Actions/ResultSet/ResultSetDataAction';
import { ResultSetDataKeysUtils } from './Actions/ResultSet/ResultSetDataKeysUtils';
import type { IResultSetValue } from './Actions/ResultSet/ResultSetFormatAction';
import { ResultSetViewAction } from './Actions/ResultSet/ResultSetViewAction';
import type { IDatabaseDataManager } from './IDatabaseDataManager';
import type { IDatabaseDataSource } from './IDatabaseDataSource';
import type { IDatabaseResultSet } from './IDatabaseResultSet';
const RESULT_VALUE_PATH = 'sql-result-value';
export class DatabaseDataManager implements IDatabaseDataManager {
private readonly cache: Map<string, string>;
activeElement: IResultSetElementKey | null;
constructor(
private readonly graphQLService: GraphQLService,
private readonly quotasService: QuotasService,
private readonly source: IDatabaseDataSource<any, any>
) {
this.cache = new Map();
this.activeElement = null;
makeObservable<this, 'cache'>(this, {
cache: observable,
activeElement: observable.ref,
});
}
isContent(element: IResultSetElementKey, resultIndex: number) {
const view = this.source.getAction(resultIndex, ResultSetViewAction);
const cellValue = view.getCellValue(element);
return isResultSetContentValue(cellValue);
}
isContentTruncated(content: IResultSetContentValue) {
return (content.contentLength ?? 0) > this.quotasService.getQuota('sqlBinaryPreviewMaxLength');
}
async getFileDataUrl(element: IResultSetElementKey, resultIndex: number) {
const result = this.source.getResult(resultIndex);
const data = this.source.getAction(resultIndex, ResultSetDataAction);
const column = data.getColumn(element.column);
const row = data.getRowValue(element.row);
if (!result || !row || !column) {
throw new Error('Failed to get value metadata information');
}
const url = await this.source.runTask(
async () => {
try {
this.activeElement = element;
const fileName = await this.loadFileName(result, column.position, row);
return this.generateFileDataUrl(fileName);
} finally {
this.activeElement = null;
}
}
);
return url;
}
async resolveFileDataUrl(element: IResultSetElementKey, resultIndex: number) {
const cache = this.retrieveFileDataUrlFromCache(element, resultIndex);
if (cache) {
return cache;
}
const url = await this.getFileDataUrl(element, resultIndex);
this.cache.set(this.getHash(element, resultIndex), url);
return url;
}
retrieveFileDataUrlFromCache(element: IResultSetElementKey, resultIndex: number) {
const hash = this.getHash(element, resultIndex);
return this.cache.get(hash);
}
async downloadFileData(element: IResultSetElementKey, resultIndex: number) {
const url = await this.getFileDataUrl(element, resultIndex);
download(url);
}
clearCache() {
this.cache.clear();
}
private generateFileDataUrl(fileName: string) {
return `${GlobalConstants.serviceURI}/${RESULT_VALUE_PATH}/${fileName}`;
}
private getHash(element: IResultSetElementKey, resultIndex: number) {
return `${ResultSetDataKeysUtils.serializeElementKey(element)}_${resultIndex}`;
}
private async loadFileName(result: IDatabaseResultSet, columnIndex: number, row: IResultSetValue[]) {
if (!result.id) {
throw new Error("Result's id must be provided");
}
const response = await this.graphQLService.sdk.getResultsetDataURL({
resultsId: result.id,
connectionId: result.connectionId,
contextId: result.contextId,
lobColumnIndex: columnIndex,
row: {
data: row,
},
});
return response.url;
}
}
@@ -15,13 +15,11 @@ import { ResultDataFormat } from '@cloudbeaver/core-sdk';
import { DatabaseDataActions } from './DatabaseDataActions';
import type { IDatabaseDataAction, IDatabaseDataActionClass, IDatabaseDataActionInterface } from './IDatabaseDataAction';
import type { IDatabaseDataActions } from './IDatabaseDataActions';
import type { IDatabaseDataManager } from './IDatabaseDataManager';
import type { IDatabaseDataResult } from './IDatabaseDataResult';
import { DatabaseDataAccessMode, IDatabaseDataSource, IRequestInfo } from './IDatabaseDataSource';
export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseDataResult>
implements IDatabaseDataSource<TOptions, TResult> {
abstract readonly dataManager: IDatabaseDataManager;
access: DatabaseDataAccessMode;
dataFormat: ResultDataFormat;
supportedDataFormats: ResultDataFormat[];
@@ -176,7 +174,6 @@ implements IDatabaseDataSource<TOptions, TResult> {
setResults(results: TResult[]): this {
this.actions.updateResults(results);
this.results = results;
this.dataManager.clearCache();
return this;
}
@@ -1,21 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { IResultSetContentValue } from './Actions/ResultSet/IResultSetContentValue';
import type { IResultSetElementKey } from './Actions/ResultSet/IResultSetDataKey';
export interface IDatabaseDataManager {
activeElement: IResultSetElementKey | null;
isContent: (element: IResultSetElementKey, resultIndex: number) => boolean;
isContentTruncated: (content: IResultSetContentValue) => boolean;
getFileDataUrl: (element: IResultSetElementKey, resultIndex: number) => Promise<string>;
resolveFileDataUrl: (element: IResultSetElementKey, resultIndex: number) => Promise<string>;
retrieveFileDataUrlFromCache: (element: IResultSetElementKey, resultIndex: number) => string | undefined;
downloadFileData: (element: IResultSetElementKey, resultIndex: number) => Promise<void>;
clearCache: () => void;
}
@@ -10,10 +10,8 @@ import type { IConnectionExecutionContext } from '@cloudbeaver/core-connections'
import type { IServiceInjector } from '@cloudbeaver/core-di';
import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
import type { DatabaseDataManager } from './DatabaseDataManager';
import type { IDatabaseDataAction, IDatabaseDataActionClass, IDatabaseDataActionInterface } from './IDatabaseDataAction';
import type { IDatabaseDataActions } from './IDatabaseDataActions';
import type { IDatabaseDataManager } from './IDatabaseDataManager';
import type { IDatabaseDataResult } from './IDatabaseDataResult';
export interface IRequestInfo {
@@ -29,7 +27,6 @@ export enum DatabaseDataAccessMode {
}
export interface IDatabaseDataSource<TOptions, TResult extends IDatabaseDataResult = IDatabaseDataResult> {
readonly dataManager: IDatabaseDataManager;
readonly access: DatabaseDataAccessMode;
readonly dataFormat: ResultDataFormat;
readonly supportedDataFormats: ResultDataFormat[];
@@ -21,6 +21,7 @@ import { bytesToSize, download, getMIME, isImageFormat, isValidUrl } from '@clou
import type { IResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetContentValue';
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
import { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction';
import { ResultSetDataKeysUtils } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataKeysUtils';
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetViewAction';
@@ -112,6 +113,8 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
const quotasService = useService(QuotasService);
const style = useStyles(styles);
const content = model.source.getAction(resultIndex, ResultSetDataContentAction);
const state = useObservableRef(() => ({
get selectedCell() {
const selection = this.model.source.getAction(this.resultIndex, ResultSetSelectAction);
@@ -139,17 +142,17 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
return '';
},
get savedSrc() {
return this.model.source.dataManager.retrieveFileDataUrlFromCache(this.selectedCell, this.resultIndex);
},
get content() {
return this.model.source.dataManager.isContent(this.selectedCell, this.resultIndex);
return content.retrieveFileDataUrlFromCache(this.selectedCell);
},
get canSave() {
return this.content || !!this.src;
if (this.truncated) {
return content.isDownloadable(this.selectedCell);
}
return !!this.src;
},
get truncated() {
return isResultSetContentValue(this.cellValue)
&& this.model.source.dataManager.isContentTruncated(this.cellValue);
return isResultSetContentValue(this.cellValue) && content.isContentTruncated(this.cellValue);
},
stretch: false,
toggleStretch() {
@@ -157,10 +160,10 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
},
async save() {
try {
if (!this.content) {
download(this.src, '', true);
if (this.truncated) {
await content.downloadFileData(this.selectedCell);
} else {
await this.model.source.dataManager.downloadFileData(this.selectedCell, this.resultIndex);
download(this.src, '', true);
}
} catch (exception: any) {
this.notificationService.logException(exception, 'data_viewer_presentation_value_content_download_error');
@@ -187,7 +190,7 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
const load = async () => {
try {
await model.source.dataManager.resolveFileDataUrl(state.selectedCell, resultIndex);
await content.resolveFileDataUrl(state.selectedCell);
} catch (exception: any) {
notificationService.logException(exception, 'data_viewer_presentation_value_content_download_error');
}
@@ -196,14 +199,16 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
return styled(style)(
<container>
<QuotaPlaceholder limit={limit} size={valueSize}>
<Button
disabled={loading}
loading={!!model.source.dataManager.activeElement && ResultSetDataKeysUtils.isElementsKeyEqual(
model.source.dataManager.activeElement, state.selectedCell)}
onClick={load}
>
{translate('ui_download')}
</Button>
{content.isDownloadable(state.selectedCell) && (
<Button
disabled={loading}
loading={!!content.activeElement && ResultSetDataKeysUtils.isElementsKeyEqual(
content.activeElement, state.selectedCell)}
onClick={load}
>
{translate('ui_download')}
</Button>
)}
</QuotaPlaceholder>
<Tools loading={loading} onSave={save} />
</container>
@@ -22,6 +22,7 @@ import { CodeEditorLoader } from '@cloudbeaver/plugin-codemirror';
import type { IResultSetElementKey } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetDataKey';
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
import { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction';
import { ResultSetEditAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetEditAction';
import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
@@ -81,6 +82,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
const quotasService = useService(QuotasService);
const textValuePresentationService = useService(TextValuePresentationService);
const style = useStyles(styles, BASE_CONTAINERS_STYLES, UNDERLINE_TAB_STYLES, VALUE_PANEL_TOOLS_STYLES);
const state = useObservableRef(() => ({
currentContentType: 'text/plain',
lastContentType: 'text/plain',
@@ -99,6 +101,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
const selection = model.source.getAction(resultIndex, ResultSetSelectAction);
const editor = model.source.getAction(resultIndex, ResultSetEditAction);
const content = model.source.getAction(resultIndex, ResultSetDataContentAction);
const focusCell = selection.getFocusedElement();
@@ -123,7 +126,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
if (isResultSetContentValue(value)) {
valueTruncated = model.source.dataManager.isContentTruncated(value);
valueTruncated = content.isContentTruncated(value);
if (valueTruncated) {
limit = bytesToSize(quotasService.getQuota('sqlBinaryPreviewMaxLength'));
@@ -162,7 +165,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
}
try {
await model.source.dataManager.downloadFileData(firstSelectedCell, resultIndex);
await content.downloadFileData(firstSelectedCell);
} catch (exception) {
notificationService.logException(exception as any, 'data_viewer_presentation_value_content_download_error');
}
@@ -170,7 +173,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
const useCodeEditor = state.currentContentType !== 'text/plain';
const autoFormat = firstSelectedCell && !editor.isElementEdited(firstSelectedCell);
const canSave = firstSelectedCell && model.source.dataManager.isContent(firstSelectedCell, resultIndex);
const canSave = !!firstSelectedCell && content.isDownloadable(firstSelectedCell);
return styled(style)(
<container>
@@ -16,6 +16,7 @@ export * from './DatabaseDataModel/Actions/ResultSet/ResultSetEditAction';
export * from './DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
export * from './DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
export * from './DatabaseDataModel/Actions/ResultSet/ResultSetViewAction';
export * from './DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction';
export * from './DatabaseDataModel/Actions/DatabaseDataActionDecorator';
export * from './DatabaseDataModel/Actions/DatabaseEditAction';
export * from './DatabaseDataModel/Actions/DatabaseSelectAction';
@@ -38,8 +39,6 @@ export * from './DatabaseDataModel/IDatabaseDataSource';
export * from './DatabaseDataModel/IDatabaseResultSet';
export * from './DatabaseDataModel/Order';
export * from './DataViewerService';
export * from './DatabaseDataModel/DatabaseDataManager';
export * from './DatabaseDataModel/IDatabaseDataManager';
// All Services and Components that is provided by this plugin should be exported here
export * from './TableViewer/TableViewerStorageService';
@@ -8,12 +8,11 @@
import { observable, makeObservable } from 'mobx';
import type { QuotasService } from '@cloudbeaver/core-app';
import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
import type { IServiceInjector } from '@cloudbeaver/core-di';
import type { ITask } from '@cloudbeaver/core-executor';
import { AsyncTaskInfoService, GraphQLService, ResultDataFormat, SqlExecuteInfo, SqlQueryResults, UpdateResultsDataBatchMutationVariables } from '@cloudbeaver/core-sdk';
import { DatabaseDataManager, DatabaseDataSource, DocumentEditAction, IDatabaseDataManager, IDatabaseDataOptions, IDatabaseResultSet, IRequestInfo, ResultSetEditAction } from '@cloudbeaver/plugin-data-viewer';
import { DatabaseDataSource, DocumentEditAction, IDatabaseDataOptions, IDatabaseResultSet, IRequestInfo, ResultSetEditAction } from '@cloudbeaver/plugin-data-viewer';
export interface IDataQueryOptions extends IDatabaseDataOptions {
query: string;
@@ -24,7 +23,6 @@ export interface IQueryRequestInfo extends IRequestInfo {
}
export class QueryDataSource extends DatabaseDataSource<IDataQueryOptions, IDatabaseResultSet> {
readonly dataManager: IDatabaseDataManager;
currentTask: ITask<SqlExecuteInfo> | null;
requestInfo: IQueryRequestInfo;
@@ -36,11 +34,9 @@ export class QueryDataSource extends DatabaseDataSource<IDataQueryOptions, IData
readonly serviceInjector: IServiceInjector,
private readonly graphQLService: GraphQLService,
private readonly asyncTaskInfoService: AsyncTaskInfoService,
private readonly quotasService: QuotasService
) {
super(serviceInjector);
this.dataManager = new DatabaseDataManager(this.graphQLService, this.quotasService, this);
this.currentTask = null;
this.requestInfo = {
requestDuration: 0,
@@ -8,7 +8,6 @@
import { makeObservable, observable } from 'mobx';
import { QuotasService } from '@cloudbeaver/core-app';
import { ConnectionExecutionContextService, ConnectionInfoResource } from '@cloudbeaver/core-connections';
import { App, injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
@@ -50,7 +49,6 @@ export class SqlQueryService {
private readonly asyncTaskInfoService: AsyncTaskInfoService,
private readonly dataViewerDataChangeConfirmationService: DataViewerDataChangeConfirmationService,
private readonly dataViewerService: DataViewerService,
private readonly quotasService: QuotasService
) {
this.statisticsMap = new Map();
@@ -88,7 +86,6 @@ export class SqlQueryService {
this.app.getServiceInjector(),
this.graphQLService,
this.asyncTaskInfoService,
this.quotasService
);
model = this.tableViewerStorageService.add(new DatabaseDataModel(source));
this.dataViewerDataChangeConfirmationService.trackTableDataUpdate(model.id);
@@ -183,7 +180,6 @@ export class SqlQueryService {
this.app.getServiceInjector(),
this.graphQLService,
this.asyncTaskInfoService,
this.quotasService
);
model = this.tableViewerStorageService.add(new DatabaseDataModel(source));
this.dataViewerDataChangeConfirmationService.trackTableDataUpdate(model.id);