Merge "PolyGerrit: Add support for "Included In""
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..8073983 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,10 @@
       #messageList.visible {
         display: block;
       }
+      #includedInOverlay {
+        padding: 2em;
+        width: 65em;
+      }
       @media screen and (min-width: 80em) {
         .commitMessage {
           max-width: var(--commit-message-max-width, 100ch);
@@ -513,6 +518,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 +588,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 cb2b8bc..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
@@ -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-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..a017635
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.html
@@ -0,0 +1,84 @@
+<!--
+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;
+        padding: 1em;
+      }
+      h1 {
+        font-size: 1.2rem;
+      }
+      h2 {
+        font-size: 1rem;
+      }
+      .closeButtonContainer {
+        position: absolute;
+        right: 2em;
+        top: 2em;
+      }
+      .container {
+        max-height: 80vh;
+        overflow-y: scroll;
+      }
+      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>Included In:</h1>
+      <span class="closeButtonContainer">
+        <gr-button id="closeButton"
+            link
+            on-tap="_handleCloseTap">Close</gr-button>
+      </span>
+    </header>
+    <div class$="[[_computeLoadingClass(_loaded)]]">Loading...</div>
+    <div class="container">
+      <template
+          is="dom-repeat"
+          items="[[_computeGroups(_includedIn)]]"
+          as="group">
+        <div>
+          <h2>[[group.title]]:</h2>
+          <ul>
+            <template is="dom-repeat" items="[[group.items]]">
+              <li>[[item]]</li>
+            </template>
+          </ul>
+        </div>
+      </template>
+    </div>
+    <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..1079eec
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.js
@@ -0,0 +1,82 @@
+// 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,
+      },
+    },
+
+    loadData() {
+      if (!this.changeNum) { return; }
+      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) {
+      if (!includedIn) { return []; }
+
+      const groups = [
+        {title: 'Branches', items: includedIn.branches},
+        {title: 'Tags', items: includedIn.tags},
+      ];
+      if (includedIn.external) {
+        for (const externalKey of Object.keys(includedIn.external)) {
+          groups.push({
+            title: externalKey,
+            items: includedIn.external[externalKey],
+          });
+        }
+      }
+      return groups;
+    },
+
+    _handleCloseTap(e) {
+      e.preventDefault();
+      this.fire('close', null, {bubbles: false});
+    },
+
+    _computeLoadingClass(loaded) {
+      return loaded ? 'loading loaded' : 'loading';
+    },
+  });
+})();
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..16aa7b2
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
@@ -0,0 +1,74 @@
+<!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: []};
+      assert.deepEqual(element._computeGroups(includedIn), [
+        {title: 'Branches', items: []},
+        {title: 'Tags', items: []},
+      ]);
+
+      includedIn.branches.push('master', 'development', 'stable-2.0');
+      includedIn.tags.push('v1.9', 'v2.0', 'v2.1');
+      assert.deepEqual(element._computeGroups(includedIn), [
+        {title: 'Branches', items: ['master', 'development', 'stable-2.0']},
+        {title: 'Tags', items: ['v1.9', 'v2.0', 'v2.1']},
+      ]);
+
+      includedIn.external = {};
+      assert.deepEqual(element._computeGroups(includedIn), [
+        {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), [
+        {title: 'Branches', items: ['master', 'development', 'stable-2.0']},
+        {title: 'Tags', items: ['v1.9', 'v2.0', 'v2.1']},
+        {title: 'foo', items: ['abc', 'def', 'ghi']},
+      ]);
+    });
+  });
+</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 27f330e..5666d3f 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
@@ -1105,6 +1105,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',