Merge changes from topic "ts-repo-commands"

* changes:
  Convert files to typescript
  Rename files to preserve history
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
index ba22eff..aee5a14 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
@@ -132,8 +132,10 @@
     this.canCreate = !!branch && !!subject;
   }
 
-  handleCreateChange() {
-    if (!this.repoName || !this.branch || !this.subject) return;
+  handleCreateChange(): Promise<void> {
+    if (!this.repoName || !this.branch || !this.subject) {
+      return Promise.resolve();
+    }
     const isPrivate = this.$.privateChangeCheckBox.checked;
     const isWip = true;
     return this.$.restAPI
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js
deleted file mode 100644
index 4ab1b98..0000000
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js
+++ /dev/null
@@ -1,158 +0,0 @@
-/**
- * @license
- * Copyright (C) 2017 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 '@polymer/iron-autogrow-textarea/iron-autogrow-textarea.js';
-import '../../../styles/gr-form-styles.js';
-import '../../../styles/gr-subpage-styles.js';
-import '../../../styles/shared-styles.js';
-import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.js';
-import '../../plugins/gr-endpoint-param/gr-endpoint-param.js';
-import '../../shared/gr-dialog/gr-dialog.js';
-import '../../shared/gr-overlay/gr-overlay.js';
-import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
-import '../gr-create-change-dialog/gr-create-change-dialog.js';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
-import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
-import {PolymerElement} from '@polymer/polymer/polymer-element.js';
-import {htmlTemplate} from './gr-repo-commands_html.js';
-import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-
-const GC_MESSAGE = 'Garbage collection completed successfully.';
-
-const CONFIG_BRANCH = 'refs/meta/config';
-const CONFIG_PATH = 'project.config';
-const EDIT_CONFIG_SUBJECT = 'Edit Repo Config';
-const INITIAL_PATCHSET = 1;
-const CREATE_CHANGE_FAILED_MESSAGE = 'Failed to create change.';
-const CREATE_CHANGE_SUCCEEDED_MESSAGE = 'Navigating to change';
-
-/**
- * @extends PolymerElement
- */
-class GrRepoCommands extends GestureEventListeners(
-    LegacyElementMixin(PolymerElement)) {
-  static get template() { return htmlTemplate; }
-
-  static get is() { return 'gr-repo-commands'; }
-
-  static get properties() {
-    return {
-      params: Object,
-      repo: String,
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      /** @type {?} */
-      _repoConfig: Object,
-      _canCreate: Boolean,
-      // states
-      _creatingChange: Boolean,
-      _editingConfig: Boolean,
-      _runningGC: Boolean,
-    };
-  }
-
-  /** @override */
-  attached() {
-    super.attached();
-    this._loadRepo();
-
-    this.dispatchEvent(new CustomEvent('title-change', {
-      detail: {title: 'Repo Commands'},
-      composed: true, bubbles: true,
-    }));
-  }
-
-  _loadRepo() {
-    if (!this.repo) { return Promise.resolve(); }
-
-    const errFn = response => {
-      this.dispatchEvent(new CustomEvent('page-error', {
-        detail: {response},
-        composed: true, bubbles: true,
-      }));
-    };
-
-    return this.$.restAPI.getProjectConfig(this.repo, errFn)
-        .then(config => {
-          if (!config) { return Promise.resolve(); }
-
-          this._repoConfig = config;
-          this._loading = false;
-        });
-  }
-
-  _computeLoadingClass(loading) {
-    return loading ? 'loading' : '';
-  }
-
-  _isLoading() {
-    return this._loading || this._loading === undefined;
-  }
-
-  _handleRunningGC() {
-    this._runningGC = true;
-    return this.$.restAPI.runRepoGC(this.repo).then(response => {
-      if (response.status === 200) {
-        this.dispatchEvent(new CustomEvent(
-            'show-alert',
-            {detail: {message: GC_MESSAGE}, bubbles: true, composed: true}));
-      }
-    })
-        .finally(() => {
-          this._runningGC = false;
-        });
-  }
-
-  _createNewChange() {
-    this.$.createChangeOverlay.open();
-  }
-
-  _handleCreateChange() {
-    this._creatingChange = true;
-    this.$.createNewChangeModal.handleCreateChange()
-        .finally(() => {
-          this._creatingChange = false;
-        });
-    this._handleCloseCreateChange();
-  }
-
-  _handleCloseCreateChange() {
-    this.$.createChangeOverlay.close();
-  }
-
-  _handleEditRepoConfig() {
-    this._editingConfig = true;
-    return this.$.restAPI.createChange(this.repo, CONFIG_BRANCH,
-        EDIT_CONFIG_SUBJECT, undefined, false, true).then(change => {
-      const message = change ?
-        CREATE_CHANGE_SUCCEEDED_MESSAGE :
-        CREATE_CHANGE_FAILED_MESSAGE;
-      this.dispatchEvent(new CustomEvent('show-alert',
-          {detail: {message}, bubbles: true, composed: true}));
-      if (!change) { return; }
-
-      GerritNav.navigateToRelativeUrl(GerritNav.getEditUrlForDiff(
-          change, CONFIG_PATH, INITIAL_PATCHSET));
-    })
-        .finally(() => {
-          this._editingConfig = false;
-        });
-  }
-}
-
-customElements.define(GrRepoCommands.is, GrRepoCommands);
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
new file mode 100644
index 0000000..a74f4bb
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
@@ -0,0 +1,218 @@
+/**
+ * @license
+ * Copyright (C) 2017 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 '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
+import '../../../styles/gr-form-styles';
+import '../../../styles/gr-subpage-styles';
+import '../../../styles/shared-styles';
+import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
+import '../../plugins/gr-endpoint-param/gr-endpoint-param';
+import '../../shared/gr-dialog/gr-dialog';
+import '../../shared/gr-overlay/gr-overlay';
+import '../../shared/gr-rest-api-interface/gr-rest-api-interface';
+import '../gr-create-change-dialog/gr-create-change-dialog';
+import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
+import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
+import {PolymerElement} from '@polymer/polymer/polymer-element';
+import {htmlTemplate} from './gr-repo-commands_html';
+import {GerritNav} from '../../core/gr-navigation/gr-navigation';
+import {customElement, property} from '@polymer/decorators';
+import {
+  ErrorCallback,
+  RestApiService,
+} from '../../../services/services/gr-rest-api/gr-rest-api';
+import {
+  BranchName,
+  ConfigInfo,
+  PatchSetNum,
+  RepoName,
+} from '../../../types/common';
+import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
+import {GrCreateChangeDialog} from '../gr-create-change-dialog/gr-create-change-dialog';
+
+const GC_MESSAGE = 'Garbage collection completed successfully.';
+const CONFIG_BRANCH = 'refs/meta/config' as BranchName;
+const CONFIG_PATH = 'project.config';
+const EDIT_CONFIG_SUBJECT = 'Edit Repo Config';
+const INITIAL_PATCHSET = 1 as PatchSetNum;
+const CREATE_CHANGE_FAILED_MESSAGE = 'Failed to create change.';
+const CREATE_CHANGE_SUCCEEDED_MESSAGE = 'Navigating to change';
+
+export interface GrRepoCommands {
+  $: {
+    restAPI: RestApiService & Element;
+    createChangeOverlay: GrOverlay;
+    createNewChangeModal: GrCreateChangeDialog;
+  };
+}
+
+@customElement('gr-repo-commands')
+export class GrRepoCommands extends GestureEventListeners(
+  LegacyElementMixin(PolymerElement)
+) {
+  static get template() {
+    return htmlTemplate;
+  }
+
+  // This is a required property. Without `repo` being set the component is not
+  // useful. Thus using !.
+  @property({type: String})
+  repo!: RepoName;
+
+  @property({type: Boolean})
+  _loading = true;
+
+  @property({type: Object})
+  _repoConfig?: ConfigInfo;
+
+  @property({type: Boolean})
+  _canCreate = false;
+
+  @property({type: Boolean})
+  _creatingChange = false;
+
+  @property({type: Boolean})
+  _editingConfig = false;
+
+  @property({type: Boolean})
+  _runningGC = false;
+
+  /** @override */
+  attached() {
+    super.attached();
+    this._loadRepo();
+
+    this.dispatchEvent(
+      new CustomEvent('title-change', {
+        detail: {title: 'Repo Commands'},
+        composed: true,
+        bubbles: true,
+      })
+    );
+  }
+
+  _loadRepo() {
+    const errFn: ErrorCallback = response => {
+      // Do not process the error, if the component is not attached to the DOM
+      // anymore, which at least in tests can happen.
+      if (!this.isConnected) return;
+      this.dispatchEvent(
+        new CustomEvent('page-error', {
+          detail: {response},
+          composed: true,
+          bubbles: true,
+        })
+      );
+    };
+
+    this.$.restAPI.getProjectConfig(this.repo, errFn).then(config => {
+      if (!config) return;
+      // Do not process the response, if the component is not attached to the
+      // DOM anymore, which at least in tests can happen.
+      if (!this.isConnected) return;
+      this._repoConfig = config;
+      this._loading = false;
+    });
+  }
+
+  _computeLoadingClass(loading: boolean) {
+    return loading ? 'loading' : '';
+  }
+
+  _isLoading() {
+    return this._loading;
+  }
+
+  _handleRunningGC() {
+    this._runningGC = true;
+    return this.$.restAPI
+      .runRepoGC(this.repo)
+      .then(response => {
+        if (response?.status === 200) {
+          this.dispatchEvent(
+            new CustomEvent('show-alert', {
+              detail: {message: GC_MESSAGE},
+              bubbles: true,
+              composed: true,
+            })
+          );
+        }
+      })
+      .finally(() => {
+        this._runningGC = false;
+      });
+  }
+
+  _createNewChange() {
+    this.$.createChangeOverlay.open();
+  }
+
+  _handleCreateChange() {
+    this._creatingChange = true;
+    this.$.createNewChangeModal.handleCreateChange().finally(() => {
+      this._creatingChange = false;
+    });
+    this._handleCloseCreateChange();
+  }
+
+  _handleCloseCreateChange() {
+    this.$.createChangeOverlay.close();
+  }
+
+  /**
+   * Returns a Promise for testing.
+   */
+  _handleEditRepoConfig() {
+    this._editingConfig = true;
+    return this.$.restAPI
+      .createChange(
+        this.repo,
+        CONFIG_BRANCH,
+        EDIT_CONFIG_SUBJECT,
+        undefined,
+        false,
+        true
+      )
+      .then(change => {
+        const message = change
+          ? CREATE_CHANGE_SUCCEEDED_MESSAGE
+          : CREATE_CHANGE_FAILED_MESSAGE;
+        this.dispatchEvent(
+          new CustomEvent('show-alert', {
+            detail: {message},
+            bubbles: true,
+            composed: true,
+          })
+        );
+        if (!change) {
+          return;
+        }
+
+        GerritNav.navigateToRelativeUrl(
+          GerritNav.getEditUrlForDiff(change, CONFIG_PATH, INITIAL_PATCHSET)
+        );
+      })
+      .finally(() => {
+        this._editingConfig = false;
+      });
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'gr-repo-commands': GrRepoCommands;
+  }
+}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.js b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.js
index 0bb0c55..56e9507 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.js
@@ -28,10 +28,11 @@
 
   setup(() => {
     element = basicFixture.instantiate();
-    repoStub = sinon.stub(
-        element.$.restAPI,
-        'getProjectConfig')
-        .callsFake(() => Promise.resolve({}));
+    // Note that this probably does not achieve what it is supposed to, because
+    // getProjectConfig() is called as soon as the element is attached, so
+    // stubbing it here has not effect anymore.
+    repoStub = sinon.stub(element.$.restAPI, 'getProjectConfig')
+        .returns(Promise.resolve({}));
   });
 
   suite('create new change dialog', () => {
@@ -72,6 +73,7 @@
       sinon.stub(GerritNav, 'navigateToRelativeUrl');
       handleSpy = sinon.spy(element, '_handleEditRepoConfig');
       alertStub = sinon.stub();
+      element.repo = 'test';
       element.addEventListener('show-alert', alertStub);
     });
 
diff --git a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts
index d172f2a..dc9040c 100644
--- a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts
+++ b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts
@@ -645,4 +645,9 @@
     groupName: GroupId,
     includedGroup: GroupId
   ): Promise<Response>;
+
+  runRepoGC(
+    repo: RepoName,
+    errFn?: ErrorCallback
+  ): Promise<Response | undefined>;
 }