diff --git a/package.json b/package.json
index ae7eb87b..7790ba75 100644
--- a/package.json
+++ b/package.json
@@ -578,7 +578,7 @@
},
{
"title": "Refresh Cluster Overview",
- "command":"vscode-couchbase.refreshClusterOverview",
+ "command": "vscode-couchbase.refreshClusterOverview",
"icon": "$(refresh)",
"category": "Couchbase"
},
@@ -586,6 +586,11 @@
"title": "Check and Create Primary Index",
"command": "vscode-couchbase.checkAndCreatePrimaryIndex",
"category": "Couchbase"
+ },
+ {
+ "command": "vscode-couchbase.tools.DDLExport",
+ "title": "DDL Export",
+ "category": "Couchbase"
}
],
"keybindings": [
@@ -755,6 +760,10 @@
{
"command": "vscode-couchbase.tools.dataExport",
"when": "false"
+ },
+ {
+ "command": "vscode-couchbase.tools.DDLExport",
+ "when": "false"
}
],
"view/item/context": [
@@ -791,6 +800,10 @@
"submenu": "vscode-couchbase.toolsMenu",
"when": "view == couchbase && viewItem == active_connection"
},
+ {
+ "submenu": "vscode-couchbase.toolsMenu",
+ "when": "view == couchbase && viewItem == active_connection"
+ },
{
"command": "vscode-couchbase.useClusterConnection",
"when": "view == couchbase && viewItem == connection"
@@ -904,6 +917,10 @@
{
"command": "vscode-couchbase.tools.dataExport",
"group": "navigation"
+ },
+ {
+ "command": "vscode-couchbase.tools.DDLExport",
+ "group": "navigation"
}
]
},
diff --git a/src/commands/extensionCommands/commands.ts b/src/commands/extensionCommands/commands.ts
index febae97d..09dea4f4 100644
--- a/src/commands/extensionCommands/commands.ts
+++ b/src/commands/extensionCommands/commands.ts
@@ -55,4 +55,5 @@ export namespace Commands {
export const refreshClusterOverview: string = "vscode-couchbase.refreshClusterOverview";
export const checkAndCreatePrimaryIndex: string = "vscode-couchbase.checkAndCreatePrimaryIndex";
export const dataExport: string = "vscode-couchbase.tools.dataExport";
+ export const ddlExport: string = "vscode-couchbase.tools.DDLExport";
}
diff --git a/src/commands/tools/ddlExport/ddlExport.ts b/src/commands/tools/ddlExport/ddlExport.ts
new file mode 100644
index 00000000..c385fd83
--- /dev/null
+++ b/src/commands/tools/ddlExport/ddlExport.ts
@@ -0,0 +1,218 @@
+import * as vscode from "vscode";
+import * as path from 'path';
+import { IConnection } from "../../../types/IConnection";
+import { getActiveConnection } from "../../../util/connections";
+import { logger } from "../../../logger/logger";
+import { getLoader } from "../../../webViews/loader.webview";
+import { ddlExportWebview } from "../../../webViews/tools/ddlExport.webview";
+import { QueryIndex, ScopeSpec } from "couchbase";
+import { getCurrentDateTime } from "../../../util/util";
+import * as fs from "fs";
+import { getIndexDefinition } from "../../../util/indexUtils";
+
+const validateFormData = (formData: any): string => {
+ const errors = [];
+
+ if (!formData.bucket) {
+ errors.push("Please select a bucket");
+ } else {
+ if (formData.scopes.length === 0) {
+ errors.push("Select one or more scopes");
+ }
+ }
+
+ if (!formData.fileDestination.trim()) {
+ errors.push("Please inform the file destination folder");
+ }
+
+ if (errors.length > 0) {
+ return errors.join("
");
+ }
+
+ return "";
+};
+
+class DDLExport {
+ private static async exportScope(bucket: string, scope: ScopeSpec, includeIndexes: boolean): Promise {
+ const ddlExportData: string[] = [];
+ const scopePrefix = "`" + bucket + "`.`" + scope.name + "`";
+
+ if ("_default" !== scope.name) {
+ ddlExportData.push("CREATE SCOPE " + scopePrefix + ";");
+ }
+ ddlExportData.push("\n");
+ const connection = getActiveConnection();
+ if (!connection) {
+ return "";
+ }
+
+ for (const collectionSpec of scope.collections) {
+ ddlExportData.push("\n");
+ ddlExportData.push("/* DDL for collection " + scope.name + "." + collectionSpec.name + " */");
+ ddlExportData.push("\n");
+ if ("_default" !== collectionSpec.name) {
+ ddlExportData.push("CREATE COLLECTION " + scopePrefix + ".`" + collectionSpec.name + "`; \n");
+ }
+ if (includeIndexes) {
+ const result = await listIndexes(connection, bucket, scope.name, collectionSpec.name);
+ ddlExportData.push(result?.map(index => getIndexDefinition(index)).join("; \n")!);
+ if (result!.length > 1) {
+ ddlExportData.push("; \n");
+ }
+ }
+ ddlExportData.push("\n");
+ }
+
+ ddlExportData.push("\n\n");
+
+ return ddlExportData.join("");
+ }
+
+ public async exportScope(bucket: string, scopes: string[], filePath: string, includeIndexes: boolean): Promise {
+ try {
+ const connection = getActiveConnection();
+ if (!connection) {
+ return;
+ }
+
+ const ifAllScopes = scopes.includes("All Scopes");
+ const ddlExportData: string[] = [];
+ const allScopes = await connection?.cluster?.bucket(bucket).collections().getAllScopes()!;
+ const selectedScopes: ScopeSpec[] = ifAllScopes ? allScopes : allScopes.filter(a => scopes.includes(a.name));
+
+ for (const scope of selectedScopes) {
+ ddlExportData.push("/*************************/ \n");
+ ddlExportData.push("/** Scope " + scope.name + " */ \n");
+ ddlExportData.push("/*************************/ \n");
+ ddlExportData.push(await DDLExport.exportScope(bucket, scope, includeIndexes));
+ ddlExportData.push("\n");
+ }
+
+ let fileName = bucket + "_" + (ifAllScopes ? "_all_" : DDLExport.getScopesFileName(scopes)) + getCurrentDateTime() + ".sqlpp";
+ if (!filePath.endsWith("/")) {
+ fileName = "/" + fileName;
+ }
+ const fullPath = filePath + fileName;
+ fs.writeFile(fullPath, ddlExportData.join(''), function (err) {
+ if (err) { throw err; }
+ vscode.window.showInformationMessage("DDL Export: File exported successfully");
+ });
+
+ } catch (e) {
+ logger.error("An error occurred while writing to export the DDL: " + e);
+ }
+ };
+
+ public static getScopesFileName(scopes: string[]): string {
+ if (scopes.length === 1) {
+ return scopes[0] + "_";
+ } else {
+ return scopes.length + "_scopes_";
+ }
+ }
+};
+
+export const listIndexes = async (connection: IConnection, bucket: string, scope: string, collection: string): Promise => {
+ if (connection) {
+ try {
+ const result = await connection?.cluster?.queryIndexes().getAllIndexes(bucket, { scopeName: scope, collectionName: collection });
+ return result;
+ } catch {
+ return [];
+ }
+ }
+
+};
+
+export const ddlExport = async () => {
+ const connection = getActiveConnection();
+ if (!connection) {
+ return;
+ }
+ const currentPanel = vscode.window.createWebviewPanel(
+ "ddlExport",
+ "DDL Export",
+ vscode.ViewColumn.One,
+ {
+ enableScripts: true,
+ enableForms: true,
+ }
+ );
+ currentPanel.iconPath = {
+ dark: vscode.Uri.file(
+ path.join(__filename, "..", "..", "images", "dark", "export_dark.svg")
+ ),
+ light: vscode.Uri.file(
+ path.join(__filename, "..", "..", "images", "light", "export_light.svg")
+ ),
+ };
+ currentPanel.webview.html = getLoader("DDL Export");
+
+ // Get all buckets
+ const buckets = await connection.cluster?.buckets().getAllBuckets();
+ if (buckets === undefined) {
+ vscode.window.showErrorMessage("Buckets not found");
+ return;
+ }
+
+ const bucketNameArr: string[] = buckets.map(b => b.name);
+ try {
+ currentPanel.webview.html = await ddlExportWebview(bucketNameArr);
+ currentPanel.webview.onDidReceiveMessage(async (message) => {
+ switch (message.command) {
+ case "vscode-couchbase.tools.DDLExport.runExport":
+ const formData = message.data;
+ const validationError = validateFormData(formData);
+ if (validationError === "") {
+ const ddl = new DDLExport();
+ ddl.exportScope(formData.bucket,
+ formData.scopes, formData.fileDestination, formData.includeIndexes);
+ } else {
+ currentPanel.webview.postMessage({
+ command: "vscode-couchbase.tools.DDLExport.formValidationError",
+ error: validationError,
+ });
+ }
+ break;
+ case "vscode-couchbase.tools.DDLExport.getScopes":
+ const scopes = await connection.cluster
+ ?.bucket(message.bucketId)
+ .collections()
+ .getAllScopes();
+ if (scopes === undefined) {
+ vscode.window.showErrorMessage("Scopes are undefined");
+ break;
+ }
+
+ currentPanel.webview.postMessage({
+ command: "vscode-couchbase.tools.DDLExport.scopesInfo",
+ scopes: scopes,
+ });
+ break;
+ case "vscode-couchbase.tools.DDLExport.getFolder":
+ const options: vscode.OpenDialogOptions = {
+ canSelectMany: false,
+ openLabel: "Choose Destination Folder",
+ canSelectFiles: false,
+ canSelectFolders: true,
+ };
+
+ vscode.window.showOpenDialog(options).then((fileUri) => {
+ if (fileUri && fileUri[0]) {
+ currentPanel.webview.postMessage({
+ command: "vscode-couchbase.tools.DDLExport.folderInfo",
+ folder: fileUri[0].fsPath,
+ });
+ }
+ });
+ break;
+ }
+ });
+ } catch (err) {
+ logger.error(`Failed to open data export webview`);
+ logger.debug(err);
+ vscode.window.showErrorMessage(
+ "Failed to open data export webview: " + err
+ );
+ }
+};
\ No newline at end of file
diff --git a/src/extension.ts b/src/extension.ts
index 21b9f490..2058e401 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -65,6 +65,7 @@ import { clearDocumentFilter } from "./commands/documents/clearDocumentFilter";
import { getClusterOverviewData } from "./util/OverviewClusterUtils/getOverviewClusterData";
import { checkAndCreatePrimaryIndex } from "./commands/indexes/checkAndCreatePrimaryIndex";
import { dataExport } from "./pages/Tools/DataExport/dataExport";
+import { ddlExport } from "./commands/tools/ddlExport/ddlExport";
export function activate(context: vscode.ExtensionContext) {
Global.setState(context.globalState);
@@ -573,6 +574,12 @@ export function activate(context: vscode.ExtensionContext) {
}
)
);
+
+ context.subscriptions.push(
+ vscode.commands.registerCommand(Commands.ddlExport, async () => {
+ ddlExport();
+ })
+ );
}
// this method is called when your extension is deactivated
diff --git a/src/pages/Tools/DataExport/dataExport.ts b/src/pages/Tools/DataExport/dataExport.ts
index 20df0d30..11978bb7 100644
--- a/src/pages/Tools/DataExport/dataExport.ts
+++ b/src/pages/Tools/DataExport/dataExport.ts
@@ -55,7 +55,7 @@ const validateFormData = (formData: any): string => {
errors.push("Please inform the file destination folder");
}
- if(!formData.threads.trim() || parseInt(formData.threads)<1){
+ if (!formData.threads.trim() || parseInt(formData.threads) < 1) {
errors.push("threads cannot be undefined or less than 1");
}
@@ -109,10 +109,10 @@ export const dataExport = async () => {
});
currentPanel.iconPath = {
dark: vscode.Uri.file(
- path.join(__filename, "..", "..", "images","dark","export_dark.svg")
+ path.join(__filename, "..", "..", "images", "dark", "export_dark.svg")
),
light: vscode.Uri.file(
- path.join(__filename, "..", "..", "images","light","export_light.svg")
+ path.join(__filename, "..", "..", "images", "light", "export_light.svg")
),
};
currentPanel.webview.html = getLoader("Data Export");
@@ -123,10 +123,8 @@ export const dataExport = async () => {
vscode.window.showErrorMessage("Buckets not found");
return;
}
- const bucketNameArr: string[] = [];
- for (let bucket of buckets) {
- bucketNameArr.push(bucket.name);
- }
+
+ const bucketNameArr: string[] = buckets.map(b => b.name);
try {
currentPanel.webview.html = await dataExportWebview(bucketNameArr);
diff --git a/src/webViews/tools/dataExport.webview.ts b/src/webViews/tools/dataExport.webview.ts
index ad49facc..c75cc8e9 100644
--- a/src/webViews/tools/dataExport.webview.ts
+++ b/src/webViews/tools/dataExport.webview.ts
@@ -19,6 +19,31 @@ export const dataExportWebview = async (buckets: string[]): Promise => {
margin-bottom: 5px;
}
+ input[type="checkbox"] {
+ appearance: none;
+ -webkit-appearance: none;
+ width: 15px;
+ height: 15px;
+ border-radius: 2px;
+ outline: none;
+ border: 1px solid #999;
+ background-color: white;
+ }
+
+ input[type="checkbox"]:checked {
+ background-color: #ea2328;
+ }
+
+ input[type="checkbox"]:checked::before {
+ content: '\\2714';
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 12px;
+ color: white;
+ height: 100%;
+ }
+
form {
max-width: 500px;
margin: 0 auto;
diff --git a/src/webViews/tools/ddlExport.webview.ts b/src/webViews/tools/ddlExport.webview.ts
new file mode 100644
index 00000000..50c2484a
--- /dev/null
+++ b/src/webViews/tools/ddlExport.webview.ts
@@ -0,0 +1,320 @@
+export const ddlExportWebview = async (buckets: string[]): Promise => {
+ return `
+
+
+
+
+
+ Data Export
+
+
+
+
+
+ DDL Export
+
+
+
+
+
+