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:
alex
2024-04-02 16:51:27 +00:00
committed by GitHub
co-authored by DenisSinelnikov denis.sinelnikov sergeyteleshev Evgenia Bezborodova
parent 101d26158b
commit 32b4e4ec19
45 changed files with 1240 additions and 10 deletions
@@ -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);
}
@@ -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 {
@@ -53,6 +53,9 @@ extend type Query {
# Available transfer processors
dataTransferAvailableStreamProcessors: [ DataTransferProcessorInfo! ]!
# Available transfer processors for import file
dataTransferAvailableImportStreamProcessors: [ DataTransferProcessorInfo! ]!
dataTransferDefaultExportSettings: DataTransferDefaultExportSettings!
dataTransferExportDataFromContainer(
@@ -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,
@@ -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/*"
);
}
}
@@ -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);
}
}
}
}
@@ -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),
};
}
@@ -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));
}
+1
View File
@@ -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',
});
+1
View File
@@ -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",
+3 -1
View File
@@ -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"
},