Convert from Polymer to Lit
Change-Id: I4486450eef72d97edb9c9bb265ed9f41b2ca688f
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/BUILD b/BUILD
index 3c5aaa8..4070f5a 100644
--- a/BUILD
+++ b/BUILD
@@ -1,15 +1,11 @@
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 = "zuul",
@@ -21,7 +17,7 @@
"Gerrit-Module: com.googlesource.gerrit.plugins.zuul.Module",
"Gerrit-HttpModule: com.googlesource.gerrit.plugins.zuul.HttpModule",
],
- resource_jars = [":gr-zuul-static"],
+ resource_jars = ["//plugins/zuul/web:zuul"],
)
junit_tests(
@@ -43,55 +39,3 @@
"@commons-lang3//jar",
],
)
-
-genrule2(
- name = "gr-zuul-static",
- srcs = [":gr-zuul"],
- outs = ["gr-zuul-static.jar"],
- cmd = " && ".join([
- "mkdir $$TMP/static",
- "cp $(locations :gr-zuul) $$TMP/static",
- "cd $$TMP",
- "zip -Drq $$ROOT/$@ -g .",
- ]),
-)
-
-polygerrit_plugin(
- name = "gr-zuul",
- app = "zuul-bundle.js",
- plugin_name = "zuul",
-)
-
-rollup_bundle(
- name = "zuul-bundle",
- srcs = glob(["gr-zuul/*.js"]),
- entry_point = "gr-zuul/plugin.js",
- rollup_bin = "//tools/node_tools:rollup-bin",
- sourcemap = "hidden",
- format = "iife",
- deps = [
- "@tools_npm//rollup-plugin-node-resolve",
- ],
-)
-
-# Define the eslinter for the plugin
-# The eslint macro creates 2 rules: lint_test and lint_bin
-eslint(
- name = "lint",
- srcs = glob([
- "gr-zuul/**/*.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/gr-zuul/gr-zuul.js b/gr-zuul/gr-zuul.js
deleted file mode 100644
index b6893ce..0000000
--- a/gr-zuul/gr-zuul.js
+++ /dev/null
@@ -1,119 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 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-zuul_html.js';
-
-class GrZuul extends Polymer.Element {
- /** @returns {string} name of the component */
- static get is() { return 'gr-zuul'; }
-
- /** @returns {?} template for this component */
- static get template() { return htmlTemplate; }
-
- static get properties() {
- return {
- change: {
- type: Object,
- observer: '_onChangeChanged',
- },
- hidden: {
- type: Boolean,
- value: true,
- reflectToAttribute: true,
- },
- _crd: {
- type: Object,
- value: {},
- },
- _crd_loaded: {
- type: Boolean,
- value: false,
- },
- };
- }
-
- _onChangeChanged() {
- this._crd_loaded = false;
- this.setHidden(true);
- const url = '/changes/' + this.change.id + '/revisions/current/crd';
- return this.plugin.restApi().send('GET', url).then(crd => {
- this._crd = crd;
- this._crd_loaded = true;
- this.setHidden(!(this._isDependsOnSectionVisible()
- || crd.needed_by.length));
- });
- }
-
- // copied from gr-related-changes-list.js, which is inaccessible from here.
- // Resolved uses of `this.ChangeStatus.[...]`, as that's inaccessible from here too.
- // Removed _isIndirectAncestor check, as the needed data is inaccessible from here.
- // Not all code paths are reachable, as we only have shallow ChangeInfo objects. We leave the
- // code here nonetheless, to allow for easier updating from gr-related-changes-list.js.
- _computeChangeStatusClass(change) {
- const classes = ['status'];
- if (change._revision_number != change._current_revision_number) {
- classes.push('notCurrent');
- } else if (change.submittable) {
- classes.push('submittable');
- } else if (change.status == 'NEW') {
- classes.push('hidden');
- }
- return classes.join(' ');
- }
-
- // copied from gr-related-changes-list.js, which is inaccessible from here.
- // Resolved uses of `this.ChangeStatus.[...]`, as that's inaccessible from here too.
- // Removed _isIndirectAncestor check, as the needed data is inaccessible from here.
- // Not all code paths are reachable, as we only have shallow ChangeInfo objects. We leave the
- // code here nonetheless, to allow for easier updating from gr-related-changes-list.js.
- _computeChangeStatus(change) {
- switch (change.status) {
- case 'MERGED':
- return 'Merged';
- case 'ABANDONED':
- return 'Abandoned';
- }
- if (change._revision_number != change._current_revision_number) {
- return 'Not current';
- } else if (change.submittable) {
- return 'Submittable';
- }
- return '';
- }
-
- setHidden(hidden) {
- if (this.hidden != hidden) {
- this.hidden = hidden;
-
- // Flag to parents that something changed
- this.dispatchEvent(new CustomEvent('new-section-loaded', {
- composed: true, bubbles: true,
- }));
- }
- }
-
- _computeDependencyUrl(changeInfo) {
- return `${window.CANONICAL_PATH || ''}/q/${changeInfo.change_id}`;
- }
-
- _isDependsOnSectionVisible() {
- return !!(this._crd.depends_on_found.length
- + this._crd.depends_on_missing.length);
- }
-}
-
-customElements.define(GrZuul.is, GrZuul);
diff --git a/gr-zuul/gr-zuul_html.js b/gr-zuul/gr-zuul_html.js
deleted file mode 100644
index 5e208d4..0000000
--- a/gr-zuul/gr-zuul_html.js
+++ /dev/null
@@ -1,129 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 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">
- section.related-changes-section {
- margin-bottom: 1.4em; /* Same as line height for collapse purposes */
- display: block;
- }
- div.foo {
- margin-bottom: 1.4em; /* Same as line height for collapse purposes */
- }
- a {
- display: block;
- }
- .changeContainer,
- a {
- max-width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .changeContainer {
- display: flex;
- }
- .changeContainer.thisChange:before {
- content: '➔';
- width: 1.2em;
- }
- h4,
- section div {
- display: flex;
- }
- h4:before,
- section div:before {
- content: ' ';
- flex-shrink: 0;
- width: 1.2em;
- }
- .status {
- color: var(--deemphasized-text-color);
- font-weight: var(--font-weight-bold);
- margin-left: var(--spacing-xs);
- }
- /* The above styles are copy/paste from gr-related-changes-list_html.js */
- .dependencyCycleDetected {
- color: #d17171;
- }
- .missingFromThisServer {
- color: #d17171;
- }
- .hidden {
- display: none;
- }
- </style>
- <template is="dom-if" if="[[_crd_loaded]]">
- <template is="dom-if" if="[[_isDependsOnSectionVisible()]]">
- <section class="related-changes-section">
- <h4>Depends on</h4>
- <template is="dom-repeat" items="[[_crd.depends_on_found]]">
- <div class="changeContainer zuulDependencyContainer">
- <a
- href$="[[_computeDependencyUrl(item)]]"
- title$="[[item.project]]: [[item.branch]]: [[item.subject]]"
- >
- [[item.project]]: [[item.branch]]: [[item.subject]]
- </a>
- <span class$="[[_computeChangeStatusClass(item)]]">
- ([[_computeChangeStatus(item)]])
- </span>
- <template is="dom-if" if="[[_crd.cycle]]">
- <span class="status dependencyCycleDetected">
- (Dependency cycle detected)
- </span>
- </template>
- </div>
- </template>
- <template is="dom-repeat" items="[[_crd.depends_on_missing]]">
- <div class="changeContainer zuulDependencyContainer">
- <span>
- [[item]]
- </span>
- <span class="status missingFromThisServer">
- (Missing from this server)
- </span>
- </div>
- </template>
- </section>
- </template>
- <template is="dom-if" if="[[_crd.needed_by.length]]">
- <section class="related-changes-section">
- <h4>Needed by</h4>
- <template is="dom-repeat" items="[[_crd.needed_by]]">
- <div class="changeContainer zuulDependencyContainer">
- <a
- href$="[[_computeDependencyUrl(item)]]"
- title$="[[item.project]]: [[item.branch]]: [[item.subject]]"
- >
- [[item.project]]: [[item.branch]]: [[item.subject]]
- </a>
- <span class$="[[_computeChangeStatusClass(item)]]">
- ([[_computeChangeStatus(item)]])
- </span>
- <template is="dom-if" if="[[_crd.cycle]]">
- <span class="status dependencyCycleDetected">
- (Dependency cycle detected)
- </span>
- </template>
- </div>
- </template>
- </section>
- </template>
- </template>
-`;
-
diff --git a/web/BUILD b/web/BUILD
new file mode 100644
index 0000000..991390e
--- /dev/null
+++ b/web/BUILD
@@ -0,0 +1,42 @@
+load("//tools/js:eslint.bzl", "plugin_eslint")
+load("//tools/bzl:js.bzl", "gerrit_js_bundle")
+load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
+
+package_group(
+ name = "visibility",
+ packages = ["//plugins/zuul/..."],
+)
+
+package(default_visibility = [":visibility"])
+
+ts_config(
+ name = "tsconfig",
+ src = "tsconfig.json",
+ deps = [
+ "//plugins:tsconfig-plugins-base.json",
+ ],
+)
+
+ts_project(
+ name = "zuul-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",
+ ],
+)
+
+gerrit_js_bundle(
+ name = "zuul",
+ srcs = [":zuul-ts"],
+ entry_point = "_bazel_ts_out/plugin.js",
+)
+
+plugin_eslint()
diff --git a/web/eslint.config.js b/web/eslint.config.js
new file mode 100644
index 0000000..54028d5
--- /dev/null
+++ b/web/eslint.config.js
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2025 The Android Open Source Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+const {defineConfig} = require('eslint/config');
+
+// eslint-disable-next-line no-undef
+__plugindir = 'zuul/web';
+
+const gerritEslint = require('../../eslint.config.js');
+
+module.exports = defineConfig([
+ {
+ extends: [gerritEslint],
+ },
+]);
diff --git a/web/gr-zuul.ts b/web/gr-zuul.ts
new file mode 100644
index 0000000..9d28ab2
--- /dev/null
+++ b/web/gr-zuul.ts
@@ -0,0 +1,250 @@
+/**
+ * @license
+ * Copyright (C) 2025 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 {ChangeInfo, NumericChangeId} from '@gerritcodereview/typescript-api/rest-api';
+import {LitElement, html, css, nothing} from 'lit';
+import {customElement, property, state} from 'lit/decorators.js';
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'gr-zuul': GrZuul;
+ }
+}
+
+interface CrdInfo {
+ depends_on_found?: ChangeInfo[];
+ depends_on_missing?: string[];
+ needed_by?: ChangeInfo[];
+ cycle?: boolean;
+}
+
+// Partial copy of https://github.com/GerritCodeReview/gerrit/blob/b42341c5cd9b1f1535df30b16f180a90617fd067/polygerrit-ui/app/types/common.ts#L1377
+interface RelatedChangeAndCommitInfo {
+ _change_number?: NumericChangeId;
+ _revision_number?: number;
+ _current_revision_number?: number;
+ status?: string;
+ submittable?: boolean;
+}
+
+
+@customElement('gr-zuul')
+export class GrZuul extends LitElement {
+ @property({type: Object}) change?: ChangeInfo;
+
+ @state() private _crd: CrdInfo = {};
+ @state() private _crdLoaded = false;
+
+ static override get styles() {
+ return [
+ css`
+ section.related-changes-section {
+ margin-bottom: 1.4em;
+ display: block;
+ }
+ a {
+ display: block;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .changeContainer {
+ display: flex;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .changeContainer.thisChange:before {
+ content: '➔';
+ width: 1.2em;
+ }
+ h4,
+ section div {
+ display: flex;
+ }
+ h4:before,
+ section div:before {
+ content: ' ';
+ flex-shrink: 0;
+ width: 1.2em;
+ }
+ .status {
+ color: var(--deemphasized-text-color);
+ font-weight: var(--font-weight-bold);
+ margin-left: var(--spacing-xs);
+ }
+ .dependencyCycleDetected,
+ .missingFromThisServer {
+ color: #d17171;
+ }
+ .hidden {
+ display: none;
+ }
+ `,
+ ];
+ }
+
+ override render() {
+ if (!this._crdLoaded) return nothing;
+
+ return html`
+ ${this._isDependsOnSectionVisible()
+ ? html`
+ <section class="related-changes-section">
+ <h4>Depends on</h4>
+ ${this._crd.depends_on_found?.map(
+ item => html`
+ <div class="changeContainer zuulDependencyContainer">
+ <a
+ href=${this._computeDependencyUrl(item)}
+ title="${item.project}: ${item.branch}: ${item.subject}"
+ >
+ ${item.project}: ${item.branch}: ${item.subject}
+ </a>
+ <span class=${this._computeChangeStatusClass(item)}>
+ (${this._computeChangeStatus(item)})
+ </span>
+ ${this._crd.cycle
+ ? html`
+ <span class="status dependencyCycleDetected">
+ (Dependency cycle detected)
+ </span>
+ `
+ : nothing}
+ </div>
+ `
+ )}
+ ${this._crd.depends_on_missing?.map(
+ item => html`
+ <div class="changeContainer zuulDependencyContainer">
+ <span>${item}</span>
+ <span class="status missingFromThisServer">
+ (Missing from this server)
+ </span>
+ </div>
+ `
+ )}
+ </section>
+ `
+ : nothing}
+
+ ${this._crd.needed_by?.length
+ ? html`
+ <section class="related-changes-section">
+ <h4>Needed by</h4>
+ ${this._crd.needed_by.map(
+ item => html`
+ <div class="changeContainer zuulDependencyContainer">
+ <a
+ href=${this._computeDependencyUrl(item)}
+ title="${item.project}: ${item.branch}: ${item.subject}"
+ >
+ ${item.project}: ${item.branch}: ${item.subject}
+ </a>
+ <span class=${this._computeChangeStatusClass(item)}>
+ (${this._computeChangeStatus(item)})
+ </span>
+ ${this._crd.cycle
+ ? html`
+ <span class="status dependencyCycleDetected">
+ (Dependency cycle detected)
+ </span>
+ `
+ : nothing}
+ </div>
+ `
+ )}
+ </section>
+ `
+ : nothing}
+ `;
+ }
+
+ override updated(changedProperties: Map<string, unknown>) {
+ if (changedProperties.has('change')) {
+ void this._onChangeChanged();
+ }
+ }
+
+ private async _onChangeChanged(): Promise<void> {
+ this._crdLoaded = false;
+ this._setHidden(true);
+ if (!this.change?.id) return;
+
+ const url = `/changes/${this.change.id}/revisions/current/crd`;
+ const plugin = (this as any).plugin;
+
+ const crd: CrdInfo = await plugin.restApi().send('GET', url);
+ this._crd = crd;
+ this._crdLoaded = true;
+
+ const visible = this._isDependsOnSectionVisible() || (crd.needed_by?.length ?? 0) > 0;
+ this._setHidden(!visible);
+ }
+
+ private _setHidden(hidden: boolean): void {
+ if (this.hidden !== hidden) {
+ this.hidden = hidden;
+ this.dispatchEvent(
+ new CustomEvent('new-section-loaded', {
+ composed: true,
+ bubbles: true,
+ })
+ );
+ }
+ }
+
+ private _computeChangeStatusClass(change: RelatedChangeAndCommitInfo): string {
+ const classes = ['status'];
+ if (change._revision_number !== change._current_revision_number) {
+ classes.push('notCurrent');
+ } else if (change.submittable) {
+ classes.push('submittable');
+ } else if (change.status === 'NEW') {
+ classes.push('hidden');
+ }
+ return classes.join(' ');
+ }
+
+ private _computeChangeStatus(change: RelatedChangeAndCommitInfo): string {
+ switch (change.status) {
+ case 'MERGED':
+ return 'Merged';
+ case 'ABANDONED':
+ return 'Abandoned';
+ default:
+ if (change._revision_number !== change._current_revision_number) {
+ return 'Not current';
+ } else if (change.submittable) {
+ return 'Submittable';
+ }
+ return '';
+ }
+ }
+
+ private _computeDependencyUrl(changeInfo: ChangeInfo): string {
+ const base = (window as any).CANONICAL_PATH || '';
+ return `${base}/q/${changeInfo.change_id}`;
+ }
+
+ private _isDependsOnSectionVisible(): boolean {
+ const {depends_on_found, depends_on_missing} = this._crd;
+ return (depends_on_found?.length ?? 0) + (depends_on_missing?.length ?? 0) > 0;
+ }
+}
diff --git a/gr-zuul/plugin.js b/web/plugin.ts
similarity index 80%
rename from gr-zuul/plugin.js
rename to web/plugin.ts
index 475f053..d75cd7d 100644
--- a/gr-zuul/plugin.js
+++ b/web/plugin.ts
@@ -1,6 +1,6 @@
/**
* @license
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2025 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,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import './gr-zuul.js';
-Gerrit.install(plugin => {
+import '@gerritcodereview/typescript-api/gerrit';
+import './gr-zuul';
+
+window.Gerrit?.install(plugin => {
plugin.registerCustomComponent(
'related-changes-section', 'gr-zuul', {slot: 'bottom'});
});
diff --git a/web/tsconfig.json b/web/tsconfig.json
new file mode 100644
index 0000000..78f5cc7
--- /dev/null
+++ b/web/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig-plugins-base.json",
+ "compilerOptions": {
+ "outDir": "../../../.ts-out/plugins/zuul" /* overridden by bazel */
+ },
+ "include": [
+ "**/*.ts"
+ ]
+}