Merge remote-tracking branch 'origin/devel' into CB-92-builder-package

This commit is contained in:
Dmitry Krupenya
2020-05-19 19:04:24 +03:00
20 changed files with 167 additions and 60 deletions
+9
View File
@@ -24,6 +24,15 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### CloudBeaver 1.0.2 - 2020-05-19
- Data export feature UI was improved
- User authentication and permission management
- Driver management was redesigned
- Clickhouse driver was added
- Build procedure was redesigned
- Many minor bugfixes
### CloudBeaver 1.0.1 - 2020-05-05
- Data export feature added to the data viewer
+2
View File
@@ -47,3 +47,5 @@ echo Copy static content
xcopy /E /Q ..\webapp\packages\dbeaver\dist cloudbeaver\web >NUL
echo Cloudbeaver is ready. Run run-server.bat in cloudbeaver folder to start the server.
pause
@@ -30,16 +30,15 @@ import org.eclipse.core.runtime.Status;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPPlatform;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.meta.Association;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.navigator.DBNModel;
import org.jkiss.dbeaver.model.navigator.DBNNode;
import org.jkiss.dbeaver.model.navigator.DBNProject;
import org.jkiss.dbeaver.model.navigator.DBNProjectDatabases;
import org.jkiss.dbeaver.model.runtime.*;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
import org.jkiss.utils.CommonUtils;
@@ -171,8 +170,8 @@ public class WebSession {
}
private void initNavigatorModel() {
DBPPlatform platform = DBWorkbench.getPlatform();
this.navigatorModel = new DBNModel(platform, false);
CBPlatform platform = CBPlatform.getInstance();
this.navigatorModel = new DBNModel(platform, this);
this.navigatorModel.initialize();
DBPProject project = platform.getWorkspace().getActiveProject();
@@ -252,6 +251,10 @@ public class WebSession {
return databases;
}
public DBNNode getNavigatorNodes() {
return databases;
}
/**
* Returns and clears progress messages
*/
@@ -62,7 +62,7 @@ public class CBResourceHandler implements DBPResourceHandler {
@Override
public String getResourceNodeName(IResource resource) {
return resource.toString();
return resource.getName();
}
@NotNull
@@ -19,10 +19,10 @@ package io.cloudbeaver.service.navigator.impl;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.service.navigator.WebDatabaseObjectInfo;
import io.cloudbeaver.service.navigator.WebNavigatorNodeInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.navigator.DBWServiceNavigator;
import io.cloudbeaver.service.navigator.WebDatabaseObjectInfo;
import io.cloudbeaver.service.navigator.WebNavigatorNodeInfo;
import io.cloudbeaver.service.navigator.WebStructContainers;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
@@ -53,18 +53,28 @@ public class WebServiceNavigator implements DBWServiceNavigator {
try {
DBRProgressMonitor monitor = session.getProgressMonitor();
DBNNode parentNode = CommonUtils.isEmpty(parentPath) || "/".equals(parentPath) ? session.getDatabases() : session.getNavigatorModel().getNodeByPath(monitor, parentPath);
if (parentNode == null) {
throw new DBWebException("Node '" + parentPath + "' not found");
DBNNode[] nodeChildren;
if (CommonUtils.isEmpty(parentPath) || "/".equals(parentPath)) {
nodeChildren = session.getDatabases().getChildren(monitor);
} else {
DBNNode parentNode = session.getNavigatorModel().getNodeByPath(monitor, parentPath);
if (parentNode == null) {
throw new DBWebException("Node '" + parentPath + "' not found");
}
if (!parentNode.hasChildren(true)) {
return EMPTY_NODE_LIST;
}
nodeChildren = parentNode.getChildren(monitor);
}
if (!parentNode.hasChildren(true)) {
return EMPTY_NODE_LIST;
}
DBNNode[] nodeChildren = parentNode.getChildren(monitor);
if (nodeChildren == null) {
return EMPTY_NODE_LIST;
}
List<WebNavigatorNodeInfo> result = new ArrayList<>();
// Add navigator extensions
for (DBNNode extraNode : session.getNavigatorModel().getRoot().getExtraNodes()) {
result.add(new WebNavigatorNodeInfo(session, extraNode));
}
for (DBNNode node : nodeChildren) {
if (!CommonUtils.toBoolean(onlyFolders) || node instanceof DBNContainer) {
result.add(new WebNavigatorNodeInfo(session, node));
@@ -29,6 +29,8 @@ import java.util.Map;
*/
public interface DBWServiceAuth extends DBWService {
String ATTR_USER_AUTH = "user-auth-info";
@WebAction(requirePermissions = {} )
WebAuthInfo authLogin(WebSession webSession, String providerId, Map<String, Object> credentials) throws DBWebException;
@@ -16,11 +16,10 @@
*/
package io.cloudbeaver.service.auth;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.user.WebUser;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.Date;
/**
* WebAuthInfo
@@ -76,4 +75,8 @@ public class WebAuthInfo {
public void setMessage(String message) {
this.message = message;
}
public static WebAuthInfo getFromSession(WebSession webSession) {
return webSession.getAttribute(DBWServiceAuth.ATTR_USER_AUTH);
}
}
@@ -42,8 +42,6 @@ import java.util.Map;
*/
public class WebServiceAuthImpl implements DBWServiceAuth {
private static final String ATTR_USER_AUTH = "user-auth-info";
@Override
public WebAuthInfo authLogin(WebSession webSession, String providerId, Map<String, Object> authParameters) throws DBWebException {
if (!CBApplication.getInstance().getAppConfiguration().isAuthenticationEnabled()) {
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -76,7 +76,7 @@ module.exports = {
app_shared_settingsMenu_theme: 'Theme',
app_shared_settingsMenu_lang: 'Language',
core_eventsLog_dbeaverErrorDetails: 'DBeaver error details',
core_eventsLog_dbeaverErrorDetails: 'Error details',
app_shared_inlineEditor_dialog_title: 'Edit mode',
app_shared_inlineEditor_dialog_apply: 'Apply',
@@ -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<NodeWithParent>) => {
@@ -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];
}
}
@@ -15,7 +15,7 @@ import { IMenuContext } from './IMenuContext';
export interface IContextMenuItem<T> extends IMenuItemOptions {
onClick?: (context: IMenuContext<T>) => void;
// if isPresent is false menu item will not be included in resulting context menu
isPresent: (context: IMenuContext<any>) => boolean;
isPresent: (context: IMenuContext<T>) => boolean;
isDisabled?: (context: IMenuContext<T>) => boolean;
// When the item is present in menu it can be hidden based on certain conditions
isHidden?: (context: IMenuContext<T>) => boolean;
@@ -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<NodeWithParent>(
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<TableViewerModel>) {
@@ -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 || ''));
}
function sortProcessors(processorA: DataTransferProcessorInfo, processorB: DataTransferProcessorInfo): number {
if (processorA.order === processorB.order)
{
return (processorA.name || '').localeCompare((processorB.name || ''));
}
return processorA.order - processorB.order;
}
return processorA.order - processorB.order;
}
@@ -26,7 +26,9 @@ export const DataExportDialog: DialogComponent<IExportContext, null> = 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}
@@ -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))(
<CommonDialogWrapper
title={title}
noBodyPadding
@@ -66,11 +86,15 @@ export const ProcessorConfigureDialog = observer(
}
onReject={onClose}
>
{isExporting && <message as="div">{translate('data_transfer_dialog_preparation')}</message>}
{!isExporting && (
<PropertiesTable
properties={properties}
propertiesState={processorProperties}
<PropertiesTable
properties={properties}
propertiesState={processorProperties}
/>
{error.responseMessage && (
<ErrorMessage
text={error.responseMessage}
hasDetails={error.hasDetails}
onShowDetails={onShowDetails}
/>
)}
</CommonDialogWrapper>
@@ -66,7 +66,7 @@ export const ProcessorSelectDialog = observer(
>
<export-object as="div">
{context.sourceName ? translate('data_transfer_exporting_sql') : `${translate('data_transfer_exporting_table')} ${node?.name}`}
<pre>{context.sourceName}</pre>
<pre title={context.sourceName}>{context.sourceName}</pre>
</export-object>
{isLoading && <Loader />}
{!isLoading && <ExportProcessorList processors={processors} onSelect={onSelect}/>}
@@ -59,7 +59,7 @@ export const ExportNotification = observer(function ExportNotification({
<notification-body as="div">
<source-name as="div">
{controller.sourceName}
<pre>{controller.task?.context.sourceName}</pre>
<pre title={controller.task?.context.sourceName}>{controller.task?.context.sourceName}</pre>
</source-name>
<actions as="div">
{controller.isSuccess && (
-1
View File
@@ -1 +0,0 @@
See [Wiki](https://github.com/dbeaver-corp/dbeaver-web/wiki/Frontend-CI&CD)