mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-3330 add data import plugin (#2506)
* CB-3330 add data import plugin * CB-3330 add mock api * CB-3330 pass processor id * CB-3330 remove extra code * CB-3330 simplify logic, allow import only one file * CB-3330. Implemented import data to table * CB-3330 replace mocks with api * CB-3330 add references * CB-3330. Parsing request, added xml, xls * CB-3330 support new settings * CB-3330 use getter * CB-3330 add svg icons * CB-3330 pass arguments in handleImport fn * CB-3330 remove extra fn wrapper * CB-3330. Fix build * CB-3330. Fix build * CB-3330. Fix import * CB-3330. Fixed import * CB-3300 fix: frontend can build * CB-3330. Refactor after review * CB-3330. Refactor after review, delete temp file after import * CB-3330. Refactor after review * CB-3330. Refactor after review * CB-3330. Added log message * CB-3330. Added response for task * CB-3330 fix icon and button shadow * CB-3330. Refactor response value * CB-3330. Refactor response value * CB-3330. delete unused imports * CB-3330 run additional task after file import * CB-3330 always allow data import for admins * CB-3330 import resource as module --------- Co-authored-by: DenisSinelnikov <142215442+DenisSinelnikov@users.noreply.github.com> Co-authored-by: denis.sinelnikov <denis.sinelnikov@dbeaver.com> Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com> Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
co-authored by
DenisSinelnikov
denis.sinelnikov
sergeyteleshev
Evgenia Bezborodova
parent
101d26158b
commit
32b4e4ec19
@@ -68,7 +68,8 @@ public class CBPlatform extends BasePlatformImpl {
|
||||
public static final String PLUGIN_ID = "io.cloudbeaver.server"; //$NON-NLS-1$
|
||||
|
||||
private static final Log log = Log.getLog(CBPlatform.class);
|
||||
private static final String TEMP_FILE_FOLDER = "temp-sql-upload-files";
|
||||
public static final String TEMP_FILE_FOLDER = "temp-sql-upload-files";
|
||||
public static final String TEMP_FILE_IMPORT_FOLDER = "temp-import-files";
|
||||
|
||||
public static final String WORK_DATA_FOLDER_NAME = ".work-data";
|
||||
|
||||
@@ -133,6 +134,7 @@ public class CBPlatform extends BasePlatformImpl {
|
||||
protected IStatus run(DBRProgressMonitor monitor) {
|
||||
try {
|
||||
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_FOLDER));
|
||||
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_IMPORT_FOLDER));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
+11
-2
@@ -31,11 +31,20 @@ import java.nio.file.Path;
|
||||
public class WSDeleteTempFileHandler implements WSEventHandler<WSEventDeleteTempFile> {
|
||||
|
||||
private static final Log log = Log.getLog(WSDeleteTempFileHandler.class);
|
||||
private static final String TEMP_FILE_FOLDER = "temp-sql-upload-files";
|
||||
|
||||
public void resetTempFolder(String sessionId) {
|
||||
Path path = CBPlatform.getInstance()
|
||||
.getTempFolder(new VoidProgressMonitor(), TEMP_FILE_FOLDER)
|
||||
.getTempFolder(new VoidProgressMonitor(), CBPlatform.TEMP_FILE_FOLDER)
|
||||
.resolve(sessionId);
|
||||
if (Files.exists(path)) {
|
||||
try {
|
||||
IOUtils.deleteDirectory(path);
|
||||
} catch (IOException e) {
|
||||
log.error("Error deleting temp path", e);
|
||||
}
|
||||
}
|
||||
path = CBPlatform.getInstance()
|
||||
.getTempFolder(new VoidProgressMonitor(), CBPlatform.TEMP_FILE_IMPORT_FOLDER)
|
||||
.resolve(sessionId);
|
||||
if (Files.exists(path)) {
|
||||
try {
|
||||
|
||||
+3
@@ -53,6 +53,9 @@ extend type Query {
|
||||
# Available transfer processors
|
||||
dataTransferAvailableStreamProcessors: [ DataTransferProcessorInfo! ]!
|
||||
|
||||
# Available transfer processors for import file
|
||||
dataTransferAvailableImportStreamProcessors: [ DataTransferProcessorInfo! ]!
|
||||
|
||||
dataTransferDefaultExportSettings: DataTransferDefaultExportSettings!
|
||||
|
||||
dataTransferExportDataFromContainer(
|
||||
|
||||
+13
@@ -26,7 +26,10 @@ import io.cloudbeaver.service.data.transfer.impl.WebDataTransferParameters;
|
||||
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferStreamProcessor;
|
||||
import io.cloudbeaver.service.sql.WebSQLContextInfo;
|
||||
import io.cloudbeaver.service.sql.WebSQLProcessor;
|
||||
import io.cloudbeaver.service.sql.WebSQLResultsInfo;
|
||||
import org.jkiss.code.NotNull;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -37,12 +40,22 @@ public interface DBWServiceDataTransfer extends DBWService {
|
||||
@WebAction
|
||||
List<WebDataTransferStreamProcessor> getAvailableStreamProcessors(WebSession session) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
List<WebDataTransferStreamProcessor> getAvailableImportStreamProcessors(WebSession session) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
WebAsyncTaskInfo dataTransferExportDataFromContainer(
|
||||
WebSQLProcessor sqlProcessor,
|
||||
String containerNodePath,
|
||||
WebDataTransferParameters parameters) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
WebAsyncTaskInfo asyncImportDataContainer(
|
||||
@NotNull String processorId,
|
||||
@NotNull Path path,
|
||||
@NotNull WebSQLResultsInfo webSQLResultsInfo,
|
||||
@NotNull WebSession webSession) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
WebAsyncTaskInfo dataTransferExportDataFromResults(
|
||||
WebSQLContextInfo sqlContextInfo,
|
||||
|
||||
+8
@@ -24,6 +24,7 @@ import io.cloudbeaver.service.WebServiceBindingBase;
|
||||
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferParameters;
|
||||
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferServlet;
|
||||
import io.cloudbeaver.service.data.transfer.impl.WebServiceDataTransfer;
|
||||
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferImportServlet;
|
||||
import io.cloudbeaver.service.sql.WebServiceBindingSQL;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
|
||||
@@ -42,6 +43,8 @@ public class WebServiceBindingDataTransfer extends WebServiceBindingBase<DBWServ
|
||||
model.getQueryType()
|
||||
.dataFetcher("dataTransferAvailableStreamProcessors",
|
||||
env -> getService(env).getAvailableStreamProcessors(getWebSession(env)))
|
||||
.dataFetcher("dataTransferAvailableImportStreamProcessors",
|
||||
env -> getService(env).getAvailableImportStreamProcessors(getWebSession(env)))
|
||||
.dataFetcher("dataTransferExportDataFromContainer", env -> getService(env).dataTransferExportDataFromContainer(
|
||||
WebServiceBindingSQL.getSQLProcessor(env),
|
||||
env.getArgument("containerNodePath"),
|
||||
@@ -68,5 +71,10 @@ public class WebServiceBindingDataTransfer extends WebServiceBindingBase<DBWServ
|
||||
new WebDataTransferServlet(application, getServiceImpl()),
|
||||
application.getServicesURI() + "data/*"
|
||||
);
|
||||
servletContext.addServlet(
|
||||
"dataTransferImport",
|
||||
new WebDataTransferImportServlet(application, getServiceImpl()),
|
||||
application.getServicesURI() + "data/import/*"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.data.transfer.impl;
|
||||
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.model.WebAsyncTaskInfo;
|
||||
import io.cloudbeaver.model.WebConnectionInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import io.cloudbeaver.server.CBConstants;
|
||||
import io.cloudbeaver.server.CBPlatform;
|
||||
import io.cloudbeaver.service.WebServiceServletBase;
|
||||
import io.cloudbeaver.service.data.transfer.DBWServiceDataTransfer;
|
||||
import io.cloudbeaver.service.sql.WebSQLContextInfo;
|
||||
import io.cloudbeaver.service.sql.WebSQLProcessor;
|
||||
import io.cloudbeaver.service.sql.WebSQLResultsInfo;
|
||||
import io.cloudbeaver.service.sql.WebServiceBindingSQL;
|
||||
import jakarta.servlet.MultipartConfigElement;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.annotation.MultipartConfig;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
@MultipartConfig
|
||||
public class WebDataTransferImportServlet extends WebServiceServletBase {
|
||||
|
||||
private static final Log log = Log.getLog(WebDataTransferImportServlet.class);
|
||||
public static final String ECLIPSE_JETTY_MULTIPART_CONFIG = "org.eclipse.jetty.multipartConfig";
|
||||
|
||||
DBWServiceDataTransfer dbwServiceDataTransfer;
|
||||
|
||||
|
||||
public WebDataTransferImportServlet(CBApplication application, DBWServiceDataTransfer dbwServiceDataTransfer) {
|
||||
super(application);
|
||||
this.dbwServiceDataTransfer = dbwServiceDataTransfer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processServiceRequest(
|
||||
WebSession session,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response
|
||||
) throws IOException, DBWebException {
|
||||
if (!session.isAuthorizedInSecurityManager()) {
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Import for users only");
|
||||
return;
|
||||
}
|
||||
if ("POST".equalsIgnoreCase(request.getMethod())) {
|
||||
Path tempFolder = CBPlatform.getInstance().getTempFolder(session.getProgressMonitor(), CBPlatform.TEMP_FILE_IMPORT_FOLDER);
|
||||
MultipartConfigElement MULTI_PART_CONFIG = new MultipartConfigElement(tempFolder.toString());
|
||||
|
||||
request.setAttribute(ECLIPSE_JETTY_MULTIPART_CONFIG, MULTI_PART_CONFIG);
|
||||
|
||||
Map<String, Object> variables = getVariables(request);
|
||||
|
||||
String projectId = JSONUtils.getString(variables, "projectId");
|
||||
String connectionId = JSONUtils.getString(variables, "connectionId");
|
||||
String contextId = JSONUtils.getString(variables, "contextId");
|
||||
String resultId = JSONUtils.getString(variables, "resultsId");
|
||||
String processorId = JSONUtils.getString(variables, "processorId");
|
||||
|
||||
if (projectId == null || connectionId == null || contextId == null || resultId == null || processorId == null) {
|
||||
throw new IllegalArgumentException("Missing required parameters");
|
||||
}
|
||||
|
||||
WebConnectionInfo webConnectionInfo = session.getWebConnectionInfo(projectId, connectionId);
|
||||
WebSQLProcessor processor = WebServiceBindingSQL.getSQLProcessor(webConnectionInfo);
|
||||
WebSQLContextInfo webSQLContextInfo = processor.getContext(contextId);
|
||||
|
||||
if (webSQLContextInfo == null) {
|
||||
throw new DBWebException("Context is empty");
|
||||
}
|
||||
|
||||
WebSQLResultsInfo webSQLResultsInfo = webSQLContextInfo.getResults(resultId);
|
||||
Path filePath;
|
||||
|
||||
try {
|
||||
InputStream file = request.getPart("fileData").getInputStream();
|
||||
filePath = tempFolder.resolve(UUID.randomUUID().toString());
|
||||
Files.write(filePath, file.readAllBytes());
|
||||
} catch (ServletException e) {
|
||||
throw new DBWebException(e.getMessage());
|
||||
}
|
||||
|
||||
WebAsyncTaskInfo asyncImportDataContainer =
|
||||
dbwServiceDataTransfer.asyncImportDataContainer(processorId, filePath, webSQLResultsInfo, session);
|
||||
response.setContentType(CBConstants.APPLICATION_JSON);
|
||||
Map<String, Object> parameters = new LinkedHashMap<>();
|
||||
parameters.put("id", asyncImportDataContainer.getId());
|
||||
parameters.put("name", asyncImportDataContainer.getName());
|
||||
parameters.put("running", asyncImportDataContainer.isRunning());
|
||||
parameters.put("status", asyncImportDataContainer.getStatus());
|
||||
parameters.put("error", asyncImportDataContainer.getError());
|
||||
parameters.put("taskResult", asyncImportDataContainer.getTaskResult());
|
||||
try (JsonWriter writer = new JsonWriter(response.getWriter())) {
|
||||
JSONUtils.serializeMap(writer, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+99
-6
@@ -37,24 +37,27 @@ import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.sql.DBQuotaException;
|
||||
import org.jkiss.dbeaver.model.sql.DBSQLException;
|
||||
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
|
||||
import org.jkiss.dbeaver.model.struct.DBSDataManipulator;
|
||||
import org.jkiss.dbeaver.model.struct.DBSEntity;
|
||||
import org.jkiss.dbeaver.model.struct.DBSObjectContainer;
|
||||
import org.jkiss.dbeaver.tools.transfer.IDataTransferConsumer;
|
||||
import org.jkiss.dbeaver.tools.transfer.IDataTransferProcessor;
|
||||
import org.jkiss.dbeaver.tools.transfer.database.DatabaseProducerSettings;
|
||||
import org.jkiss.dbeaver.tools.transfer.database.DatabaseTransferProducer;
|
||||
import org.jkiss.dbeaver.tools.transfer.database.*;
|
||||
import org.jkiss.dbeaver.tools.transfer.registry.DataTransferProcessorDescriptor;
|
||||
import org.jkiss.dbeaver.tools.transfer.registry.DataTransferRegistry;
|
||||
import org.jkiss.dbeaver.tools.transfer.stream.IStreamDataExporter;
|
||||
import org.jkiss.dbeaver.tools.transfer.stream.StreamConsumerSettings;
|
||||
import org.jkiss.dbeaver.tools.transfer.stream.StreamTransferConsumer;
|
||||
import org.jkiss.dbeaver.tools.transfer.stream.*;
|
||||
import org.jkiss.dbeaver.utils.ContentUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.BatchUpdateException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -82,7 +85,19 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
|
||||
|
||||
@Override
|
||||
public List<WebDataTransferStreamProcessor> getAvailableStreamProcessors(WebSession session) {
|
||||
List<DataTransferProcessorDescriptor> processors = DataTransferRegistry.getInstance().getAvailableProcessors(StreamTransferConsumer.class, DBSEntity.class);
|
||||
List<DataTransferProcessorDescriptor> processors = DataTransferRegistry.getInstance()
|
||||
.getAvailableProcessors(StreamTransferConsumer.class, DBSEntity.class);
|
||||
if (CommonUtils.isEmpty(processors)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return processors.stream().map(x -> new WebDataTransferStreamProcessor(session, x)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WebDataTransferStreamProcessor> getAvailableImportStreamProcessors(WebSession session) {
|
||||
List<DataTransferProcessorDescriptor> processors =
|
||||
DataTransferRegistry.getInstance().getAvailableProcessors(StreamTransferProducer.class, DBSEntity.class);
|
||||
if (CommonUtils.isEmpty(processors)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@@ -205,6 +220,43 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
|
||||
return sqlProcessor.getWebSession().createAndRunAsyncTask("Data export", runnable);
|
||||
}
|
||||
|
||||
public WebAsyncTaskInfo asyncImportDataContainer(@NotNull String processorId,
|
||||
@NotNull Path path,
|
||||
@NotNull WebSQLResultsInfo sqlContext,
|
||||
@NotNull WebSession webSession) throws DBWebException {
|
||||
webSession.addInfoMessage("Import data");
|
||||
DataTransferProcessorDescriptor processor = DataTransferRegistry.getInstance().getProcessor(processorId);
|
||||
|
||||
DBSDataContainer dataContainer = sqlContext.getDataContainer();
|
||||
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
|
||||
@Override
|
||||
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
|
||||
monitor.beginTask("Import data", 1);
|
||||
try {
|
||||
monitor.subTask("Import data using " + processor.getName());
|
||||
try {
|
||||
importData(monitor, processor, (DBSDataManipulator) dataContainer, path);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof DBException) {
|
||||
throw e;
|
||||
}
|
||||
throw new DBException("Error importing data", e);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
throw new InvocationTargetException(e);
|
||||
} finally {
|
||||
try {
|
||||
Files.deleteIfExists(path);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to delete file: " + e.getMessage(), e);
|
||||
}
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
};
|
||||
return webSession.createAndRunAsyncTask("Data import", runnable);
|
||||
}
|
||||
|
||||
private void exportData(
|
||||
DBRProgressMonitor monitor,
|
||||
DataTransferProcessorDescriptor processor,
|
||||
@@ -282,4 +334,45 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
|
||||
consumer.finishTransfer(monitor, false);
|
||||
}
|
||||
|
||||
private void importData(
|
||||
DBRProgressMonitor monitor,
|
||||
DataTransferProcessorDescriptor processor,
|
||||
@NotNull DBSDataManipulator dataContainer,
|
||||
Path path) throws DBException
|
||||
{
|
||||
IDataTransferProcessor processorInstance = processor.getInstance();
|
||||
|
||||
if (dataContainer.getDataSource() != null) {
|
||||
DatabaseTransferConsumer consumer = new DatabaseTransferConsumer(dataContainer);
|
||||
|
||||
DatabaseConsumerSettings databaseConsumerSettings = new DatabaseConsumerSettings();
|
||||
databaseConsumerSettings.setContainer((DBSObjectContainer) dataContainer.getDataSource());
|
||||
consumer.setSettings(databaseConsumerSettings);
|
||||
DatabaseMappingContainer databaseMappingContainer = new DatabaseMappingContainer(databaseConsumerSettings, dataContainer);
|
||||
databaseMappingContainer.getAttributeMappings(monitor);
|
||||
databaseMappingContainer.setTarget(dataContainer);
|
||||
consumer.setContainerMapping(databaseMappingContainer);
|
||||
|
||||
StreamTransferProducer producer = new StreamTransferProducer(new StreamEntityMapping(path), processor);
|
||||
|
||||
StreamProducerSettings producerSettings = new StreamProducerSettings();
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
for (DBPPropertyDescriptor prop : processor.getProperties()) {
|
||||
properties.put(prop.getId(), prop.getDefaultValue());
|
||||
}
|
||||
producerSettings.setProcessorProperties(properties);
|
||||
producerSettings.updateProducerSettingsFromStream(
|
||||
monitor,
|
||||
producer,
|
||||
processorInstance,
|
||||
properties);
|
||||
|
||||
try {
|
||||
producer.transferData(monitor, consumer, processorInstance, producerSettings, null);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Import failed cause: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { GlobalConstants } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { CustomGraphQLClient, UploadProgressEvent } from '../CustomGraphQLClient';
|
||||
import type { AsyncTaskInfo } from '../sdk';
|
||||
|
||||
export interface IUploadResultDataExtension {
|
||||
uploadResultData: (
|
||||
connectionId: string,
|
||||
contextId: string,
|
||||
projectId: string,
|
||||
resultsId: string,
|
||||
processorId: string,
|
||||
file: File,
|
||||
onUploadProgress?: (event: UploadProgressEvent) => void,
|
||||
) => Promise<AsyncTaskInfo>;
|
||||
}
|
||||
|
||||
export function uploadResultDataExtension(client: CustomGraphQLClient): IUploadResultDataExtension {
|
||||
return {
|
||||
uploadResultData(
|
||||
connectionId: string,
|
||||
contextId: string,
|
||||
projectId: string,
|
||||
resultsId: string,
|
||||
processorId: string,
|
||||
file: File,
|
||||
onUploadProgress?: (event: UploadProgressEvent) => void,
|
||||
): Promise<AsyncTaskInfo> {
|
||||
return client.uploadFile(
|
||||
GlobalConstants.absoluteServiceUrl('data', 'import'),
|
||||
file,
|
||||
undefined,
|
||||
{ connectionId, contextId, projectId, resultsId, processorId },
|
||||
onUploadProgress,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { CustomGraphQLClient } from './CustomGraphQLClient';
|
||||
import { EnvironmentService } from './EnvironmentService';
|
||||
import { uploadBlobResultSetExtension } from './Extensions/uploadBlobResultSetExtension';
|
||||
import { uploadDriverLibraryExtension } from './Extensions/uploadDriverLibraryExtension';
|
||||
import { uploadResultDataExtension } from './Extensions/uploadResultDataExtension';
|
||||
import type { IResponseInterceptor } from './IResponseInterceptor';
|
||||
import { getSdk } from './sdk';
|
||||
|
||||
@@ -21,6 +22,7 @@ function extendedSDK(client: CustomGraphQLClient) {
|
||||
...sdk,
|
||||
...uploadDriverLibraryExtension(client),
|
||||
...uploadBlobResultSetExtension(client),
|
||||
...uploadResultDataExtension(client),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
query getDataTransferImportProcessors {
|
||||
processors: dataTransferAvailableImportStreamProcessors {
|
||||
id
|
||||
name
|
||||
description
|
||||
fileExtension
|
||||
appFileExtension
|
||||
appName
|
||||
order
|
||||
icon
|
||||
isBinary
|
||||
isHTML
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
export function getProgressPercent(done: number, total: number) {
|
||||
const percentCompleted = Math.round((done * 100) / total);
|
||||
return Math.min(100, Math.max(0, percentCompleted));
|
||||
}
|
||||
@@ -79,3 +79,4 @@ export * from './replaceSubstring';
|
||||
export * from './formatNumber';
|
||||
export * from './withTimestamp';
|
||||
export * from './toSafeHtmlString';
|
||||
export * from './getProgressPercent';
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { createAction } from '../createAction';
|
||||
|
||||
export const ACTION_IMPORT = createAction('import', {
|
||||
label: 'ui_import',
|
||||
tooltip: 'ui_import',
|
||||
});
|
||||
@@ -18,6 +18,7 @@ export * from './Action/Actions/ACTION_ZOOM_IN';
|
||||
export * from './Action/Actions/ACTION_ZOOM_OUT';
|
||||
export * from './Action/Actions/ACTION_DOWNLOAD';
|
||||
export * from './Action/Actions/ACTION_UPLOAD';
|
||||
export * from './Action/Actions/ACTION_IMPORT';
|
||||
export * from './Action/KeyBinding/Bindings/KEY_BINDING_OPEN_IN_TAB';
|
||||
export * from './Action/KeyBinding/Bindings/KEY_BINDING_REDO';
|
||||
export * from './Action/KeyBinding/Bindings/KEY_BINDING_UNDO';
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/lib
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env*
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@cloudbeaver/plugin-data-import",
|
||||
"sideEffects": [
|
||||
"src/**/*.css",
|
||||
"src/**/*.scss",
|
||||
"public/**/*"
|
||||
],
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"license": "Apache-2.0",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc -b",
|
||||
"lint": "eslint ./src/ --ext .ts,.tsx",
|
||||
"lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
|
||||
"test": "yarn build && core-cli-test",
|
||||
"validate-dependencies": "core-cli-validate-dependencies",
|
||||
"update-ts-references": "rimraf --glob dist && typescript-resolve-references"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudbeaver/core-blocks": "~0.1.0",
|
||||
"@cloudbeaver/core-connections": "~0.1.0",
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-dialogs": "~0.1.0",
|
||||
"@cloudbeaver/core-events": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-navigation-tree": "~0.1.0",
|
||||
"@cloudbeaver/core-resource": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-ui": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0",
|
||||
"@cloudbeaver/core-view": "~0.1.0",
|
||||
"@cloudbeaver/plugin-data-viewer": "~0.1.0",
|
||||
"@cloudbeaver/plugin-datasource-context-switch": "~0.1.0",
|
||||
"@cloudbeaver/plugin-sql-editor": "~0.1.0",
|
||||
"@cloudbeaver/plugin-top-app-bar": "~0.1.0",
|
||||
"mobx": "^6.12.0",
|
||||
"mobx-react-lite": "^4.0.5",
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.63",
|
||||
"typescript": "^5.3.3",
|
||||
"typescript-plugin-css-modules": "^5.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
|
||||
.cls-1, .cls-2 {
|
||||
fill: #338ecc;
|
||||
stroke-width: 0px;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M27.5,4H4.5c-1.84,0-3.5,1.63-3.5,3.5v7.5h2v-5h26v14c0,1.2-.82,2-2,2H5c-1.18,0-2-.8-2-2v-3H1v3.5c0,1.87,1.66,3.5,3.5,3.5h23c1.84,0,3.5-1.63,3.5-3.5V7.5c0-1.87-1.66-3.5-3.5-3.5Z"/>
|
||||
<polygon class="cls-2" points="13.09 17.95 7.04 14 7.04 17 1 17 1 19 7.04 19 7.04 21.91 13.09 17.95"/>
|
||||
<rect class="cls-2" x="15" y="10" width="2" height="16"/>
|
||||
<rect class="cls-2" x="16" y="17" width="13" height="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 745 B |
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
|
||||
.cls-1, .cls-2 {
|
||||
fill: #338ecc;
|
||||
stroke-width: 0px;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M19.67,4H4.33c-1.23,0-2.33,1.08-2.33,2.33v5h1.33v-3.33h17.33v9.33c0,.8-.54,1.33-1.33,1.33H4.67c-.79,0-1.33-.53-1.33-1.33v-2h-1.33v2.33c0,1.25,1.11,2.33,2.33,2.33h15.33c1.23,0,2.33-1.08,2.33-2.33V6.33c0-1.25-1.11-2.33-2.33-2.33Z"/>
|
||||
<polygon class="cls-2" points="10.06 13.3 6.03 10.67 6.03 12.67 2 12.67 2 14 6.03 14 6.03 15.94 10.06 13.3"/>
|
||||
<rect class="cls-2" x="11.33" y="8" width="1.33" height="10.67"/>
|
||||
<rect class="cls-2" x="12" y="12.67" width="8.67" height="1.33"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 820 B |
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
|
||||
.cls-1, .cls-2 {
|
||||
fill: #338ecc;
|
||||
stroke-width: 0px;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M13.25,2H2.75c-.92,0-1.75.81-1.75,1.75v3.75h1v-2.5h12v7c0,.6-.41,1-1,1H3c-.59,0-1-.4-1-1v-1.5h-1v1.75c0,.94.83,1.75,1.75,1.75h10.5c.92,0,1.75-.81,1.75-1.75V3.75c0-.94-.83-1.75-1.75-1.75Z"/>
|
||||
<polygon class="cls-2" points="6.05 8.97 3.02 7 3.02 8.5 1 8.5 1 9.5 3.02 9.5 3.02 10.95 6.05 8.97"/>
|
||||
<rect class="cls-2" x="7" y="5" width="1" height="8"/>
|
||||
<rect class="cls-2" x="7.5" y="8.5" width="6.5" height="1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 755 B |
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
|
||||
import { ACTION_IMPORT, ActionService, DATA_CONTEXT_MENU, menuExtractItems, MenuService } from '@cloudbeaver/core-view';
|
||||
import {
|
||||
DATA_CONTEXT_DV_DDM,
|
||||
DATA_CONTEXT_DV_DDM_RESULT_INDEX,
|
||||
DATA_VIEWER_DATA_MODEL_ACTIONS_MENU,
|
||||
DatabaseEditAction,
|
||||
} from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { DataImportDialogLazy } from './DataImportDialog/DataImportDialogLazy';
|
||||
import { DataImportService } from './DataImportService';
|
||||
|
||||
@injectable()
|
||||
export class DataImportBootstrap extends Bootstrap {
|
||||
constructor(
|
||||
private readonly menuService: MenuService,
|
||||
private readonly actionService: ActionService,
|
||||
private readonly commonDialogService: CommonDialogService,
|
||||
private readonly dataImportService: DataImportService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
register() {
|
||||
this.actionService.addHandler({
|
||||
id: 'data-import-base-handler',
|
||||
isActionApplicable(context, action) {
|
||||
const menu = context.hasValue(DATA_CONTEXT_MENU, DATA_VIEWER_DATA_MODEL_ACTIONS_MENU);
|
||||
const model = context.tryGet(DATA_CONTEXT_DV_DDM);
|
||||
const resultIndex = context.tryGet(DATA_CONTEXT_DV_DDM_RESULT_INDEX);
|
||||
|
||||
if (!menu || !model || resultIndex === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action === ACTION_IMPORT) {
|
||||
if (model.isReadonly(resultIndex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const editor = model.source.getActionImplementation(resultIndex, DatabaseEditAction);
|
||||
return editor?.hasFeature('add') === true;
|
||||
}
|
||||
|
||||
return [ACTION_IMPORT].includes(action);
|
||||
},
|
||||
isDisabled(context) {
|
||||
const model = context.get(DATA_CONTEXT_DV_DDM);
|
||||
const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX);
|
||||
|
||||
return model.isLoading() || model.isDisabled(resultIndex) || !model.getResult(resultIndex);
|
||||
},
|
||||
getActionInfo(_, action) {
|
||||
if (action === ACTION_IMPORT) {
|
||||
return { ...action.info, icon: '/icons/data-import.svg' };
|
||||
}
|
||||
|
||||
return action.info;
|
||||
},
|
||||
handler: async (context, action) => {
|
||||
const model = context.get(DATA_CONTEXT_DV_DDM);
|
||||
const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX);
|
||||
|
||||
if (action === ACTION_IMPORT) {
|
||||
const result = model.getResult(resultIndex);
|
||||
|
||||
if (!result?.id) {
|
||||
throw new Error('Result must be provided');
|
||||
}
|
||||
|
||||
const executionContext = model.source.executionContext?.context;
|
||||
|
||||
if (!executionContext) {
|
||||
throw new Error('Execution context must be provided');
|
||||
}
|
||||
|
||||
const state = await this.commonDialogService.open(DataImportDialogLazy, { tableName: model.name ?? model.id });
|
||||
|
||||
if (state === DialogueStateResult.Rejected || state === DialogueStateResult.Resolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await this.dataImportService.importData(
|
||||
executionContext.connectionId,
|
||||
executionContext.id,
|
||||
executionContext.projectId,
|
||||
result.id,
|
||||
state.processorId,
|
||||
state.file,
|
||||
);
|
||||
|
||||
if (success) {
|
||||
await model.refresh();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
menus: [DATA_VIEWER_DATA_MODEL_ACTIONS_MENU],
|
||||
isApplicable: () => !this.dataImportService.disabled,
|
||||
getItems(_, items) {
|
||||
return [...items, ACTION_IMPORT];
|
||||
},
|
||||
orderItems(_, items) {
|
||||
const extracted = menuExtractItems(items, [ACTION_IMPORT]);
|
||||
return [...items, ...extracted];
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { observer } from 'mobx-react-lite';
|
||||
|
||||
import {
|
||||
Button,
|
||||
CommonDialogBody,
|
||||
CommonDialogFooter,
|
||||
CommonDialogHeader,
|
||||
CommonDialogWrapper,
|
||||
Container,
|
||||
Fill,
|
||||
useTranslate,
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import type { DialogComponent } from '@cloudbeaver/core-dialogs';
|
||||
|
||||
import { DataImportFileSelector } from './DataImportFileSelector';
|
||||
import { EDataImportDialogStep } from './EDataImportDialogStep';
|
||||
import type { IDataImportDialogState } from './IDataImportDialogState';
|
||||
import { ImportProcessorList } from './ImportProcessorList';
|
||||
import { useDataImportDialog } from './useDataImportDialog';
|
||||
|
||||
export interface IDataImportDialogResult {
|
||||
file: File;
|
||||
processorId: string;
|
||||
}
|
||||
|
||||
export interface IDataImportDialogPayload {
|
||||
tableName: string;
|
||||
initialState?: IDataImportDialogState;
|
||||
}
|
||||
|
||||
export const DataImportDialog: DialogComponent<IDataImportDialogPayload, IDataImportDialogResult> = observer(function DataImportDialog({
|
||||
payload,
|
||||
resolveDialog,
|
||||
rejectDialog,
|
||||
}) {
|
||||
const translate = useTranslate();
|
||||
const dialog = useDataImportDialog(payload.initialState);
|
||||
|
||||
let title = translate('plugin_data_import_title');
|
||||
let icon = '/icons/data-import.svg';
|
||||
|
||||
if (dialog.state.step === EDataImportDialogStep.File && dialog.state.selectedProcessor) {
|
||||
title += ` (${dialog.state.selectedProcessor.name ?? dialog.state.selectedProcessor.id})`;
|
||||
icon = dialog.state.selectedProcessor.icon ?? icon;
|
||||
}
|
||||
|
||||
return (
|
||||
<CommonDialogWrapper size="large" fixedSize>
|
||||
<CommonDialogHeader title={title} subTitle={payload.tableName} icon={icon} onReject={rejectDialog} />
|
||||
<CommonDialogBody noBodyPadding>
|
||||
{dialog.state.step === EDataImportDialogStep.Processor && <ImportProcessorList onSelect={dialog.selectProcessor} />}
|
||||
{dialog.state.step === EDataImportDialogStep.File && <DataImportFileSelector state={dialog.state} onDelete={dialog.deleteFile} />}
|
||||
</CommonDialogBody>
|
||||
|
||||
<CommonDialogFooter>
|
||||
<Button type="button" mod={['outlined']} onClick={rejectDialog}>
|
||||
{translate('ui_processing_cancel')}
|
||||
</Button>
|
||||
<Fill />
|
||||
{dialog.state.step === EDataImportDialogStep.File && (
|
||||
<Container noWrap keepSize gap>
|
||||
<Button type="button" mod={['outlined']} onClick={dialog.stepBack}>
|
||||
{translate('ui_stepper_back')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
mod={['unelevated']}
|
||||
disabled={!dialog.state.file || !dialog.state.selectedProcessor}
|
||||
onClick={() => resolveDialog({ file: dialog.state.file!, processorId: dialog.state.selectedProcessor!.id })}
|
||||
>
|
||||
{translate('ui_import')}
|
||||
</Button>
|
||||
</Container>
|
||||
)}
|
||||
</CommonDialogFooter>
|
||||
</CommonDialogWrapper>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { importLazyComponent } from '@cloudbeaver/core-blocks';
|
||||
|
||||
export const DataImportDialogLazy = importLazyComponent(() => import('./DataImportDialog').then(m => m.DataImportDialog));
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
.tableColumnValue {
|
||||
height: 36px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { observer } from 'mobx-react-lite';
|
||||
|
||||
import { ActionIconButton, Container, s, TableColumnValue, TableItem, useS } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import classes from './DataImportFileItem.m.css';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
name: string;
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
className?: string;
|
||||
onDelete: (id: string) => void;
|
||||
}
|
||||
|
||||
export const DataImportFileItem = observer<Props>(function DataImportFileItem({ id, name, tooltip, disabled, className, onDelete }) {
|
||||
const styles = useS(classes);
|
||||
|
||||
return (
|
||||
<TableItem item={id} title={tooltip} disabled={disabled} selectDisabled={disabled} className={className}>
|
||||
<TableColumnValue className={s(styles, { tableColumnValue: true })}>{name}</TableColumnValue>
|
||||
<TableColumnValue className={s(styles, { tableColumnValue: true })} flex centerContent>
|
||||
<Container zeroBasis />
|
||||
<ActionIconButton name="cross-bold" onClick={() => onDelete(id)} />
|
||||
</TableColumnValue>
|
||||
</TableItem>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
.columnHeader {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { observer } from 'mobx-react-lite';
|
||||
|
||||
import { Container, Group, InputFiles, s, Table, TableBody, TableColumnHeader, TableHeader, useS, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import { DataImportFileItem } from './DataImportFileItem';
|
||||
import classes from './DataImportFileSelector.m.css';
|
||||
import type { IDataImportDialogState } from './IDataImportDialogState';
|
||||
|
||||
interface Props {
|
||||
state: IDataImportDialogState;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
export const DataImportFileSelector = observer<Props>(function DataImportFileSelector({ state, onDelete }) {
|
||||
const translate = useTranslate();
|
||||
const style = useS(classes);
|
||||
|
||||
function handleFileSelect(value: FileList | null) {
|
||||
if (value) {
|
||||
state.file = value[0];
|
||||
}
|
||||
}
|
||||
|
||||
const extension = state.selectedProcessor?.fileExtension ? `.${state.selectedProcessor.fileExtension}` : undefined;
|
||||
|
||||
return (
|
||||
<Group box overflow>
|
||||
<Table>
|
||||
<TableHeader fixed>
|
||||
<TableColumnHeader className={s(style, { columnHeader: true })} heightBig>
|
||||
{translate('ui_name')}
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader className={s(style, { columnHeader: true })} heightBig flex>
|
||||
<Container zeroBasis />
|
||||
<InputFiles accept={extension} hideTags keepSize onChange={handleFileSelect} />
|
||||
</TableColumnHeader>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{state.file && <DataImportFileItem id={state.file.name} name={state.file.name} tooltip={state.file.name} onDelete={onDelete} />}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Group>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
export enum EDataImportDialogStep {
|
||||
Processor,
|
||||
File,
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { DataTransferProcessorInfo } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { EDataImportDialogStep } from './EDataImportDialogStep';
|
||||
|
||||
export interface IDataImportDialogState {
|
||||
step: EDataImportDialogStep;
|
||||
file: File | null;
|
||||
selectedProcessor: DataTransferProcessorInfo | null;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
.staticImage {
|
||||
display: flex;
|
||||
width: 24px;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { observer } from 'mobx-react-lite';
|
||||
|
||||
import { ItemList, ListItem, ListItemDescription, ListItemIcon, ListItemName, StaticImage, useResource } from '@cloudbeaver/core-blocks';
|
||||
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
|
||||
import type { DataTransferProcessorInfo } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { DataImportProcessorsResource } from '../DataImportProcessorsResource';
|
||||
import classes from './ImportProcessorList.m.css';
|
||||
|
||||
interface Props {
|
||||
onSelect: (processor: DataTransferProcessorInfo) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ImportProcessorList = observer<Props>(function ImportProcessorList({ onSelect, className }) {
|
||||
const dataImportProcessorsResource = useResource(ImportProcessorList, DataImportProcessorsResource, CachedMapAllKey, { forceSuspense: true });
|
||||
|
||||
return (
|
||||
<ItemList className={className}>
|
||||
{dataImportProcessorsResource.resource.values.map(processor => (
|
||||
<ListItem key={processor.id} onClick={() => onSelect(processor)}>
|
||||
<ListItemIcon>
|
||||
<StaticImage className={classes.staticImage} icon={processor.icon} />
|
||||
</ListItemIcon>
|
||||
<ListItemName>{processor.name}</ListItemName>
|
||||
<ListItemDescription title={processor.description}>{processor.description}</ListItemDescription>
|
||||
</ListItem>
|
||||
))}
|
||||
</ItemList>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { action, observable } from 'mobx';
|
||||
|
||||
import { useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import type { DataTransferProcessorInfo } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { EDataImportDialogStep } from './EDataImportDialogStep';
|
||||
import type { IDataImportDialogState } from './IDataImportDialogState';
|
||||
|
||||
interface IDialog {
|
||||
state: IDataImportDialogState;
|
||||
stepBack: () => void;
|
||||
selectProcessor: (processor: DataTransferProcessorInfo) => void;
|
||||
deleteFile: () => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
const DEFAULT_STATE_GETTER: () => IDataImportDialogState = () => ({
|
||||
step: EDataImportDialogStep.Processor,
|
||||
file: null,
|
||||
selectedProcessor: null,
|
||||
});
|
||||
|
||||
export function useDataImportDialog(initialState?: IDataImportDialogState) {
|
||||
const dialog = useObservableRef<IDialog>(
|
||||
() => ({
|
||||
state: initialState ?? DEFAULT_STATE_GETTER(),
|
||||
stepBack() {
|
||||
if (this.state.step === EDataImportDialogStep.File) {
|
||||
this.state.step = EDataImportDialogStep.Processor;
|
||||
}
|
||||
},
|
||||
selectProcessor(processor: DataTransferProcessorInfo) {
|
||||
if (this.state.selectedProcessor && this.state.selectedProcessor.id !== processor.id) {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
this.state.selectedProcessor = processor;
|
||||
this.state.step = EDataImportDialogStep.File;
|
||||
},
|
||||
deleteFile() {
|
||||
this.state.file = null;
|
||||
},
|
||||
reset() {
|
||||
this.state = DEFAULT_STATE_GETTER();
|
||||
},
|
||||
}),
|
||||
{ state: observable, stepBack: action.bound, selectProcessor: action.bound, deleteFile: action.bound, reset: action.bound },
|
||||
false,
|
||||
);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { injectable } from '@cloudbeaver/core-di';
|
||||
import { CachedMapAllKey, CachedMapResource, resourceKeyList } from '@cloudbeaver/core-resource';
|
||||
import { ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { DataTransferProcessorInfo, GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
|
||||
@injectable()
|
||||
export class DataImportProcessorsResource extends CachedMapResource<string, DataTransferProcessorInfo> {
|
||||
constructor(
|
||||
private readonly graphQLService: GraphQLService,
|
||||
serverConfigResource: ServerConfigResource,
|
||||
) {
|
||||
super(() => new Map());
|
||||
this.sync(
|
||||
serverConfigResource,
|
||||
() => {},
|
||||
() => CachedMapAllKey,
|
||||
);
|
||||
}
|
||||
|
||||
protected async loader(): Promise<Map<string, DataTransferProcessorInfo>> {
|
||||
const { processors } = await this.graphQLService.sdk.getDataTransferImportProcessors();
|
||||
|
||||
this.replace(resourceKeyList(processors.map(processor => processor.id)), processors);
|
||||
|
||||
return this.data;
|
||||
}
|
||||
|
||||
protected validateKey(key: string): boolean {
|
||||
return typeof key === 'string';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { computed, makeObservable } from 'mobx';
|
||||
|
||||
import { ProcessSnackbar } from '@cloudbeaver/core-blocks';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { EAdminPermission, SessionPermissionsResource } from '@cloudbeaver/core-root';
|
||||
import { AsyncTaskInfoService, GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
import { getProgressPercent } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { DataImportSettingsService } from './DataImportSettingsService';
|
||||
|
||||
@injectable()
|
||||
export class DataImportService {
|
||||
get disabled() {
|
||||
if (this.sessionPermissionsResource.has(EAdminPermission.admin)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.dataImportSettingsService.disabled;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly dataImportSettingsService: DataImportSettingsService,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly graphQLService: GraphQLService,
|
||||
private readonly asyncTaskInfoService: AsyncTaskInfoService,
|
||||
private readonly sessionPermissionsResource: SessionPermissionsResource,
|
||||
) {
|
||||
makeObservable(this, {
|
||||
disabled: computed,
|
||||
});
|
||||
}
|
||||
|
||||
async importData(connectionId: string, contextId: string, projectId: string, resultsId: string, processorId: string, file: File) {
|
||||
const { controller, notification } = this.notificationService.processNotification(
|
||||
() => ProcessSnackbar,
|
||||
{},
|
||||
{ title: 'plugin_data_import_process_title', message: file.name },
|
||||
);
|
||||
|
||||
try {
|
||||
const result = await this.graphQLService.sdk.uploadResultData(connectionId, contextId, projectId, resultsId, processorId, file, event => {
|
||||
if (event.total !== undefined) {
|
||||
const percentCompleted = getProgressPercent(event.loaded, event.total);
|
||||
|
||||
if (notification.message) {
|
||||
controller.setMessage(`${percentCompleted}%\n${notification.message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const task = this.asyncTaskInfoService.create(async () => {
|
||||
const { taskInfo } = await this.graphQLService.sdk.getAsyncTaskInfo({ taskId: result.id, removeOnFinish: false });
|
||||
return taskInfo;
|
||||
});
|
||||
|
||||
controller.setMessage('plugin_data_import_process_file_processing_step_message');
|
||||
await this.asyncTaskInfoService.run(task);
|
||||
|
||||
controller.resolve('plugin_data_import_process_success');
|
||||
return true;
|
||||
} catch (exception: any) {
|
||||
controller.reject(exception, 'plugin_data_import_process_fail');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { injectable } from '@cloudbeaver/core-di';
|
||||
import { SettingsProvider, SettingsProviderService } from '@cloudbeaver/core-settings';
|
||||
import { schema, schemaExtra } from '@cloudbeaver/core-utils';
|
||||
|
||||
const defaultSettings = schema.object({
|
||||
'plugin.data-import.disabled': schemaExtra.stringedBoolean().default(false),
|
||||
});
|
||||
|
||||
export type DataImportSettings = schema.infer<typeof defaultSettings>;
|
||||
|
||||
@injectable()
|
||||
export class DataImportSettingsService {
|
||||
get disabled(): boolean {
|
||||
return this.settings.getValue('plugin.data-import.disabled');
|
||||
}
|
||||
readonly settings: SettingsProvider<typeof defaultSettings>;
|
||||
|
||||
constructor(private readonly settingsProviderService: SettingsProviderService) {
|
||||
this.settings = this.settingsProviderService.createSettings(defaultSettings);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { LocalizationService } from '@cloudbeaver/core-localization';
|
||||
|
||||
@injectable()
|
||||
export class LocaleService extends Bootstrap {
|
||||
constructor(private readonly localizationService: LocalizationService) {
|
||||
super();
|
||||
}
|
||||
|
||||
register(): void | Promise<void> {
|
||||
this.localizationService.addProvider(this.provider.bind(this));
|
||||
}
|
||||
|
||||
load(): void | Promise<void> {}
|
||||
|
||||
private async provider(locale: string) {
|
||||
switch (locale) {
|
||||
case 'ru':
|
||||
return (await import('./locales/ru')).default;
|
||||
case 'it':
|
||||
return (await import('./locales/it')).default;
|
||||
case 'zh':
|
||||
return (await import('./locales/zh')).default;
|
||||
default:
|
||||
return (await import('./locales/en')).default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { dataImportPluginManifest } from './manifest';
|
||||
|
||||
export * from './DataImportSettingsService';
|
||||
@@ -0,0 +1,7 @@
|
||||
export default [
|
||||
['plugin_data_import_title', 'Data import'],
|
||||
['plugin_data_import_process_title', 'Importing data...'],
|
||||
['plugin_data_import_process_success', 'Data imported successfully'],
|
||||
['plugin_data_import_process_fail', 'Data import failed'],
|
||||
['plugin_data_import_process_file_processing_step_message', 'File uploaded, processing...'],
|
||||
];
|
||||
@@ -0,0 +1,7 @@
|
||||
export default [
|
||||
['plugin_data_import_title', 'Data import'],
|
||||
['plugin_data_import_process_title', 'Importing data...'],
|
||||
['plugin_data_import_process_success', 'Data imported successfully'],
|
||||
['plugin_data_import_process_fail', 'Data import failed'],
|
||||
['plugin_data_import_process_file_processing_step_message', 'File uploaded, processing...'],
|
||||
];
|
||||
@@ -0,0 +1,7 @@
|
||||
export default [
|
||||
['plugin_data_import_title', 'Импорт данных'],
|
||||
['plugin_data_import_process_title', 'Импорт данных...'],
|
||||
['plugin_data_import_process_success', 'Данные успешно импортированы'],
|
||||
['plugin_data_import_process_fail', 'Ошибка импорта данных'],
|
||||
['plugin_data_import_process_file_processing_step_message', 'Файл загружен, обработка...'],
|
||||
];
|
||||
@@ -0,0 +1,7 @@
|
||||
export default [
|
||||
['plugin_data_import_title', 'Data import'],
|
||||
['plugin_data_import_process_title', 'Importing data...'],
|
||||
['plugin_data_import_process_success', 'Data imported successfully'],
|
||||
['plugin_data_import_process_fail', 'Data import failed'],
|
||||
['plugin_data_import_process_file_processing_step_message', 'File uploaded, processing...'],
|
||||
];
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { DataImportBootstrap } from './DataImportBootstrap';
|
||||
import { DataImportProcessorsResource } from './DataImportProcessorsResource';
|
||||
import { DataImportService } from './DataImportService';
|
||||
import { DataImportSettingsService } from './DataImportSettingsService';
|
||||
import { LocaleService } from './LocaleService';
|
||||
|
||||
export const dataImportPluginManifest: PluginManifest = {
|
||||
info: {
|
||||
name: 'Data Import Plugin',
|
||||
},
|
||||
|
||||
providers: [LocaleService, DataImportSettingsService, DataImportBootstrap, DataImportService, DataImportProcessorsResource],
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../core-blocks/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-connections/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-di/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-dialogs/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-events/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-navigation-tree/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-resource/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-ui/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-view/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../plugin-data-viewer/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../plugin-datasource-context-switch/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../plugin-sql-editor/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../plugin-top-app-bar/tsconfig.json"
|
||||
}
|
||||
],
|
||||
"include": [
|
||||
"__custom_mocks__/**/*",
|
||||
"src/**/*",
|
||||
"src/**/*.json",
|
||||
"src/**/*.css",
|
||||
"src/**/*.scss"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules",
|
||||
"lib/**/*",
|
||||
"dist/**/*"
|
||||
]
|
||||
}
|
||||
@@ -41,6 +41,7 @@
|
||||
"@cloudbeaver/plugin-connections": "~0.1.0",
|
||||
"@cloudbeaver/plugin-connections-administration": "~0.1.0",
|
||||
"@cloudbeaver/plugin-data-export": "~0.1.0",
|
||||
"@cloudbeaver/plugin-data-import": "~0.1.0",
|
||||
"@cloudbeaver/plugin-data-spreadsheet-new": "~0.1.0",
|
||||
"@cloudbeaver/plugin-data-viewer": "~0.1.0",
|
||||
"@cloudbeaver/plugin-data-viewer-result-set-grouping": "~0.1.0",
|
||||
|
||||
@@ -13,6 +13,7 @@ import connectionTemplate from '@cloudbeaver/plugin-connection-template';
|
||||
import connectionPlugin from '@cloudbeaver/plugin-connections';
|
||||
import connectionAdministration from '@cloudbeaver/plugin-connections-administration';
|
||||
import { dataExportManifest } from '@cloudbeaver/plugin-data-export';
|
||||
import { dataImportPluginManifest } from '@cloudbeaver/plugin-data-import';
|
||||
import { dataSpreadsheetNewManifest } from '@cloudbeaver/plugin-data-spreadsheet-new';
|
||||
import { dataViewerManifest } from '@cloudbeaver/plugin-data-viewer';
|
||||
import { dvResultSetGroupingPlugin } from '@cloudbeaver/plugin-data-viewer-result-set-grouping';
|
||||
@@ -54,9 +55,9 @@ import topAppBar from '@cloudbeaver/plugin-top-app-bar';
|
||||
import { administrationTopAppBarPlugin } from '@cloudbeaver/plugin-top-app-bar-administration';
|
||||
import userProfile from '@cloudbeaver/plugin-user-profile';
|
||||
import userProfileAdministration from '@cloudbeaver/plugin-user-profile-administration';
|
||||
import { userProfileSettingsPlugin } from '@cloudbeaver/plugin-user-profile-settings';
|
||||
import version from '@cloudbeaver/plugin-version';
|
||||
import versionUpdate from '@cloudbeaver/plugin-version-update-administration';
|
||||
import { userProfileSettingsPlugin } from '@cloudbeaver/plugin-user-profile-settings';
|
||||
|
||||
import { defaultProductManifest } from './manifest';
|
||||
|
||||
@@ -73,6 +74,7 @@ const PLUGINS: PluginManifest[] = [
|
||||
connectionTemplate,
|
||||
connectionSearch,
|
||||
dataExportManifest,
|
||||
dataImportPluginManifest,
|
||||
dataViewerManifest,
|
||||
dvResultSetGroupingPlugin,
|
||||
gisViewer,
|
||||
|
||||
@@ -54,6 +54,9 @@
|
||||
{
|
||||
"path": "../plugin-data-export/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../plugin-data-import/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../plugin-data-spreadsheet-new/tsconfig.json"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user