Merge branch 'stable-3.4' into stable-3.5 * stable-3.4: Adapt to new ProjectCache interface Depends-On: https://gerrit-review.googlesource.com/c/gerrit/+/324619 Change-Id: Ie236754fc3d3412b68a7e291752f71920d6d4ff2
diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index e69de29..0000000 --- a/.eslintignore +++ /dev/null
diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 6d9ae7c..0000000 --- a/.eslintrc.json +++ /dev/null
@@ -1,166 +0,0 @@ -{ - "extends": ["eslint:recommended", "google"], - "parserOptions": { - "ecmaVersion": 8, - "sourceType": "module" - }, - "env": { - "browser": true, - "es6": true - }, - "globals": { - "__dirname": false, - "app": false, - "page": false, - "Polymer": false, - "process": false, - "require": false, - "Gerrit": false, - "Promise": false, - "assert": false, - "test": false, - "flushAsynchronousOperations": false - }, - "rules": { - "arrow-parens": ["error", "as-needed"], - "block-spacing": ["error", "always"], - "brace-style": ["error", "1tbs", { "allowSingleLine": true }], - "camelcase": "off", - "comma-dangle": ["error", { - "arrays": "always-multiline", - "objects": "always-multiline", - "imports": "always-multiline", - "exports": "always-multiline", - "functions": "never" - }], - "eol-last": "off", - "indent": ["error", 2, { - "MemberExpression": 2, - "FunctionDeclaration": {"body": 1, "parameters": 2}, - "FunctionExpression": {"body": 1, "parameters": 2}, - "CallExpression": {"arguments": 2 }, - "ArrayExpression": 1, - "ObjectExpression": 1, - "SwitchCase": 1 - }], - "keyword-spacing": ["error", { "after": true, "before": true }], - "lines-between-class-members": ["error", "always"], - "max-len": [ - "error", - 80, - 2, - { - "ignoreComments": true, - "ignorePattern": "^import .*;$" - } - ], - "new-cap": ["error", { "capIsNewExceptions": ["Polymer", "LegacyElementMixin", "GestureEventListeners", "LegacyDataMixin"] }], - "no-console": "off", - "no-multiple-empty-lines": [ "error", { "max": 1 } ], - "no-prototype-builtins": "off", - "no-redeclare": "off", - "no-restricted-syntax": [ - "error", - { - "selector": "ExpressionStatement > CallExpression > MemberExpression[object.name='test'][property.name='only']", - "message": "Remove test.only." - }, - { - "selector": "ExpressionStatement > CallExpression > MemberExpression[object.name='suite'][property.name='only']", - "message": "Remove suite.only." - } - ], - "no-undef": "off", - "no-useless-escape": "off", - "no-var": "error", - "object-shorthand": ["error", "always"], - "padding-line-between-statements": [ - "error", - { - "blankLine": "always", - "prev": "class", - "next": "*" - }, - { - "blankLine": "always", - "prev": "*", - "next": "class" - } - ], - "prefer-arrow-callback": "error", - "prefer-const": "error", - "prefer-spread": "error", - "quote-props": ["error", "consistent-as-needed"], - "semi": [2, "always"], - "template-curly-spacing": "error", - "valid-jsdoc": "off", - - "require-jsdoc": 0, - "valid-jsdoc": 0, - "jsdoc/check-alignment": 2, - "jsdoc/check-examples": 0, - "jsdoc/check-indentation": 0, - "jsdoc/check-param-names": 0, - "jsdoc/check-syntax": 0, - "jsdoc/check-tag-names": 0, - "jsdoc/check-types": 0, - "jsdoc/implements-on-classes": 2, - "jsdoc/match-description": 0, - "jsdoc/newline-after-description": 2, - "jsdoc/no-types": 0, - "jsdoc/no-undefined-types": 0, - "jsdoc/require-description": 0, - "jsdoc/require-description-complete-sentence": 0, - "jsdoc/require-example": 0, - "jsdoc/require-hyphen-before-param-description": 0, - "jsdoc/require-jsdoc": 0, - "jsdoc/require-param": 0, - "jsdoc/require-param-description": 0, - "jsdoc/require-param-name": 2, - "jsdoc/require-param-type": 2, - "jsdoc/require-returns": 0, - "jsdoc/require-returns-check": 0, - "jsdoc/require-returns-description": 0, - "jsdoc/require-returns-type": 2, - "jsdoc/valid-types": 2, - "jsdoc/require-file-overview": ["error", { - "tags": { - "license": { - "mustExist": true, - "preventDuplicates": true - } - } - }], - "import/named": 2, - "import/no-unresolved": 2, - "import/no-self-import": 2, - // The no-cycle rule is slow, because it doesn't cache dependencies. - // Disable it. - "import/no-cycle": 0, - "import/no-useless-path-segments": 2, - "import/no-unused-modules": 2, - "import/no-default-export": 2 - }, - "plugins": [ - "html", - "jsdoc", - "import" - ], - "settings": { - "html/report-bad-indent": "error" - }, - "overrides": [ - { - "files": ["*_html.js", "*-styles.js", "externs.js"], - "rules": { - "max-len": "off" - } - }, - { - "files": ["*.html"], - "rules": { - "jsdoc/require-file-overview": "off" - } - } - ] -}
diff --git a/.gitignore b/.gitignore index 4c0afce..81322bc 100644 --- a/.gitignore +++ b/.gitignore
@@ -4,3 +4,4 @@ /.settings /bazel-* /eclipse-out +/node_modules
diff --git a/BUILD b/BUILD index d560d98..1f7aabb 100644 --- a/BUILD +++ b/BUILD
@@ -1,60 +1,26 @@ load("@rules_java//java:defs.bzl", "java_library") -load("@npm//@bazel/rollup:index.bzl", "rollup_bundle") load("//tools/bzl:junit.bzl", "junit_tests") -load("//tools/js:eslint.bzl", "eslint") load( "//tools/bzl:plugin.bzl", "PLUGIN_DEPS", "PLUGIN_TEST_DEPS", "gerrit_plugin", ) -load("//tools/bzl:genrule2.bzl", "genrule2") -load("//tools/bzl:js.bzl", "polygerrit_plugin") gerrit_plugin( name = "delete-project", srcs = glob(["src/main/java/**/*.java"]), manifest_entries = [ "Gerrit-PluginName: delete-project", - "Gerrit-Module: com.googlesource.gerrit.plugins.deleteproject.Module", + "Gerrit-Module: com.googlesource.gerrit.plugins.deleteproject.PluginModule", "Gerrit-HttpModule: com.googlesource.gerrit.plugins.deleteproject.HttpModule", "Gerrit-SshModule: com.googlesource.gerrit.plugins.deleteproject.SshModule", ], - resource_jars = [":gr-delete-repo-static"], + resource_jars = ["//plugins/delete-project/web:gr-delete-repo"], resources = glob(["src/main/resources/Documentation/*.md"]), deps = ["@commons-io//jar"], ) -genrule2( - name = "gr-delete-repo-static", - srcs = [":gr-delete-repo"], - outs = ["gr-delete-repo-static.jar"], - cmd = " && ".join([ - "mkdir $$TMP/static", - "cp $(locations :gr-delete-repo) $$TMP/static", - "cd $$TMP", - "zip -Drq $$ROOT/$@ -g .", - ]), -) - -polygerrit_plugin( - name = "gr-delete-repo", - app = "delete-project-bundle.js", - plugin_name = "delete-project", -) - -rollup_bundle( - name = "delete-project-bundle", - srcs = glob(["gr-delete-repo/*.js"]), - entry_point = "gr-delete-repo/plugin.js", - format = "iife", - rollup_bin = "//tools/node_tools:rollup-bin", - sourcemap = "hidden", - deps = [ - "@tools_npm//rollup-plugin-node-resolve", - ], -) - junit_tests( name = "delete-project_tests", srcs = glob(["src/test/java/**/*.java"]), @@ -74,24 +40,3 @@ "@mockito//jar", ], ) - -# Define the eslinter for the plugin -# The eslint macro creates 2 rules: lint_test and lint_bin -eslint( - name = "lint", - srcs = glob([ - "gr-delete-repo/**/*.js", - ]), - config = ".eslintrc.json", - data = [], - extensions = [ - ".js", - ], - ignore = ".eslintignore", - plugins = [ - "@npm//eslint-config-google", - "@npm//eslint-plugin-html", - "@npm//eslint-plugin-import", - "@npm//eslint-plugin-jsdoc", - ], -)
diff --git a/README.md b/README.md index 1444d1a..5a87b38 100644 --- a/README.md +++ b/README.md
@@ -5,3 +5,24 @@ [](https://gerrit-ci.gerritforge.com/view/Plugins-master/job/plugin-delete-project-bazel-master/) + +## JavaScript Plugin Development + +For running unit tests execute: + + bazel test --test_output=all //plugins/delete-project/web:karma_test + +For checking or fixing eslint formatter problems run: + + bazel test //plugins/delete-project/web:lint_test + bazel run //plugins/delete-project/web:lint_bin -- --fix "$(pwd)/plugins/delete-project/web" + +For testing the plugin with +[Gerrit FE Dev Helper](https://gerrit.googlesource.com/gerrit-fe-dev-helper/) +build the JavaScript bundle and copy it to the `plugins/` folder: + + bazel build //plugins/delete-project/web:gr-delete-repo + cp -f bazel-bin/plugins/delete-project/web/gr-delete-repo.js plugins/ + +and let the Dev Helper redirect from `.+/plugins/delete-project/static/gr-delete-repo.js` to +`http://localhost:8081/plugins_/gr-delete-repo.js`.
diff --git a/gr-delete-repo/gr-delete-repo.js b/gr-delete-repo/gr-delete-repo.js deleted file mode 100644 index 20c3555..0000000 --- a/gr-delete-repo/gr-delete-repo.js +++ /dev/null
@@ -1,83 +0,0 @@ -/** - * @license - * Copyright (C) 2018 The Android Open Source Project - * - * 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. - */ - -import {htmlTemplate} from './gr-delete-repo_html.js'; - -class GrDeleteRepo extends Polymer.Element { - /** @returns {string} name of the component */ - static get is() { return 'gr-delete-repo'; } - - /** @returns {?} template for this component */ - static get template() { return htmlTemplate; } - - /** - * Defines properties of the component - * - * @returns {?} - */ - static get properties() { - return { - repoName: String, - config: Object, - action: Object, - actionId: String, - }; - } - - connectedCallback() { - super.connectedCallback(); - this.actionId = this.plugin.getPluginName() + '~delete'; - this.action = this.config.actions[this.actionId]; - this.hidden = !this.action; - } - - _handleCommandTap() { - this.$.deleteRepoOverlay.open(); - } - - _handleCloseDeleteRepo() { - this.$.deleteRepoOverlay.close(); - } - - _handleDeleteRepo() { - const endpoint = '/projects/' + - encodeURIComponent(this.repoName) + '/' + - this.actionId; - - const json = { - force: this.$.forceDeleteOpenChangesCheckBox.checked, - preserve: this.$.preserveGitRepoCheckBox.checked, - }; - - const errFn = response => { - this.dispatchEvent(new CustomEvent('page-error', { - detail: {response}, - bubbles: true, - composed: true, - })); - }; - - return this.plugin.restApi().send( - this.action.method, endpoint, json, errFn) - .then(r => { - this.plugin.restApi().invalidateReposCache(); - Gerrit.Nav.navigateToRelativeUrl('/admin/repos'); - }); - } -} - -customElements.define(GrDeleteRepo.is, GrDeleteRepo);
diff --git a/gr-delete-repo/gr-delete-repo_html.js b/gr-delete-repo/gr-delete-repo_html.js deleted file mode 100644 index e3a985d..0000000 --- a/gr-delete-repo/gr-delete-repo_html.js +++ /dev/null
@@ -1,66 +0,0 @@ -/** - * @license - * Copyright (C) 2018 The Android Open Source Project - * - * 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. - */ - -export const htmlTemplate = Polymer.html` - <style include="shared-styles"></style> - <style include="gr-form-styles"> - :host { - display: block; - margin-bottom: var(--spacing-xxl); - } - </style> - <h3 class="heading-3"> - [[action.label]] - </h3> - <gr-button - title="[[action.title]]" - loading="[[_deleting]]" - disabled="[[!action.enabled]]" - on-click="_handleCommandTap" - > - [[action.label]] - </gr-button> - <gr-overlay id="deleteRepoOverlay" with-backdrop> - <gr-dialog - id="deleteRepoDialog" - confirm-label="Delete" - on-confirm="_handleDeleteRepo" - on-cancel="_handleCloseDeleteRepo"> - <div class="header" slot="header"> - Are you really sure you want to delete the repo: "[[repoName]]"? - </div> - <div class="main" slot="main"> - <div class="gr-form-styles"> - <div id="form"> - <section> - <input - type="checkbox" - id="forceDeleteOpenChangesCheckBox"> - <label for="forceDeleteOpenChangesCheckBox">Delete repo even if open changes exist?</label> - </section> - <section> - <input - type="checkbox" - id="preserveGitRepoCheckBox"> - <label for="preserveGitRepoCheckBox">Preserve GIT Repository?</label> - </section> - </div> - </div> - </div> - </gr-dialog> - </gr-overlay> -`;
diff --git a/package.json b/package.json deleted file mode 100644 index 5a46795..0000000 --- a/package.json +++ /dev/null
@@ -1,13 +0,0 @@ -{ - "name": "delete-project", - "description": "Delete project plugin", - "browser": true, - "scripts": { - "safe_bazelisk": "if which bazelisk >/dev/null; then bazel_bin=bazelisk; else bazel_bin=bazel; fi && $bazel_bin", - "eslint": "npm run safe_bazelisk test :lint_test", - "eslintfix": "npm run safe_bazelisk run :lint_bin -- -- --fix $(pwd)" - }, - "devDependencies": {}, - "license": "Apache-2.0", - "private": true -}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/HttpModule.java index e0d8541..563b1ab 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/HttpModule.java +++ b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/HttpModule.java
@@ -23,6 +23,6 @@ @Override protected void configureServlets() { DynamicSet.bind(binder(), WebUiPlugin.class) - .toInstance(new JavaScriptPlugin("delete-project.js")); + .toInstance(new JavaScriptPlugin("gr-delete-repo.js")); } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/Module.java b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/PluginModule.java similarity index 96% rename from src/main/java/com/googlesource/gerrit/plugins/deleteproject/Module.java rename to src/main/java/com/googlesource/gerrit/plugins/deleteproject/PluginModule.java index 7a91b29..4d0a802 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/Module.java +++ b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/PluginModule.java
@@ -30,12 +30,12 @@ import com.googlesource.gerrit.plugins.deleteproject.fs.DeleteTrashFolders; import com.googlesource.gerrit.plugins.deleteproject.fs.FilesystemDeleteHandler; -public class Module extends AbstractModule { +public class PluginModule extends AbstractModule { private final boolean scheduleCleaning; @Inject - Module(Configuration config) { + PluginModule(Configuration config) { this.scheduleCleaning = config.getArchiveDuration() > 0; }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProjectIT.java b/src/test/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProjectIT.java index e878096..bfc44a1 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProjectIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProjectIT.java
@@ -46,6 +46,7 @@ import java.nio.file.Paths; import java.util.stream.Stream; import org.apache.commons.io.FileUtils; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RemoteRefUpdate; @@ -57,7 +58,7 @@ @UseSsh @TestPlugin( name = "delete-project", - sysModule = "com.googlesource.gerrit.plugins.deleteproject.Module", + sysModule = "com.googlesource.gerrit.plugins.deleteproject.PluginModule", sshModule = "com.googlesource.gerrit.plugins.deleteproject.SshModule", httpModule = "com.googlesource.gerrit.plugins.deleteproject.HttpModule") public class DeleteProjectIT extends LightweightPluginDaemonTest { @@ -258,7 +259,12 @@ String projectName = createProjectOverAPI(name, null, true, null).get(); File projectDir = verifyProjectRepoExists(Project.NameKey.parse(projectName)); - Path parentFolder = projectDir.toPath().getParent().resolve(PARENT_FOLDER).resolve(projectName); + Path parentFolder = + projectDir + .toPath() + .getParent() + .resolve(PARENT_FOLDER) + .resolve(projectName + Constants.DOT_GIT); parentFolder.toFile().mkdirs(); assertThat(parentFolder.toFile().exists()).isTrue(); assertThat(isEmpty(parentFolder)).isTrue();
diff --git a/gr-delete-repo/plugin.js b/web/.eslintrc.js similarity index 74% copy from gr-delete-repo/plugin.js copy to web/.eslintrc.js index 2e22d59..ee166d3 100644 --- a/gr-delete-repo/plugin.js +++ b/web/.eslintrc.js
@@ -1,6 +1,6 @@ /** * @license - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import './gr-delete-repo.js'; - -Gerrit.install(plugin => { - plugin.registerCustomComponent( - 'repo-command', 'gr-delete-repo'); -}); +__plugindir = 'delete-project/web'; +module.exports = { + extends: '../../.eslintrc.js', +};
diff --git a/web/BUILD b/web/BUILD new file mode 100644 index 0000000..5490c23 --- /dev/null +++ b/web/BUILD
@@ -0,0 +1,61 @@ +load("//tools/js:eslint.bzl", "plugin_eslint") +load("//tools/bzl:js.bzl", "gerrit_js_bundle", "karma_test") +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") + +package_group( + name = "visibility", + packages = ["//plugins/delete-project/..."], +) + +package(default_visibility = [":visibility"]) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//plugins:tsconfig-plugins-base.json", + ], +) + +ts_project( + name = "delete-project-ts", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*test*"], + ), + incremental = True, + out_dir = "_bazel_ts_out", + tsc = "//tools/node_tools:tsc-bin", + tsconfig = ":tsconfig", + deps = [ + "@plugins_npm//@gerritcodereview/typescript-api", + "@plugins_npm//lit", + ], +) + +ts_project( + name = "delete-project-ts-tests", + srcs = glob(["**/*.ts"]), + incremental = True, + out_dir = "_bazel_ts_out_tests", + tsc = "//tools/node_tools:tsc-bin", + tsconfig = ":tsconfig", + deps = [ + "@plugins_npm//:node_modules", + "@ui_dev_npm//:node_modules", + ], +) + +gerrit_js_bundle( + name = "gr-delete-repo", + srcs = [":delete-project-ts"], + entry_point = "_bazel_ts_out/plugin.js", +) + +karma_test( + name = "karma_test", + srcs = ["karma_test.sh"], + data = [":delete-project-ts-tests"], +) + +plugin_eslint()
diff --git a/web/gr-delete-repo.ts b/web/gr-delete-repo.ts new file mode 100644 index 0000000..5a2ece1 --- /dev/null +++ b/web/gr-delete-repo.ts
@@ -0,0 +1,180 @@ +/** + * @license + * Copyright (C) 2018 The Android Open Source Project + * + * 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. + */ +import {PluginApi} from '@gerritcodereview/typescript-api/plugin'; +import { + ActionInfo, + ConfigInfo, + RepoName, +} from '@gerritcodereview/typescript-api/rest-api'; +import {css, html, LitElement} from 'lit'; +import {customElement, property, query, state} from 'lit/decorators'; + +// TODO: This should be defined and exposed by @gerritcodereview/typescript-api +type GrOverlay = Element & { + open(): void; + close(): void; +}; + +declare global { + interface HTMLElementTagNameMap { + 'gr-delete-repo': GrDeleteRepo; + } + interface Window { + CANONICAL_PATH?: string; + } +} + +@customElement('gr-delete-repo') +export class GrDeleteRepo extends LitElement { + @query('#deleteRepoOverlay') + deleteRepoOverlay?: GrOverlay; + + @query('#preserveGitRepoCheckBox') + preserveGitRepoCheckBox?: HTMLInputElement; + + @query('#forceDeleteOpenChangesCheckBox') + forceDeleteOpenChangesCheckBox?: HTMLInputElement; + + /** Guaranteed to be provided by the 'repo-command' endpoint. */ + @property({type: Object}) + plugin!: PluginApi; + + /** Guaranteed to be provided by the 'repo-command' endpoint. */ + @property({type: Object}) + config!: ConfigInfo; + + /** Guaranteed to be provided by the 'repo-command' endpoint. */ + @property({type: String}) + repoName!: RepoName; + + @state() + private error?: string; + + static override styles = css` + :host { + display: block; + margin-bottom: var(--spacing-xxl); + } + /* TODO: Find a way to use shared styles in lit elements in plugins. */ + h3 { + font: inherit; + margin: 0; + } + .error { + color: red; + } + `; + + get action(): ActionInfo | undefined { + return this.config.actions?.[this.actionId]; + } + + get actionId(): string { + return `${this.plugin.getPluginName()}~delete`; + } + + private renderError() { + if (!this.error) return; + return html`<div class="error">${this.error}</div>`; + } + + override render() { + if (!this.action) return; + return html` + <h3>${this.action.label}</h3> + <gr-button + title="${this.action.title}" + ?disabled="${!this.action.enabled}" + @click="${() => { + this.error = undefined; + this.deleteRepoOverlay?.open(); + }}" + > + ${this.action.label} + </gr-button> + ${this.renderError()} + <gr-overlay id="deleteRepoOverlay" with-backdrop> + <gr-dialog + id="deleteRepoDialog" + confirm-label="Delete" + @confirm="${this.deleteRepo}" + @cancel="${() => this.deleteRepoOverlay?.close()}" + > + <div class="header" slot="header"> + Are you really sure you want to delete the repo: "${this.repoName}"? + </div> + <div class="main" slot="main"> + <div> + <div id="form"> + <section> + <input type="checkbox" id="forceDeleteOpenChangesCheckBox" /> + <label for="forceDeleteOpenChangesCheckBox" + >Delete repo even if open changes exist?</label + > + </section> + <section> + <input type="checkbox" id="preserveGitRepoCheckBox" /> + <label for="preserveGitRepoCheckBox" + >Preserve GIT Repository?</label + > + </section> + </div> + </div> + </div> + </gr-dialog> + </gr-overlay> + `; + } + + private deleteRepo() { + if (!this.action) { + this.error = 'delete action undefined'; + this.deleteRepoOverlay?.close(); + return; + } + if (!this.action.method) { + this.error = 'delete action does not have a HTTP method set'; + this.deleteRepoOverlay?.close(); + return; + } + this.error = undefined; + + const endpoint = `/projects/${encodeURIComponent(this.repoName)}/${ + this.actionId + }`; + const json = { + force: this.forceDeleteOpenChangesCheckBox?.checked ?? false, + preserve: this.preserveGitRepoCheckBox?.checked ?? false, + }; + return this.plugin + .restApi() + .send(this.action.method, endpoint, json) + .then(_ => { + this.plugin.restApi().invalidateReposCache(); + this.deleteRepoOverlay?.close(); + window.location.href = `${this.getBaseUrl()}/admin/repos`; + }) + .catch(e => { + this.error = e; + this.deleteRepoOverlay?.close(); + }); + } + + private getBaseUrl() { + return window.CANONICAL_PATH || ''; + } +}
diff --git a/web/gr-delete-repo_test.ts b/web/gr-delete-repo_test.ts new file mode 100644 index 0000000..2da6b2b --- /dev/null +++ b/web/gr-delete-repo_test.ts
@@ -0,0 +1,76 @@ +/** + * @license + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ +import './test/test-setup'; +import './gr-delete-repo'; +import {queryAndAssert} from './test/test-util'; +import {PluginApi} from '@gerritcodereview/typescript-api/plugin'; +import {GrDeleteRepo} from './gr-delete-repo'; +import { + ConfigInfo, + HttpMethod, + RepoName, +} from '@gerritcodereview/typescript-api/rest-api'; + +suite('gr-delete-repo tests', () => { + let element: GrDeleteRepo; + let sendStub: sinon.SinonStub; + + setup(async () => { + sendStub = sinon.stub(); + sendStub.returns(Promise.resolve({})); + + element = document.createElement('gr-delete-repo'); + element.repoName = 'test-repo-name' as RepoName; + element.config = { + actions: { + 'delete-project~delete': { + label: 'test-action-label', + title: 'test-aciton-title', + enabled: true, + method: HttpMethod.DELETE, + }, + }, + } as unknown as ConfigInfo; + element.plugin = { + getPluginName: () => 'delete-project', + restApi: () => { + return { + send: sendStub, + invalidateReposCache: () => {}, + }; + }, + } as unknown as PluginApi; + document.body.appendChild(element); + await element.updateComplete; + }); + + teardown(() => { + document.body.removeChild(element); + }); + + test('confirm and send', () => { + const dialog = queryAndAssert<HTMLElement>(element, '#deleteRepoDialog'); + dialog.dispatchEvent(new CustomEvent('confirm')); + assert.isTrue(sendStub.called); + const method = sendStub.firstCall.args[0] as HttpMethod; + const endpoint = sendStub.firstCall.args[1] as string; + const json = sendStub.firstCall.args[2]; + assert.equal(method, HttpMethod.DELETE); + assert.equal(endpoint, '/projects/test-repo-name/delete-project~delete'); + assert.deepEqual(json, {force: false, preserve: false}); + }); +});
diff --git a/web/karma_test.sh b/web/karma_test.sh new file mode 100755 index 0000000..35430ad --- /dev/null +++ b/web/karma_test.sh
@@ -0,0 +1,6 @@ +#!/bin/bash + +set -euo pipefail +./$1 start $2 --single-run \ + --root 'plugins/delete-project/web/_bazel_ts_out_tests/' \ + --test-files '*_test.js'
diff --git a/gr-delete-repo/plugin.js b/web/plugin.ts similarity index 77% rename from gr-delete-repo/plugin.js rename to web/plugin.ts index 2e22d59..7355b50 100644 --- a/gr-delete-repo/plugin.js +++ b/web/plugin.ts
@@ -14,9 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import './gr-delete-repo.js'; +import '@gerritcodereview/typescript-api/gerrit'; +import './gr-delete-repo'; -Gerrit.install(plugin => { - plugin.registerCustomComponent( - 'repo-command', 'gr-delete-repo'); +window.Gerrit.install(plugin => { + plugin.registerCustomComponent('repo-command', 'gr-delete-repo'); });
diff --git a/web/test/test-setup.ts b/web/test/test-setup.ts new file mode 100644 index 0000000..ddeaf89 --- /dev/null +++ b/web/test/test-setup.ts
@@ -0,0 +1,43 @@ +/** + * @license + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ +import 'chai/chai'; +import '@gerritcodereview/typescript-api/gerrit'; +import {css} from 'lit'; + +declare global { + interface Window { + assert: typeof chai.assert; + expect: typeof chai.expect; + sinon: typeof sinon; + } + let assert: typeof chai.assert; + let expect: typeof chai.expect; + let sinon: typeof sinon; +} +window.assert = chai.assert; +window.expect = chai.expect; +window.sinon = sinon; + +window.Gerrit = { + install: () => {}, + styles: { + form: css``, + menuPage: css``, + subPage: css``, + table: css``, + }, +};
diff --git a/web/test/test-util.ts b/web/test/test-util.ts new file mode 100644 index 0000000..25a1c79 --- /dev/null +++ b/web/test/test-util.ts
@@ -0,0 +1,42 @@ +/** + * @license + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ +export function queryAll<E extends Element = Element>( + el: Element, + selector: string +): NodeListOf<E> { + if (!el) throw new Error('element not defined'); + const root = el.shadowRoot ?? el; + return root.querySelectorAll<E>(selector); +} + +export function query<E extends Element = Element>( + el: Element | undefined, + selector: string +): E | undefined { + if (!el) return undefined; + const root = el.shadowRoot ?? el; + return root.querySelector<E>(selector) ?? undefined; +} + +export function queryAndAssert<E extends Element = Element>( + el: Element | undefined, + selector: string +): E { + const found = query<E>(el, selector); + if (!found) throw new Error(`selector '${selector}' did not match anything'`); + return found; +}
diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 0000000..d12f85e --- /dev/null +++ b/web/tsconfig.json
@@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig-plugins-base.json", + "compilerOptions": { + "outDir": "../../../.ts-out/plugins/delete-project", /* overridden by bazel */ + }, + "include": [ + "**/*.ts" + ] +}