Merge "Fix javascript errors in gr-admin-view components"
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 8d5f759..bfa174b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -44,6 +44,7 @@
 <link rel="import" href="../gr-download-dialog/gr-download-dialog.html">
 <link rel="import" href="../gr-file-list-header/gr-file-list-header.html">
 <link rel="import" href="../gr-file-list/gr-file-list.html">
+<link rel="import" href="../gr-included-in-dialog/gr-included-in-dialog.html">
 <link rel="import" href="../gr-messages-list/gr-messages-list.html">
 <link rel="import" href="../gr-related-changes-list/gr-related-changes-list.html">
 <link rel="import" href="../gr-reply-dialog/gr-reply-dialog.html">
@@ -256,6 +257,9 @@
       #messageList.visible {
         display: block;
       }
+      #includedInOverlay {
+        width: 65em;
+      }
       @media screen and (min-width: 80em) {
         .commitMessage {
           max-width: var(--commit-message-max-width, 100ch);
@@ -513,6 +517,7 @@
             files-expanded="[[_filesExpanded]]"
             on-open-diff-prefs="_handleOpenDiffPrefs"
             on-open-download-dialog="_handleOpenDownloadDialog"
+            on-open-included-in-dialog="_handleOpenIncludedInDialog"
             on-expand-diffs="_expandAllDiffs"
             on-collapse-diffs="_collapseAllDiffs">
         </gr-file-list-header>
@@ -582,6 +587,12 @@
           config="[[_serverConfig.download]]"
           on-close="_handleDownloadDialogClose"></gr-download-dialog>
     </gr-overlay>
+    <gr-overlay id="includedInOverlay" with-backdrop>
+      <gr-included-in-dialog
+          id="includedInDialog"
+          change-num="[[_changeNum]]"
+          on-close="_handleIncludedInDialogClose"></gr-included-in-dialog>
+    </gr-overlay>
     <gr-overlay id="replyOverlay"
         class="scrollable"
         no-cancel-on-outside-click
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 20ae258..e552dd1 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -526,6 +526,18 @@
       this.$.fileList.openDiffPrefs();
     },
 
+    _handleOpenIncludedInDialog() {
+      this.$.includedInDialog.loadData().then(() => {
+        Polymer.dom.flush();
+        this.$.includedInOverlay.refit();
+      });
+      this.$.includedInOverlay.open();
+    },
+
+    _handleIncludedInDialogClose(e) {
+      this.$.includedInOverlay.close();
+    },
+
     _handleOpenDownloadDialog() {
       this.$.downloadOverlay.open().then(() => {
         this.$.downloadOverlay
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
index d02a189..54f271a 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
@@ -80,6 +80,12 @@
       .downloadContainer {
         margin-right: 16px;
       }
+      .includedInContainer {
+        margin-right: 16px;
+      }
+      .includedInContainer.hide {
+        display: none;
+      }
       .rightControls {
         align-self: flex-end;
         margin: auto 0 auto auto;
@@ -209,6 +215,11 @@
               class="download"
               on-tap="_handleDownloadTap">Download</gr-button>
         </span>
+        <span class$="includedInContainer [[_hideIncludedIn(change)]] desktop">
+          <gr-button link
+              class="includedIn"
+              on-tap="_handleIncludedInTap">Included In</gr-button>
+        </span>
         <template is="dom-if"
             if="[[_fileListActionsVisible(shownFileCount, _maxFilesForBulkActions)]]">
           <gr-button
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js
index 84add8a..93f010a 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js
@@ -149,7 +149,7 @@
           .then(res => {
             if (res.ok) {
               if (target) { target.disabled = false; }
-              this.set(['_change', 'revisions', sha, 'description'], desc);
+              this.set(['change', 'revisions', sha, 'description'], desc);
               this._patchsetDescription = desc;
             }
           }).catch(err => {
@@ -179,6 +179,11 @@
       this.fire('open-diff-prefs');
     },
 
+    _handleIncludedInTap(e) {
+      e.preventDefault();
+      this.fire('open-included-in-dialog');
+    },
+
     _handleDownloadTap(e) {
       e.preventDefault();
       this.fire('open-download-dialog');
@@ -199,5 +204,9 @@
     _getRevisionInfo(change) {
       return new Gerrit.RevisionInfo(change);
     },
+
+    _hideIncludedIn(change) {
+      return change && change.status === 'MERGED' ? '' : 'hide';
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
index a92d783..f7056dc 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
@@ -134,6 +134,7 @@
         // The API stub should be called with an empty string for the new
         // description.
         assert.equal(putDescStub.lastCall.args[2], '');
+        assert.equal(element.change.revisions.rev1.description, '');
 
         flushAsynchronousOperations();
         // The editable label should now be visible and the chip hidden.
@@ -154,6 +155,7 @@
       }).then(() => {
         flushAsynchronousOperations();
         // The chip should be visible again, and the label hidden.
+        assert.equal(element.change.revisions.rev1.description, 'test2');
         assert.equal(getComputedStyle(label).display, 'none');
         assert.notEqual(getComputedStyle(chip).display, 'none');
       });
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.html b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.html
new file mode 100644
index 0000000..c3d5808
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.html
@@ -0,0 +1,100 @@
+<!--
+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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+
+<dom-module id="gr-included-in-dialog">
+  <template>
+    <style include="shared-styles">
+      :host {
+        display: block;
+        max-height: 80vh;
+        overflow-y: auto;
+        padding: 4.5em 1em 1em 1em;
+      }
+      header {
+        background: #fff;
+        border-bottom: 1px solid #cdcdcd;
+        left: 0;
+        padding: 1em;
+        position: absolute;
+        right: 0;
+        top: 0;
+      }
+      #title {
+        display: inline-block;
+        font-size: 1.2rem;
+        margin-top: .2em;
+      }
+      h2 {
+        font-size: 1rem;
+      }
+      #filterInput {
+        display: inline-block;
+        float: right;
+        margin: 0 1em;
+        padding: .2em;
+      }
+      .closeButtonContainer {
+        float: right;
+      }
+      ul {
+        margin-bottom: 1em;
+      }
+      ul li {
+        border-radius: .2em;
+        background: #eee;
+        display: inline-block;
+        margin: 0 .2em .4em .2em;
+        padding: .2em .4em;
+      }
+      .loading.loaded {
+        display: none;
+      }
+    </style>
+    <header>
+      <h1 id="title">Included In:</h1>
+      <span class="closeButtonContainer">
+        <gr-button id="closeButton"
+            link
+            on-tap="_handleCloseTap">Close</gr-button>
+      </span>
+      <input
+          id="filterInput"
+          is="iron-input"
+          placeholder="Filter"
+          on-bind-value-changed="_onFilterChanged">
+    </header>
+    <div class$="[[_computeLoadingClass(_loaded)]]">Loading...</div>
+    <template
+        is="dom-repeat"
+        items="[[_computeGroups(_includedIn, _filterText)]]"
+        as="group">
+      <div>
+        <h2>[[group.title]]:</h2>
+        <ul>
+          <template is="dom-repeat" items="[[group.items]]">
+            <li>[[item]]</li>
+          </template>
+        </ul>
+      </div>
+    </template>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+  </template>
+  <script src="gr-included-in-dialog.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.js b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.js
new file mode 100644
index 0000000..93e644e
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.js
@@ -0,0 +1,96 @@
+// 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.
+(function() {
+  'use strict';
+
+  Polymer({
+    is: 'gr-included-in-dialog',
+
+    /**
+     * Fired when the user presses the close button.
+     *
+     * @event close
+     */
+
+    properties: {
+      /** @type {?} */
+      changeNum: {
+        type: Object,
+        observer: '_resetData',
+      },
+      /** @type {?} */
+      _includedIn: Object,
+      _loaded: {
+        type: Boolean,
+        value: false,
+      },
+      _filterText: {
+        type: String,
+        value: '',
+      },
+    },
+
+    loadData() {
+      if (!this.changeNum) { return; }
+      this._filterText = '';
+      return this.$.restAPI.getChangeIncludedIn(this.changeNum).then(
+          configs => {
+            if (!configs) { return; }
+            this._includedIn = configs;
+            this._loaded = true;
+          });
+    },
+
+    _resetData() {
+      this._includedIn = null;
+      this._loaded = false;
+    },
+
+    _computeGroups(includedIn, filterText) {
+      if (!includedIn) { return []; }
+
+      const filter = item => !filterText.length ||
+          item.toLowerCase().indexOf(filterText.toLowerCase()) !== -1;
+
+      const groups = [
+        {title: 'Branches', items: includedIn.branches.filter(filter)},
+        {title: 'Tags', items: includedIn.tags.filter(filter)},
+      ];
+      if (includedIn.external) {
+        for (const externalKey of Object.keys(includedIn.external)) {
+          groups.push({
+            title: externalKey,
+            items: includedIn.external[externalKey].filter(filter),
+          });
+        }
+      }
+      return groups.filter(g => g.items.length);
+    },
+
+    _handleCloseTap(e) {
+      e.preventDefault();
+      this.fire('close', null, {bubbles: false});
+    },
+
+    _computeLoadingClass(loaded) {
+      return loaded ? 'loading loaded' : 'loading';
+    },
+
+    _onFilterChanged() {
+      this.debounce('filter-change', () => {
+        this._filterText = this.$.filterInput.bindValue;
+      }, 100);
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
new file mode 100644
index 0000000..1f7af38
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-included-in-dialog</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-included-in-dialog.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-included-in-dialog></gr-included-in-dialog>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-included-in-dialog', () => {
+    let element;
+    let sandbox;
+
+    setup(() => {
+      sandbox = sinon.sandbox.create();
+      element = fixture('basic');
+    });
+
+    teardown(() => { sandbox.restore(); });
+
+    test('_computeGroups', () => {
+      const includedIn = {branches: [], tags: []};
+      let filterText = '';
+      assert.deepEqual(element._computeGroups(includedIn, filterText), []);
+
+      includedIn.branches.push('master', 'development', 'stable-2.0');
+      includedIn.tags.push('v1.9', 'v2.0', 'v2.1');
+      assert.deepEqual(element._computeGroups(includedIn, filterText), [
+        {title: 'Branches', items: ['master', 'development', 'stable-2.0']},
+        {title: 'Tags', items: ['v1.9', 'v2.0', 'v2.1']},
+      ]);
+
+      includedIn.external = {};
+      assert.deepEqual(element._computeGroups(includedIn, filterText), [
+        {title: 'Branches', items: ['master', 'development', 'stable-2.0']},
+        {title: 'Tags', items: ['v1.9', 'v2.0', 'v2.1']},
+      ]);
+
+      includedIn.external.foo = ['abc', 'def', 'ghi'];
+      assert.deepEqual(element._computeGroups(includedIn, filterText), [
+        {title: 'Branches', items: ['master', 'development', 'stable-2.0']},
+        {title: 'Tags', items: ['v1.9', 'v2.0', 'v2.1']},
+        {title: 'foo', items: ['abc', 'def', 'ghi']},
+      ]);
+
+      filterText = 'v2';
+      assert.deepEqual(element._computeGroups(includedIn, filterText), [
+        {title: 'Tags', items: ['v2.0', 'v2.1']},
+      ]);
+
+      // Filtering is case-insensitive.
+      filterText = 'V2';
+      assert.deepEqual(element._computeGroups(includedIn, filterText), [
+        {title: 'Tags', items: ['v2.0', 'v2.1']},
+      ]);
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index daa2a4f..88faaba 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -1107,6 +1107,13 @@
           opt_errFn, null, params);
     },
 
+    /**
+     * @param {number|string} changeNum
+     */
+    getChangeIncludedIn(changeNum) {
+      return this._getChangeURLAndFetch(changeNum, '/in', null);
+    },
+
     _computeFilter(filter) {
       if (filter && filter.startsWith('^')) {
         filter = '&r=' + encodeURIComponent(filter);
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 2bb8b2e..f0500ee 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -73,6 +73,7 @@
     'change/gr-download-dialog/gr-download-dialog_test.html',
     'change/gr-file-list-header/gr-file-list-header_test.html',
     'change/gr-file-list/gr-file-list_test.html',
+    'change/gr-included-in-dialog/gr-included-in-dialog_test.html',
     'change/gr-label-score-row/gr-label-score-row_test.html',
     'change/gr-label-scores/gr-label-scores_test.html',
     'change/gr-message/gr-message_test.html',