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 @@
[![Build Status](https://gerrit-ci.gerritforge.com/view/Plugins-master/job/plugin-delete-project-bazel-master/badge/icon
)](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"
+ ]
+}