From 7ed099b71bc1d10007dca91f279d873e5cd95b07 Mon Sep 17 00:00:00 2001 From: Wroud Date: Mon, 18 May 2020 17:23:44 +0300 Subject: [PATCH] fix(data-export-plugin): add menu item and tooltip CB-114 --- .../ConnectionDialogsService.ts | 2 +- .../shared/NodesManager/NodeManagerUtils.ts | 9 +++ .../Menu/ContextMenu/IContextMenuItem.ts | 2 +- .../src/DataExportMenuService.ts | 29 ++++++++- .../src/Dialog/DataExportController.ts | 45 +++++++++---- .../src/Dialog/DataExportDialog.tsx | 2 + .../src/Dialog/ProcessorConfigureDialog.tsx | 64 +++++++++++++------ .../src/Dialog/ProcessorSelectDialog.tsx | 2 +- .../ExportNotification/ExportNotification.tsx | 2 +- 9 files changed, 119 insertions(+), 38 deletions(-) diff --git a/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionDialogsService.ts b/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionDialogsService.ts index 738b97f439f..aa760a237fe 100644 --- a/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionDialogsService.ts +++ b/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionDialogsService.ts @@ -56,7 +56,7 @@ export class ConnectionDialogsService { id: 'closeConnection', isPresent(context) { return context.contextType === NavigationTreeContextMenuService.nodeContextType - && Boolean((context.data as NodeWithParent)?.object?.features?.includes(EObjectFeature.dataSource)); + && !!context.data.object?.features?.includes(EObjectFeature.dataSource); }, title: 'Disconnect', onClick: (context: IMenuContext) => { diff --git a/webapp/packages/core/src/app/shared/NodesManager/NodeManagerUtils.ts b/webapp/packages/core/src/app/shared/NodesManager/NodeManagerUtils.ts index 1546f262989..5f142cca548 100644 --- a/webapp/packages/core/src/app/shared/NodesManager/NodeManagerUtils.ts +++ b/webapp/packages/core/src/app/shared/NodesManager/NodeManagerUtils.ts @@ -17,4 +17,13 @@ export class NodeManagerUtils { return connectionNodeId.replace('database://', ''); } + static nodeIdToConnectionId(nodeId: string): string { + const matches = nodeId.match(/database:\/\/(.*?)(|\/.*)$/); + if (!matches) { + throw new Error('Not database object'); + } + + return matches[1]; + } + } diff --git a/webapp/packages/core/src/dialogs/Menu/ContextMenu/IContextMenuItem.ts b/webapp/packages/core/src/dialogs/Menu/ContextMenu/IContextMenuItem.ts index e2dddb9e9a5..7748bf62a95 100644 --- a/webapp/packages/core/src/dialogs/Menu/ContextMenu/IContextMenuItem.ts +++ b/webapp/packages/core/src/dialogs/Menu/ContextMenu/IContextMenuItem.ts @@ -15,7 +15,7 @@ import { IMenuContext } from './IMenuContext'; export interface IContextMenuItem extends IMenuItemOptions { onClick?: (context: IMenuContext) => void; // if isPresent is false menu item will not be included in resulting context menu - isPresent: (context: IMenuContext) => boolean; + isPresent: (context: IMenuContext) => boolean; isDisabled?: (context: IMenuContext) => boolean; // When the item is present in menu it can be hidden based on certain conditions isHidden?: (context: IMenuContext) => boolean; diff --git a/webapp/packages/data-export-plugin/src/DataExportMenuService.ts b/webapp/packages/data-export-plugin/src/DataExportMenuService.ts index e975c7cfd57..ae6885370d3 100644 --- a/webapp/packages/data-export-plugin/src/DataExportMenuService.ts +++ b/webapp/packages/data-export-plugin/src/DataExportMenuService.ts @@ -6,8 +6,13 @@ * you may not use this file except in compliance with the License. */ +import { + NavigationTreeContextMenuService, NodeManagerUtils, NodeWithParent, EObjectFeature +} from '@dbeaver/core/app'; import { injectable } from '@dbeaver/core/di'; -import { IContextMenuItem, IMenuContext, CommonDialogService } from '@dbeaver/core/dialogs'; +import { + IContextMenuItem, IMenuContext, CommonDialogService, ContextMenuService +} from '@dbeaver/core/dialogs'; import { TableFooterMenuService, TableViewerModel } from '@dbeaver/data-viewer-plugin'; import { DataExportDialog } from './Dialog/DataExportDialog'; @@ -18,6 +23,7 @@ export class DataExportMenuService { constructor( private commonDialogService: CommonDialogService, private tableFooterMenuService: TableFooterMenuService, + private contextMenuService: ContextMenuService, ) { } register() { @@ -32,6 +38,27 @@ export class DataExportMenuService { onClick: this.exportData.bind(this), }; this.tableFooterMenuService.registerMenuItem(exportData); + + this.contextMenuService.addMenuItem( + this.contextMenuService.getRootMenuToken(), + { + id: 'export', + isPresent(context) { + return context.contextType === NavigationTreeContextMenuService.nodeContextType + && !!context.data.object?.features?.includes(EObjectFeature.dataContainer); + }, + order: 2, + title: 'Export', + onClick: (context) => { + const node = context.data; + const connectionId = NodeManagerUtils.nodeIdToConnectionId(node.id); + this.commonDialogService.open(DataExportDialog, { + connectionId, + containerNodePath: node.id, + }); + }, + } + ); } private exportData(context: IMenuContext) { diff --git a/webapp/packages/data-export-plugin/src/Dialog/DataExportController.ts b/webapp/packages/data-export-plugin/src/Dialog/DataExportController.ts index 9773dabce2f..a07e1817dc2 100644 --- a/webapp/packages/data-export-plugin/src/Dialog/DataExportController.ts +++ b/webapp/packages/data-export-plugin/src/Dialog/DataExportController.ts @@ -8,10 +8,12 @@ import { observable, computed } from 'mobx'; +import { ErrorDetailsDialog } from '@dbeaver/core/app'; import { IProperty } from '@dbeaver/core/blocks'; -import { injectable, IInitializableController } from '@dbeaver/core/di'; +import { injectable, IInitializableController, IDestructibleController } from '@dbeaver/core/di'; +import { CommonDialogService } from '@dbeaver/core/dialogs'; import { NotificationService } from '@dbeaver/core/eventsLog'; -import { DataTransferProcessorInfo } from '@dbeaver/core/sdk'; +import { DataTransferProcessorInfo, GQLErrorCatcher } from '@dbeaver/core/sdk'; import { DataExportService } from '../DataExportService'; import { IExportContext } from '../IExportContext'; @@ -22,7 +24,7 @@ export enum DataExportStep { } @injectable() -export class DataExportController implements IInitializableController { +export class DataExportController implements IInitializableController, IDestructibleController { @observable step = DataExportStep.DataTransferProcessor get isLoading() { return this.dataExportService.processors.isLoading(); @@ -35,18 +37,22 @@ export class DataExportController implements IInitializableController { .from( this.dataExportService.processors.data.values() ) - .sort((a, b) => this.sortProcessors(a, b)); + .sort(sortProcessors); } @observable processorProperties: any = {} @observable properties: IProperty[] = [] + readonly error = new GQLErrorCatcher(); + private context!: IExportContext; private close!: () => void; + private isDistructed = false; constructor( private dataExportService: DataExportService, - private notificationService: NotificationService + private notificationService: NotificationService, + private commonDialogService: CommonDialogService ) { } init(context: IExportContext, close: () => void) { @@ -55,6 +61,10 @@ export class DataExportController implements IInitializableController { this.loadProcessors(); } + destruct(): void { + this.isDistructed = true; + } + prepareExport = async () => { if (!this.processor || this.isExporting) { return; @@ -71,7 +81,9 @@ export class DataExportController implements IInitializableController { ); this.close(); } catch (exception) { - this.notificationService.logException(exception, 'Can\'t export'); + if (!this.error.catch(exception) || this.isDistructed) { + this.notificationService.logException(exception, 'Can\'t export'); + } } finally { this.isExporting = false; close(); @@ -100,6 +112,13 @@ export class DataExportController implements IInitializableController { this.processorProperties = {}; this.step = DataExportStep.Configure; + this.error.clear(); + } + + showDetails = () => { + if (this.error.exception) { + this.commonDialogService.open(ErrorDetailsDialog, this.error.exception); + } } private async loadProcessors() { @@ -109,13 +128,13 @@ export class DataExportController implements IInitializableController { this.notificationService.logException(exception, 'Can\'t load data export processors'); } } +} - private sortProcessors(processorA: DataTransferProcessorInfo, processorB: DataTransferProcessorInfo): number { - if (processorA.order === processorB.order) - { - return (processorA.name || '').localeCompare((processorB.name || '')); - } - - return processorA.order - processorB.order; +function sortProcessors(processorA: DataTransferProcessorInfo, processorB: DataTransferProcessorInfo): number { + if (processorA.order === processorB.order) + { + return (processorA.name || '').localeCompare((processorB.name || '')); } + + return processorA.order - processorB.order; } diff --git a/webapp/packages/data-export-plugin/src/Dialog/DataExportDialog.tsx b/webapp/packages/data-export-plugin/src/Dialog/DataExportDialog.tsx index dbb0db6e684..fa1acd83bde 100644 --- a/webapp/packages/data-export-plugin/src/Dialog/DataExportDialog.tsx +++ b/webapp/packages/data-export-plugin/src/Dialog/DataExportDialog.tsx @@ -26,7 +26,9 @@ export const DataExportDialog: DialogComponent = observer( processor={controller.processor} properties={controller.properties} processorProperties={controller.processorProperties} + error={controller.error} isExporting={controller.isExporting} + onShowDetails={controller.showDetails} onBack={() => controller.setStep(DataExportStep.DataTransferProcessor)} onClose={props.rejectDialog} onExport={controller.prepareExport} diff --git a/webapp/packages/data-export-plugin/src/Dialog/ProcessorConfigureDialog.tsx b/webapp/packages/data-export-plugin/src/Dialog/ProcessorConfigureDialog.tsx index 847d35efd29..d150fdbd5a3 100644 --- a/webapp/packages/data-export-plugin/src/Dialog/ProcessorConfigureDialog.tsx +++ b/webapp/packages/data-export-plugin/src/Dialog/ProcessorConfigureDialog.tsx @@ -9,31 +9,49 @@ import { observer } from 'mobx-react'; import styled, { css } from 'reshadow'; -import { IProperty, PropertiesTable } from '@dbeaver/core/blocks'; +import { IProperty, PropertiesTable, ErrorMessage } from '@dbeaver/core/blocks'; import { CommonDialogWrapper } from '@dbeaver/core/dialogs'; import { useTranslate } from '@dbeaver/core/localization'; -import { DataTransferProcessorInfo } from '@dbeaver/core/sdk'; +import { DataTransferProcessorInfo, GQLErrorCatcher } from '@dbeaver/core/sdk'; +import { composes, useStyles } from '@dbeaver/core/theming'; import { ProcessorConfigureDialogFooter } from './ProcessorConfigureDialogFooter'; -const styles = css` - CommonDialogWrapper { - max-height: 500px; - min-height: 500px; - } - PropertiesTable { - flex: 1; - } - message { - margin: auto; - } -`; +const styles = composes( + css` + Tab { + composes: theme-ripple theme-background-secondary theme-text-on-secondary from global; + } + ErrorMessage { + composes: theme-background-secondary from global; + } + `, + css` + CommonDialogWrapper { + max-height: 500px; + min-height: 500px; + } + PropertiesTable { + flex: 1; + } + message { + margin: auto; + } + ErrorMessage { + position: sticky; + bottom: 0; + padding: 8px 24px; + } + ` +); type ProcessorSelectDialogProps = { processor: DataTransferProcessorInfo; properties: IProperty[]; processorProperties: any; + error: GQLErrorCatcher; isExporting: boolean; + onShowDetails(): void; onClose(): void; onBack(): void; onExport(): void; @@ -44,7 +62,9 @@ export const ProcessorConfigureDialog = observer( processor, properties, processorProperties, + error, isExporting, + onShowDetails, onClose, onBack, onExport, @@ -52,7 +72,7 @@ export const ProcessorConfigureDialog = observer( const translate = useTranslate(); const title = `${translate('data_transfer_dialog_configuration_title')} (${processor.name})`; - return styled(styles)( + return styled(useStyles(styles))( - {isExporting && {translate('data_transfer_dialog_preparation')}} - {!isExporting && ( - + {error.responseMessage && ( + )} diff --git a/webapp/packages/data-export-plugin/src/Dialog/ProcessorSelectDialog.tsx b/webapp/packages/data-export-plugin/src/Dialog/ProcessorSelectDialog.tsx index fb11495de4b..f7891ded9ef 100644 --- a/webapp/packages/data-export-plugin/src/Dialog/ProcessorSelectDialog.tsx +++ b/webapp/packages/data-export-plugin/src/Dialog/ProcessorSelectDialog.tsx @@ -66,7 +66,7 @@ export const ProcessorSelectDialog = observer( > {context.sourceName ? translate('data_transfer_exporting_sql') : `${translate('data_transfer_exporting_table')} ${node?.name}`} -
{context.sourceName}
+
{context.sourceName}
{isLoading && } {!isLoading && } diff --git a/webapp/packages/data-export-plugin/src/ExportNotification/ExportNotification.tsx b/webapp/packages/data-export-plugin/src/ExportNotification/ExportNotification.tsx index adf58fa64f8..04faf817392 100644 --- a/webapp/packages/data-export-plugin/src/ExportNotification/ExportNotification.tsx +++ b/webapp/packages/data-export-plugin/src/ExportNotification/ExportNotification.tsx @@ -59,7 +59,7 @@ export const ExportNotification = observer(function ExportNotification({ {controller.sourceName} -
{controller.task?.context.sourceName}
+
{controller.task?.context.sourceName}
{controller.isSuccess && (