From 42d1aaa979979591424774fa35c7ac69868d8f92 Mon Sep 17 00:00:00 2001 From: Alison Hart Date: Thu, 17 Oct 2024 13:59:29 -0400 Subject: [PATCH] Update creator webviews to reflect ansible-creator CLI changes (#1582) Adds a check for the ansible-creator version, as ansible-creator v24.10.0 is required for ansible-creator's new CLI structure to work with ADT server. Changes any reference of "scm-org" to "namespace" and "scm-project" to "collection". Adds a UI test for the Create Ansible Project Page webview. Adds the "semver" dependency to do version comparison for ansible-creator. --- .../createAnsibleProjectPageStyle.css | 4 +- package.json | 1 + src/definitions/constants.ts | 2 + .../createAnsibleCollectionPage.ts | 2 +- .../createAnsibleProjectPage.ts | 43 ++++++++-- src/features/contentCreator/types.ts | 4 +- .../createAnsibleProjectPageApp.ts | 34 ++++---- test/ui-test/allTestsSuite.ts | 2 + test/ui-test/contentCreatorUiTest.ts | 78 +++++++++++++++++++ test/units/contentCreator/utilities.test.ts | 6 ++ yarn.lock | 9 ++- 11 files changed, 152 insertions(+), 33 deletions(-) create mode 100644 test/ui-test/contentCreatorUiTest.ts diff --git a/media/contentCreator/createAnsibleProjectPageStyle.css b/media/contentCreator/createAnsibleProjectPageStyle.css index 0eb4f8988..3599c1c05 100644 --- a/media/contentCreator/createAnsibleProjectPageStyle.css +++ b/media/contentCreator/createAnsibleProjectPageStyle.css @@ -19,14 +19,14 @@ vscode-text-area { margin-bottom: 6px; } -.scm-org-project-div { +.playbook-project-div { display: flex; flex-direction: row; justify-content: space-between; align-items: center; } -#scm-org-name, #scm-project-name { +#namespace-name, #collection-name { width:49%; display:inline-block; } diff --git a/package.json b/package.json index b92b35878..5043dc1d6 100644 --- a/package.json +++ b/package.json @@ -877,6 +877,7 @@ "ini": "^4.1.3", "marked": "^13.0.3", "minimatch": "^9.0.5", + "semver": "^7.6.3", "start-server-and-test": "^2.0.5", "uuid": "^10.0.0", "vscode-languageclient": "^9.0.1", diff --git a/src/definitions/constants.ts b/src/definitions/constants.ts index 0103688d3..deea0ef4c 100644 --- a/src/definitions/constants.ts +++ b/src/definitions/constants.ts @@ -40,3 +40,5 @@ export const IncludeVarValidTaskName = [ ]; export const ANSIBLE_LIGHTSPEED_API_TIMEOUT = 50000; + +export const ANSIBLE_CREATOR_VERSION_MIN = "24.10.0"; diff --git a/src/features/contentCreator/createAnsibleCollectionPage.ts b/src/features/contentCreator/createAnsibleCollectionPage.ts index 6707f9557..bb0338eb1 100644 --- a/src/features/contentCreator/createAnsibleCollectionPage.ts +++ b/src/features/contentCreator/createAnsibleCollectionPage.ts @@ -353,7 +353,7 @@ export class CreateAnsibleCollection { ? initPath : `${os.homedir()}/.ansible/collections/ansible_collections`; - let ansibleCreatorInitCommand = `ansible-creator init ${namespaceName}.${collectionName} --init-path=${initPathUrl} --no-ansi`; + let ansibleCreatorInitCommand = `ansible-creator init collection ${namespaceName}.${collectionName} ${initPathUrl} --no-ansi`; // adjust collection url for using it in ade and opening it in workspace // NOTE: this is done in order to synchronize the behavior of ade and extension diff --git a/src/features/contentCreator/createAnsibleProjectPage.ts b/src/features/contentCreator/createAnsibleProjectPage.ts index da30164e9..b49606ecb 100644 --- a/src/features/contentCreator/createAnsibleProjectPage.ts +++ b/src/features/contentCreator/createAnsibleProjectPage.ts @@ -2,17 +2,20 @@ import * as vscode from "vscode"; import * as os from "os"; +import * as semver from "semver"; import { getUri } from "../utils/getUri"; import { getNonce } from "../utils/getNonce"; import { AnsibleProjectFormInterface, PostMessageEvent } from "./types"; import { withInterpreter } from "../utils/commandRunner"; import { SettingsManager } from "../../settings"; -import { expandPath, runCommand } from "./utils"; +import { expandPath, getBinDetail, runCommand } from "./utils"; +import { ANSIBLE_CREATOR_VERSION_MIN } from "../../definitions/constants"; export class CreateAnsibleProject { public static currentPanel: CreateAnsibleProject | 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; @@ -128,9 +131,9 @@ export class CreateAnsibleProject { -
- SCM organization * - SCM project * +
+ Namespace * + Collection *
@@ -277,6 +280,26 @@ export class CreateAnsibleProject { ); } + // Get ansible-creator version and check which command should be used + public async getCreatorCommand( + namespace: string, + collection: string, + url: string, + ): Promise { + let command = ""; + const creatorVersion = ( + await getBinDetail("ansible-creator", "--version") + ).toString(); + console.log("ansible-creator version: ", creatorVersion); + + if (semver.gte(creatorVersion, ANSIBLE_CREATOR_VERSION_MIN)) { + command = `ansible-creator init playbook ${namespace}.${collection} ${url} --no-ansi`; + } else { + command = `ansible-creator init --project=ansible-project --init-path=${url} --scm-org=${namespace} --scm-project=${collection} --no-ansi`; + } + return command; + } + public async openExplorerDialog( selectOption: string, ): Promise { @@ -304,8 +327,8 @@ export class CreateAnsibleProject { ) { const { destinationPath, - scmOrgName, - scmProjectName, + namespaceName, + collectionName, logToFile, logFilePath, logFileAppend, @@ -316,9 +339,13 @@ export class CreateAnsibleProject { const destinationPathUrl = destinationPath ? destinationPath - : `${os.homedir()}/${scmOrgName}-${scmProjectName}`; + : `${os.homedir()}/${namespaceName}-${collectionName}`; - let ansibleCreatorInitCommand = `ansible-creator init --project=ansible-project --init-path=${destinationPathUrl} --scm-org=${scmOrgName} --scm-project=${scmProjectName} --no-ansi`; + let ansibleCreatorInitCommand = await this.getCreatorCommand( + namespaceName, + collectionName, + destinationPathUrl, + ); if (isForced) { ansibleCreatorInitCommand += " --force"; diff --git a/src/features/contentCreator/types.ts b/src/features/contentCreator/types.ts index 65f4f4fbb..c8b71a3c6 100644 --- a/src/features/contentCreator/types.ts +++ b/src/features/contentCreator/types.ts @@ -13,8 +13,8 @@ export type AnsibleCollectionFormInterface = { export type AnsibleProjectFormInterface = { destinationPath: string; - scmOrgName: string; - scmProjectName: string; + namespaceName: string; + collectionName: string; verbosity: string; logToFile: boolean; logFilePath: string; diff --git a/src/webview/apps/contentCreator/createAnsibleProjectPageApp.ts b/src/webview/apps/contentCreator/createAnsibleProjectPageApp.ts index 9b3c741df..c80544aa2 100644 --- a/src/webview/apps/contentCreator/createAnsibleProjectPageApp.ts +++ b/src/webview/apps/contentCreator/createAnsibleProjectPageApp.ts @@ -22,8 +22,8 @@ window.addEventListener("load", main); let destinationPathUrlTextField: TextField; let folderExplorerButton: Button; -let scmOrgNameTextField: TextField; -let scmProjectNameTextField: TextField; +let namespaceNameTextField: TextField; +let collectionNameTextField: TextField; let initCreateButton: Button; let initClearButton: Button; @@ -59,9 +59,11 @@ function main() { ) as TextField; folderExplorerButton = document.getElementById("folder-explorer") as Button; - scmOrgNameTextField = document.getElementById("scm-org-name") as TextField; - scmProjectNameTextField = document.getElementById( - "scm-project-name", + namespaceNameTextField = document.getElementById( + "namespace-name", + ) as TextField; + collectionNameTextField = document.getElementById( + "collection-name", ) as TextField; forceCheckbox = document.getElementById("force-checkbox") as Checkbox; @@ -96,8 +98,8 @@ function main() { // projectNameTextField?.addEventListener("input", toggleCreateButton); destinationPathUrlTextField.addEventListener("input", toggleCreateButton); - scmOrgNameTextField.addEventListener("input", toggleCreateButton); - scmProjectNameTextField.addEventListener("input", toggleCreateButton); + namespaceNameTextField.addEventListener("input", toggleCreateButton); + collectionNameTextField.addEventListener("input", toggleCreateButton); folderExplorerButton.addEventListener("click", openExplorer); fileExplorerButton.addEventListener("click", openExplorer); @@ -166,11 +168,11 @@ function toggleCreateButton() { if (!destinationPathUrlTextField.value.trim()) { initCollectionPathElement.innerHTML = `${ destinationPathUrlTextField.placeholder - }/${scmOrgNameTextField.value.trim()}-${scmProjectNameTextField.value.trim()}`; + }/${namespaceNameTextField.value.trim()}-${collectionNameTextField.value.trim()}`; if ( - !scmOrgNameTextField.value.trim() || - !scmProjectNameTextField.value.trim() + !namespaceNameTextField.value.trim() || + !collectionNameTextField.value.trim() ) { initCollectionPathElement.innerHTML = destinationPathUrlTextField.placeholder; @@ -181,8 +183,8 @@ function toggleCreateButton() { } if ( - scmOrgNameTextField.value.trim() && - scmProjectNameTextField.value.trim() + namespaceNameTextField.value.trim() && + collectionNameTextField.value.trim() ) { initCreateButton.disabled = false; } else { @@ -193,8 +195,8 @@ function toggleCreateButton() { function handleInitClearClick() { // projectNameTextField.value = ""; destinationPathUrlTextField.value = ""; - scmOrgNameTextField.value = ""; - scmProjectNameTextField.value = ""; + namespaceNameTextField.value = ""; + collectionNameTextField.value = ""; initCollectionPathElement.innerHTML = destinationPathUrlTextField.placeholder; @@ -232,8 +234,8 @@ function handleInitCreateClick() { payload: { // projectName: projectNameTextField.value.trim(), destinationPath: destinationPathUrlTextField.value.trim(), - scmOrgName: scmOrgNameTextField.value.trim(), - scmProjectName: scmProjectNameTextField.value.trim(), + namespaceName: namespaceNameTextField.value.trim(), + collectionName: collectionNameTextField.value.trim(), verbosity: verboseDropdown.currentValue.trim(), logToFile: logToFileCheckbox.checked, logFilePath: logFilePath.value.trim(), diff --git a/test/ui-test/allTestsSuite.ts b/test/ui-test/allTestsSuite.ts index 6ed8ae3a2..2e63df916 100644 --- a/test/ui-test/allTestsSuite.ts +++ b/test/ui-test/allTestsSuite.ts @@ -1,3 +1,4 @@ +import { contentCreatorUiTest } from "./contentCreatorUiTest"; import { extensionUIAssetsTest } from "./extensionUITest"; import { lightspeedUILoginTest } from "./lightspeedAuthUiTest"; import { lightspeedOneClickTrialUITest } from "./lightspeedOneClickTrialUITest"; @@ -22,4 +23,5 @@ describe("VSCode Ansible - UI tests", function () { // lightspeedUISignOutTest(); } walkthroughUiTest(); + contentCreatorUiTest(); }); diff --git a/test/ui-test/contentCreatorUiTest.ts b/test/ui-test/contentCreatorUiTest.ts new file mode 100644 index 000000000..cbbb07c81 --- /dev/null +++ b/test/ui-test/contentCreatorUiTest.ts @@ -0,0 +1,78 @@ +import { + By, + EditorView, + WebElement, + WebView, + Workbench, +} from "vscode-extension-tester"; +import { sleep } from "./uiTestHelper"; +import { config, expect } from "chai"; + +config.truncateThreshold = 0; +export function contentCreatorUiTest(): void { + describe("Test Ansible playbook project scaffolding", () => { + let workbench: Workbench; + let createButton: WebElement; + let editorView: EditorView; + + before(async () => { + workbench = new Workbench(); + editorView = new EditorView(); + if (editorView) { + await editorView.closeAllEditors(); + } + }); + + it("Check create-ansible-project webview elements", async () => { + await workbench.executeCommand("Ansible: Create New Playbook Project"); + await sleep(4000); + + const playbookProject = (await new EditorView().openEditor( + "Create Ansible project", + )) as WebView; + + expect(playbookProject, "webView should not be undefined").not.to.be + .undefined; + await playbookProject.switchToFrame(5000); + expect( + playbookProject, + "webView should not be undefined after switching to its frame", + ).not.to.be.undefined; + + const namespaceTextField = await playbookProject.findWebElement( + By.xpath("//vscode-text-field[@id='namespace-name']"), + ); + expect(namespaceTextField, "namespaceTextField should not be undefined") + .not.to.be.undefined; + await namespaceTextField.sendKeys("test_namespace"); + + const collectionTextField = await playbookProject.findWebElement( + By.xpath("//vscode-text-field[@id='collection-name']"), + ); + expect(collectionTextField, "collectionTextField should not be undefined") + .not.to.be.undefined; + await collectionTextField.sendKeys("test_collection"); + + const forceCheckbox = await playbookProject.findWebElement( + By.xpath("//vscode-checkbox[@id='force-checkbox']"), + ); + + expect(forceCheckbox, "forceCheckbox should not be undefined").not.to.be + .undefined; + await forceCheckbox.click(); + + createButton = await playbookProject.findWebElement( + By.xpath("//vscode-button[@id='create-button']"), + ); + expect(createButton, "createButton should not be undefined").not.to.be + .undefined; + + expect( + await createButton.isEnabled(), + "Create button should be enabled now", + ).to.be.true; + await createButton.click(); + await playbookProject.switchBack(); + }); + }); +} diff --git a/test/units/contentCreator/utilities.test.ts b/test/units/contentCreator/utilities.test.ts index 9821c634f..3c348391c 100644 --- a/test/units/contentCreator/utilities.test.ts +++ b/test/units/contentCreator/utilities.test.ts @@ -27,6 +27,12 @@ const getBinDetailTests = [ arg: "--version", expected: "failed", }, + { + name: "valid binary (ansible-creator)", + command: "ansible-creator", + arg: "--version", + expected: "ansible-creator", + }, ]; const runCommandTests = [ diff --git a/yarn.lock b/yarn.lock index 238c6dd7e..038a8317b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2333,6 +2333,7 @@ __metadata: react: "npm:^18.3.1" rimraf: "npm:^5.0.9" selenium-webdriver: "npm:^4.23.0" + semver: "npm:^7.6.3" shiki: "npm:^1.12.0" sinon: "npm:^18.0.0" start-server-and-test: "npm:^2.0.5" @@ -8882,12 +8883,12 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2": - version: 7.6.2 - resolution: "semver@npm:7.6.2" +"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3": + version: 7.6.3 + resolution: "semver@npm:7.6.3" bin: semver: bin/semver.js - checksum: 10/296b17d027f57a87ef645e9c725bff4865a38dfc9caf29b26aa084b85820972fbe7372caea1ba6857162fa990702c6d9c1d82297cecb72d56c78ab29070d2ca2 + checksum: 10/36b1fbe1a2b6f873559cd57b238f1094a053dbfd997ceeb8757d79d1d2089c56d1321b9f1069ce263dc64cfa922fa1d2ad566b39426fe1ac6c723c1487589e10 languageName: node linkType: hard