From 931b0a21dd950fe341eaa5a2d564bfe0182bed6c Mon Sep 17 00:00:00 2001 From: Alison Hart Date: Tue, 3 Dec 2024 06:06:14 -0500 Subject: [PATCH] Add a webview to generate a devfile without ansible-creator (#1681) * Add a webview to generate a devfile without ansible-creator --- .config/dictionary.txt | 3 + .pre-commit-config.yaml | 7 + .vscodeignore | 5 + media/baseStyles/baseFormStyle.css | 10 + .../contentCreator/createDevfilePageStyle.css | 131 +++++++ package.json | 4 + .../createDevfile/devfile-template.txt | 15 + src/extension.ts | 11 + .../contentCreator/createDevfilePage.ts | 353 ++++++++++++++++++ src/features/contentCreator/types.ts | 7 + src/features/quickLinks/quickLinksView.ts | 7 + .../contentCreator/createDevfilePageApp.ts | 234 ++++++++++++ test/ui-test/contentCreatorUiTest.ts | 107 ++++++ tools/compare_devfile.sh | 22 ++ webpack.config.ts | 14 + 15 files changed, 930 insertions(+) create mode 100644 media/contentCreator/createDevfilePageStyle.css create mode 100644 resources/contentCreator/createDevfile/devfile-template.txt create mode 100644 src/features/contentCreator/createDevfilePage.ts create mode 100644 src/webview/apps/contentCreator/createDevfilePageApp.ts create mode 100755 tools/compare_devfile.sh diff --git a/.config/dictionary.txt b/.config/dictionary.txt index 22e2ad8fe..a565fef1b 100644 --- a/.config/dictionary.txt +++ b/.config/dictionary.txt @@ -3,6 +3,8 @@ Ansible Anson CFLAGS Containerfile +Devfile +Devfiles Dockal Dpkg Elio @@ -13,6 +15,7 @@ Ganesh HORIZONTALLINE IAM Jenkinsfile +KUBEDOCK Launay Maciążek Nalawade diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bd2b9cab3..c4d61e03e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,6 +14,7 @@ ci: - codecov - depcheck - eslint + - compare-devfile exclude: > (?x)^( .config/requirements.in| @@ -104,6 +105,12 @@ repos: language: system files: "codecov.yml" pass_filenames: false + + - id: compare-devfile + name: Compare extension devfile with ansible-creator + entry: tools/compare_devfile.sh + language: script + - repo: https://github.com/ScribeMD/pre-commit-hooks rev: 0.16.3 hooks: diff --git a/.vscodeignore b/.vscodeignore index 7a341c39d..6eac85581 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -23,6 +23,7 @@ !media/contentCreator/icons/ansible-logo-red.png !media/contentCreator/createAnsibleCollectionPageStyle.css !media/contentCreator/createAnsibleProjectPageStyle.css +!media/contentCreator/createDevfilePageStyle.css !media/contentCreator/welcomePageStyle.css !media/lightspeedExplorerView/style.css !media/welcomePage/lightspeed.png @@ -39,6 +40,7 @@ !out/client/webview/apps/contentCreator/welcomePageApp.js !out/client/webview/apps/contentCreator/createAnsibleCollectionPageApp.js !out/client/webview/apps/contentCreator/createAnsibleProjectPageApp.js +!out/client/webview/apps/contentCreator/createDevfilePageApp.js !out/client/webview/apps/quickLinks/quickLinksApp.js !out/server/src/server.js !package.json @@ -73,3 +75,6 @@ !media/walkthroughs/startAutomatingPlaybook/learning.png !media/walkthroughs/startAutomatingPlaybook/open-folder.png !media/walkthroughs/startAutomatingPlaybook/playbook-project.md + +# resources +!resources/contentCreator/createDevfile/devfile-template.txt diff --git a/media/baseStyles/baseFormStyle.css b/media/baseStyles/baseFormStyle.css index 1a3d07eff..0179597e3 100644 --- a/media/baseStyles/baseFormStyle.css +++ b/media/baseStyles/baseFormStyle.css @@ -18,6 +18,16 @@ body { margin: 25px 0px 50px; } +.title-description-div { + box-sizing: border-box; + display: flex; + flex-flow: column nowrap; + width: 100%; + max-width: 650px; + text-align: left; + margin: 25px 0px 40px; +} + h1 { border: none; font-size: 2.7em; diff --git a/media/contentCreator/createDevfilePageStyle.css b/media/contentCreator/createDevfilePageStyle.css new file mode 100644 index 000000000..604f14c29 --- /dev/null +++ b/media/contentCreator/createDevfilePageStyle.css @@ -0,0 +1,131 @@ +@import url(../baseStyles/baseFormStyle.css); + +.container { + display: flex; + flex-direction: column; +} + +.element { + margin-bottom: 14px; +} + +h3 { + border: none; + font-size: 1.1em; + font-style: normal; + font-weight: normal; + margin: 0; + color: var(--vscode-foreground); + white-space: wrap; +} + +.description-div { + box-sizing: border-box; + display: flex; + flex-flow: column nowrap; + width: 100%; + max-width: 650px; + text-align: left; + margin: 0px 0px 40px; +} + +vscode-text-field { + margin-top: 6px; + margin-bottom: 6px; +} + +vscode-text-area { + margin-top: 6px; + margin-bottom: 6px; +} + +.devfile-name-div { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} + +#devfile-name { + width:49%; + display:inline-block; +} + +.checkbox-div { + display: flex; /* Use flexbox */ + flex-direction: column; /* Arrange child elements vertically */ + margin-top: 22px; + margin-bottom: 10px; + width: 100%; +} + +.image-div { + display: flex; /* Use flexbox */ + flex-direction: row; /* Arrange child elements vertically */ + margin-top: 12px; + margin-bottom: 30px; + width: 100%; +} + +vscode-dropdown { + width: 400px; +} + +.full-devfile-path { + display: flex; /* Use flexbox */ + flex-direction: row; /* Arrange child elements vertically */ + color: var(--vscode-descriptionForeground); +} + +.group-buttons { + display: flex; /* Use flexbox */ + flex-direction: row; /* Arrange child elements vertically */ +} + +.p-collection-name { + font-style: italic; +} + +vscode-button { + margin: 0px 3px; +} + +vscode-checkbox i { + color: var(--vscode-descriptionForeground); + font-size: small; +} + +#ade-docs-link { + margin-left: 30px; + font-style: italic; +} + +.dropdown-container { + box-sizing: border-box; + display: flex; + flex-flow: column nowrap; + align-items: flex-start; + justify-content: flex-start; +} + +.dropdown-container label { + display: block; + color: var(--vscode-foreground); + cursor: pointer; + font-size: var(--vscode-font-size); + line-height: normal; + margin-bottom: 2px; +} + +#log-to-file-options-div { + display: none; + flex-direction: column; + border-style: dotted; + border-color: var(--focus-border); + border-width: 0.5px; + padding: 8px; +} + +.log-level-div { + margin: 4px 0px; +} diff --git a/package.json b/package.json index b4f2ec798..7d1ce338d 100644 --- a/package.json +++ b/package.json @@ -346,6 +346,10 @@ "command": "ansible.content-creator.create-ansible-project", "title": "Ansible: Create New Playbook Project" }, + { + "command": "ansible.content-creator.create-devfile", + "title": "Ansible: Create a Devfile" + }, { "command": "ansible.content-creator.create", "title": "Ansible Content Creator: Create" diff --git a/resources/contentCreator/createDevfile/devfile-template.txt b/resources/contentCreator/createDevfile/devfile-template.txt new file mode 100644 index 000000000..afb2a31fd --- /dev/null +++ b/resources/contentCreator/createDevfile/devfile-template.txt @@ -0,0 +1,15 @@ +schemaVersion: 2.2.2 +metadata: + name: {{ dev_file_name }} +components: + - name: tooling-container + container: + image: {{ dev_file_image }} + memoryRequest: 256M + memoryLimit: 6Gi + cpuRequest: 250m + cpuLimit: 2000m + args: ["tail", "-f", "/dev/null"] + env: + - name: KUBEDOCK_ENABLED + value: "true" diff --git a/src/extension.ts b/src/extension.ts index f3d3baa3e..9942ce5a7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -71,6 +71,7 @@ import { AuthProviderType, } from "./features/lightspeed/lightspeedUser"; import { PlaybookFeedbackEvent } from "./interfaces/lightspeed"; +import { CreateDevfile } from "./features/contentCreator/createDevfilePage"; export let client: LanguageClient; export let lightSpeedManager: LightSpeedManager; @@ -539,6 +540,16 @@ export async function activate(context: ExtensionContext): Promise { ), ); + // open web-view for creating devfile + context.subscriptions.push( + vscode.commands.registerCommand( + "ansible.content-creator.create-devfile", + () => { + CreateDevfile.render(context.extensionUri); + }, + ), + ); + // open ansible-creator create context.subscriptions.push( vscode.commands.registerCommand("ansible.content-creator.create", () => { diff --git a/src/features/contentCreator/createDevfilePage.ts b/src/features/contentCreator/createDevfilePage.ts new file mode 100644 index 000000000..2a56d0a79 --- /dev/null +++ b/src/features/contentCreator/createDevfilePage.ts @@ -0,0 +1,353 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import * as vscode from "vscode"; +import * as os from "os"; +import { getUri } from "../utils/getUri"; +import { getNonce } from "../utils/getNonce"; +import { DevfileFormInterface, PostMessageEvent } from "./types"; +import * as fs from "fs"; +import { SettingsManager } from "../../settings"; +import { expandPath } from "./utils"; +import { randomUUID } from "crypto"; + +export class CreateDevfile { + public static currentPanel: CreateDevfile | undefined; + private readonly _panel: vscode.WebviewPanel; + private _disposables: vscode.Disposable[] = []; + public static readonly viewType = "CreateProject"; + + private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) { + this._panel = panel; + this._panel.webview.html = this._getWebviewContent( + this._panel.webview, + extensionUri, + ); + this._setWebviewMessageListener(this._panel.webview, extensionUri); + this._panel.onDidDispose( + () => { + this.dispose(); + }, + null, + this._disposables, + ); + } + + public dispose() { + CreateDevfile.currentPanel = undefined; + + this._panel.dispose(); + + while (this._disposables.length) { + const disposable = this._disposables.pop(); + if (disposable) { + disposable.dispose(); + } + } + } + + public static render(extensionUri: vscode.Uri) { + if (CreateDevfile.currentPanel) { + CreateDevfile.currentPanel._panel.reveal(vscode.ViewColumn.One); + } else { + const panel = vscode.window.createWebviewPanel( + "create-devfile", + "Create Devfile", + vscode.ViewColumn.One, + { + enableScripts: true, + localResourceRoots: [ + vscode.Uri.joinPath(extensionUri, "out"), + vscode.Uri.joinPath(extensionUri, "media"), + ], + enableCommandUris: true, + retainContextWhenHidden: true, + }, + ); + + CreateDevfile.currentPanel = new CreateDevfile(panel, extensionUri); + } + } + + private _getWebviewContent( + webview: vscode.Webview, + extensionUri: vscode.Uri, + ) { + const codiconsUri = getUri(webview, extensionUri, [ + "media", + "codicons", + "codicon.css", + ]); + + const styleUri = getUri(webview, extensionUri, [ + "media", + "contentCreator", + "createDevfilePageStyle.css", + ]); + + const nonce = getNonce(); + + const workspaceDir = this.getWorkspaceFolder(); + + const webviewUri = getUri(webview, extensionUri, [ + "out", + "client", + "webview", + "apps", + "contentCreator", + "createDevfilePageApp.js", + ]); + + return /*html*/ ` + + + + + + + AAA + + + + + +
+

Create a devfile

+

Leverage Red Hat Openshift Dev Spaces

+
+
+

Devfiles are yaml files used for development environment customization.

Enter your project details below to utilize a devfile template designed for Red Hat OpenShift Dev Spaces.

+
+ +
+
+ + Destination directory * +
+ + + +
+
+ +
+ Ansible project name * +
+ +
+

Devfile path: 

+
+ +
+ +
+ +
+ Overwrite
Overwrite an existing devfile.
+
+ +
+ + +   Reset All + + + +   Create + +
+ +
+ +
+ Logs + +
+ + +   Clear Logs + + + +   Open Devfile + +
+
+
+ + + + + + `; + } + + private _setWebviewMessageListener( + webview: vscode.Webview, + extensionUri: vscode.Uri, + ) { + webview.onDidReceiveMessage( + async (message: any) => { + const command = message.command; + let payload; + + switch (command) { + case "open-explorer": { + payload = message.payload; + const selectedUri = await this.openExplorerDialog( + payload.selectOption, + ); + webview.postMessage({ + command: "file-uri", + arguments: { selectedUri: selectedUri }, + } as PostMessageEvent); + return; + } + case "devfile-create": + payload = message.payload as DevfileFormInterface; + await this.runDevfileCreateProcess(payload, webview, extensionUri); + return; + + case "open-devfile": + payload = message.payload; + this.openDevfile(payload.projectUrl); + return; + } + }, + undefined, + this._disposables, + ); + } + + public async openExplorerDialog( + selectOption: string, + ): Promise { + const options: vscode.OpenDialogOptions = { + canSelectMany: false, + openLabel: "Select", + canSelectFolders: selectOption === "folder", + defaultUri: vscode.Uri.parse(os.homedir()), + }; + + let selectedUri: string | undefined; + await vscode.window.showOpenDialog(options).then((fileUri) => { + if (fileUri?.[0]) { + selectedUri = fileUri[0].fsPath; + } + }); + + return selectedUri; + } + + public getWorkspaceFolder() { + let folder: string = ""; + if (vscode.workspace.workspaceFolders) { + folder = vscode.workspace.workspaceFolders[0].uri.path; + } + return folder; + } + + public async runDevfileCreateProcess( + payload: DevfileFormInterface, + webView: vscode.Webview, + extensionUri: vscode.Uri, + ) { + const { destinationPath, name, image, isOverwritten } = payload; + let commandResult: string; + let message: string; + let commandOutput = ""; + + commandOutput += `------------------------------------ devfile generation logs ------------------------------------\n`; + + const destinationPathUrl = `${destinationPath}/devfile.yaml`; + + const devfileExists = fs.existsSync(expandPath(destinationPathUrl)); + + if (devfileExists && !isOverwritten) { + message = `Error: Devfile already exists at ${destinationPathUrl} and was not overwritten. Use the 'Overwrite' option to overwrite the existing file.`; + commandResult = "failed"; + } else { + commandResult = this.createDevfile( + destinationPathUrl, + name, + image, + extensionUri, + ); + if (commandResult === "failed") { + message = + "ERROR: Could not create devfile. Please check that your destination path exists and write permissions are configured for it."; + } else { + message = `Creating new devfile at ${destinationPathUrl}`; + } + } + commandOutput += message; + console.debug(message); + + const extSettings = new SettingsManager(); + await extSettings.initialize(); + + await webView.postMessage({ + command: "execution-log", + arguments: { + commandOutput: commandOutput, + projectUrl: destinationPathUrl, + status: commandResult, + }, + } as PostMessageEvent); + + // if (commandResult === "passed") { + // const selection = await vscode.window.showInformationMessage( + // `Devfile created at: ${destinationPathUrl}`, + // `Open devfile ↗`, + // ); + // if (selection === "Open devfile ↗") { + // this.openDevfile(destinationPathUrl); + // } + // } + } + + public createDevfile( + destinationUrl: string, + devfileName: string, + devfileImage: string, + extensionUri: vscode.Uri, + ) { + let devfile: string; + const relativeTemplatePath = + "resources/contentCreator/createDevfile/devfile-template.txt"; + + const expandedDestUrl = expandPath(destinationUrl); + + const uuid = randomUUID().slice(0, 8); + const fullDevfileName = `${devfileName}-${uuid}`; + + const absoluteTemplatePath = vscode.Uri.joinPath( + extensionUri, + relativeTemplatePath, + ) + .toString() + .replace("file://", ""); + + try { + devfile = fs.readFileSync(absoluteTemplatePath, "utf8"); + devfile = devfile.replace("{{ dev_file_name }}", fullDevfileName); + devfile = devfile.replace("{{ dev_file_image }}", devfileImage); + fs.writeFileSync(expandedDestUrl, devfile); + return "passed"; + } catch (err) { + console.error("Devfile could not be created. Error: ", err); + return "failed"; + } + } + + public openDevfile(fileUrl: string) { + const updatedUrl = expandPath(fileUrl); + vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(updatedUrl)); + } +} diff --git a/src/features/contentCreator/types.ts b/src/features/contentCreator/types.ts index fd050e761..85ef58ebc 100644 --- a/src/features/contentCreator/types.ts +++ b/src/features/contentCreator/types.ts @@ -23,6 +23,13 @@ export type AnsibleProjectFormInterface = { isOverwritten: boolean; }; +export type DevfileFormInterface = { + destinationPath: string; + name: string; + image: string; + isOverwritten: boolean; +}; + export type PostMessageEvent = | { command: "ADEPresence"; diff --git a/src/features/quickLinks/quickLinksView.ts b/src/features/quickLinks/quickLinksView.ts index d6b3a8dc8..7e3576834 100644 --- a/src/features/quickLinks/quickLinksView.ts +++ b/src/features/quickLinks/quickLinksView.ts @@ -106,6 +106,13 @@ export function getWebviewQuickLinks(webview: Webview, extensionUri: Uri) { + diff --git a/src/webview/apps/contentCreator/createDevfilePageApp.ts b/src/webview/apps/contentCreator/createDevfilePageApp.ts new file mode 100644 index 000000000..26644b330 --- /dev/null +++ b/src/webview/apps/contentCreator/createDevfilePageApp.ts @@ -0,0 +1,234 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { + allComponents, + Button, + Checkbox, + TextArea, + TextField, + provideVSCodeDesignSystem, + Dropdown, +} from "@vscode/webview-ui-toolkit"; +import { + DevfileFormInterface, + PostMessageEvent, +} from "../../../features/contentCreator/types"; + +provideVSCodeDesignSystem().register(allComponents); + +const vscode = acquireVsCodeApi(); +window.addEventListener("load", main); + +let destinationPathUrlTextField: TextField; +let folderExplorerButton: Button; + +let devfileNameTextField: TextField; + +let devfileCreateButton: Button; +let devfileClearButton: Button; + +let devfileClearLogsButton: Button; + +let overwriteCheckbox: Checkbox; + +let imageDropdown: Dropdown; + +let devfilePathDiv: HTMLElement | null; +let devfilePathElement: HTMLElement; + +let devfileLogsTextArea: TextArea; +let devfileOpenScaffoldedFolderButton: Button; + +let projectUrl = ""; + +function main() { + destinationPathUrlTextField = document.getElementById( + "path-url", + ) as TextField; + folderExplorerButton = document.getElementById("folder-explorer") as Button; + + devfileNameTextField = document.getElementById("devfile-name") as TextField; + + overwriteCheckbox = document.getElementById("overwrite-checkbox") as Checkbox; + + imageDropdown = document.getElementById("image-dropdown") as Dropdown; + devfileCreateButton = document.getElementById("create-button") as Button; + devfileClearButton = document.getElementById("reset-button") as Button; + + devfileLogsTextArea = document.getElementById("log-text-area") as TextArea; + + devfileClearLogsButton = document.getElementById( + "clear-logs-button", + ) as Button; + + devfileOpenScaffoldedFolderButton = document.getElementById( + "open-file-button", + ) as Button; + + destinationPathUrlTextField.addEventListener("input", toggleCreateButton); + devfileNameTextField.addEventListener("input", toggleCreateButton); + + folderExplorerButton.addEventListener("click", openFolderExplorer); + + devfileCreateButton.addEventListener("click", handleCreateClick); + devfileCreateButton.disabled = true; + + devfileClearButton.addEventListener("click", handleResetClick); + + devfileOpenScaffoldedFolderButton.addEventListener( + "click", + handleOpenDevfileClick, + ); + + devfileClearLogsButton.addEventListener("click", handleDevfileLogsClick); + + devfilePathDiv = document.getElementById("full-devfile-path"); + + devfilePathElement = document.createElement("p"); + + if (destinationPathUrlTextField.placeholder !== "") { + devfilePathElement.innerHTML = `${destinationPathUrlTextField.placeholder}/devfile.yaml`; + } else { + devfilePathElement.innerHTML = + "No folders are open in the workspace - Enter a destination directory."; + } + + destinationPathUrlTextField.value = destinationPathUrlTextField.placeholder; + + devfilePathDiv?.appendChild(devfilePathElement); +} + +function openFolderExplorer(event: any) { + const source = event.target.parentNode.id; + + const typeOption = "folder"; + + vscode.postMessage({ + command: "open-explorer", + payload: { + selectOption: typeOption, + }, + }); + + window.addEventListener( + "message", + (event: MessageEvent) => { + const message = event.data; + + if (message.command === "file-uri") { + const selectedUri = message.arguments.selectedUri; + + if (selectedUri) { + if (source === "folder-explorer") { + destinationPathUrlTextField.value = selectedUri; + devfilePathElement.innerHTML = selectedUri; + } + } + } + }, + ); +} + +function toggleCreateButton() { + // update

tag text + if (!destinationPathUrlTextField.value.trim()) { + if (destinationPathUrlTextField.placeholder !== "") { + devfilePathElement.innerHTML = `${destinationPathUrlTextField.placeholder}/devfile.yaml`; + } else { + devfilePathElement.innerHTML = + "No folders are open in the workspace - Enter a destination directory."; + } + } else { + devfilePathElement.innerHTML = `${destinationPathUrlTextField.value.trim()}/devfile.yaml`; + } + + if ( + devfileNameTextField.value.trim() && + (destinationPathUrlTextField.value.trim() || + destinationPathUrlTextField.placeholder !== "") + ) { + devfileCreateButton.disabled = false; + } else { + devfileCreateButton.disabled = true; + } +} + +function handleResetClick() { + destinationPathUrlTextField.value = destinationPathUrlTextField.placeholder; + devfileNameTextField.value = ""; + + if (destinationPathUrlTextField.placeholder !== "") { + devfilePathElement.innerHTML = `${destinationPathUrlTextField.placeholder}/devfile.yaml`; + } else { + devfilePathElement.innerHTML = + "No folders are open in the workspace - Enter a destination directory."; + } + + overwriteCheckbox.checked = false; + imageDropdown.currentValue = + "ghcr.io/ansible/ansible-workspace-env-reference:latest"; + + devfileCreateButton.disabled = true; +} + +function handleCreateClick() { + let path: string; + devfileCreateButton.disabled = true; + if (destinationPathUrlTextField.value === "") { + path = destinationPathUrlTextField.placeholder; + } else { + path = destinationPathUrlTextField.value.trim(); + } + + vscode.postMessage({ + command: "devfile-create", + payload: { + destinationPath: path, + name: devfileNameTextField.value.trim(), + image: imageDropdown.currentValue.trim(), + isOverwritten: overwriteCheckbox.checked, + } as DevfileFormInterface, + }); + + window.addEventListener( + "message", + async (event: MessageEvent) => { + const message = event.data; + + switch (message.command) { + case "execution-log": + devfileLogsTextArea.value = message.arguments.commandOutput; + + if ( + message.arguments.status && + message.arguments.status === "passed" + ) { + devfileOpenScaffoldedFolderButton.disabled = false; + } else { + devfileOpenScaffoldedFolderButton.disabled = true; + } + + projectUrl = message.arguments.projectUrl + ? message.arguments.projectUrl + : ""; + + devfileCreateButton.disabled = false; + + return; + } + }, + ); +} + +function handleOpenDevfileClick() { + vscode.postMessage({ + command: "open-devfile", + payload: { + projectUrl: projectUrl, + }, + }); +} + +function handleDevfileLogsClick() { + devfileLogsTextArea.value = ""; +} diff --git a/test/ui-test/contentCreatorUiTest.ts b/test/ui-test/contentCreatorUiTest.ts index 430493d1b..64643fa24 100644 --- a/test/ui-test/contentCreatorUiTest.ts +++ b/test/ui-test/contentCreatorUiTest.ts @@ -89,4 +89,111 @@ export function contentCreatorUiTest(): void { ); }); }); + describe("Test devfile generation webview (without creator)", () => { + let workbench: Workbench; + let createDevfileButton: WebElement; + let editorView: EditorView; + + before(async () => { + workbench = new Workbench(); + }); + it("Check create-devfile webview elements", async () => { + await workbench.executeCommand("Ansible: Create a Devfile"); + await sleep(2000); + + await new EditorView().openEditor("Create Devfile"); + const devfileWebview = await getWebviewByLocator( + By.xpath("//vscode-text-field[@id='path-url']"), + ); + + const descriptionText = await ( + await devfileWebview.findWebElement( + By.xpath("//div[@class='description-div']"), + ) + ).getText(); + + expect( + descriptionText, + "descriptionText should contain 'Devfiles are yaml files'", + ).to.contain("Devfiles are yaml files"); + + const devfileDestination = await devfileWebview.findWebElement( + By.xpath("//vscode-text-field[@id='path-url']"), + ); + + expect(devfileDestination, "devfileDestination should not be undefined") + .not.to.be.undefined; + + await devfileDestination.sendKeys("~"); + + const devfileNameTextField = await devfileWebview.findWebElement( + By.xpath("//vscode-text-field[@id='devfile-name']"), + ); + + expect( + devfileNameTextField, + "devfileNameTextField should not be undefined", + ).not.to.be.undefined; + + await devfileNameTextField.sendKeys("test"); + + createDevfileButton = await devfileWebview.findWebElement( + By.xpath("//vscode-button[@id='create-button']"), + ); + expect(createDevfileButton, "createDevfileButton should not be undefined") + .not.to.be.undefined; + + expect( + await createDevfileButton.isEnabled(), + "createDevfileButton button should be enabled now", + ).to.be.true; + + const overwriteDevfileCheckbox = await devfileWebview.findWebElement( + By.xpath("//vscode-checkbox[@id='overwrite-checkbox']"), + ); + await overwriteDevfileCheckbox.click(); + await createDevfileButton.click(); + await sleep(1000); + + const devfileClearLogsButton = await devfileWebview.findWebElement( + By.xpath("//vscode-button[@id='clear-logs-button']"), + ); + + expect( + devfileClearLogsButton, + "devfileClearLogsButton should not be undefined", + ).not.to.be.undefined; + + await devfileClearLogsButton.click(); + + const resetButton = await devfileWebview.findWebElement( + By.xpath("//vscode-button[@id='reset-button']"), + ); + + expect(resetButton, "resetButton should not be undefined").not.to.be + .undefined; + + await resetButton.click(); + + // Check that the devfile can't be generated if the dir doesn't exist + await devfileDestination.sendKeys("~/test"); + await devfileNameTextField.sendKeys("test"); + await createDevfileButton.click(); + await resetButton.click(); + await sleep(1000); + + // Check that the devfile can't be generated if it already exists + await devfileDestination.sendKeys("~"); + await devfileNameTextField.sendKeys("test"); + await createDevfileButton.click(); + await sleep(1000); + + await devfileWebview.switchBack(); + + editorView = new EditorView(); + if (editorView) { + await editorView.closeAllEditors(); + } + }); + }); } diff --git a/tools/compare_devfile.sh b/tools/compare_devfile.sh new file mode 100755 index 000000000..cdad1ace0 --- /dev/null +++ b/tools/compare_devfile.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Pull devfile template from ansible-creator +CREATOR_REPO="https://github.com/ansible/ansible-creator.git" +CREATOR_DEVFILE_TEMPLATE="src/ansible_creator/resources/common/devfile/devfile.yaml.j2" +EXTENSION_DEVFILE_TEMPLATE="resources/contentCreator/createDevfile/devfile-template.txt" + +# Clone ansible-creator repo + +TEMP_DIR=$(mktemp -d) +git clone --depth 1 --branch main "$CREATOR_REPO" "$TEMP_DIR" + +# Compare the files +if diff "$TEMP_DIR/$CREATOR_DEVFILE_TEMPLATE" "$EXTENSION_DEVFILE_TEMPLATE" > /dev/null; then + echo "Devfile template matches ansible-creator devfile template." + rm -rf "$TEMP_DIR" + exit 0 +else + echo "Devfile template under resources/ does not match the template ansible-creator is using." + rm -rf "$TEMP_DIR" + exit 1 +fi diff --git a/webpack.config.ts b/webpack.config.ts index 2b49b3eb4..28dbe79c6 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -224,6 +224,19 @@ const createAnsibleProjectWebviewConfig = { }, }; +const createDevfileWebviewConfig = { + ...config, + target: ["web", "es2020"], + entry: "./src/webview/apps/contentCreator/createDevfilePageApp.ts", + experiments: { outputModule: true }, + output: { + path: path.resolve(__dirname, "out"), + filename: "./client/webview/apps/contentCreator/createDevfilePageApp.js", + libraryTarget: "module", + chunkFormat: "module", + }, +}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any module.exports = (_env: any, argv: { mode: string }) => { // Use non-bundled js for client/server in dev environment @@ -241,6 +254,7 @@ module.exports = (_env: any, argv: { mode: string }) => { playbookExplanationWebviewConfig, roleGenerationWebviewConfig, createAnsibleProjectWebviewConfig, + createDevfileWebviewConfig, quickLinksWebviewConfig, ]; };