Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/static_src/elements/issue-detail/metadata/mr-metadata/mr-issue-metadata.js b/static_src/elements/issue-detail/metadata/mr-metadata/mr-issue-metadata.js
new file mode 100644
index 0000000..60d570c
--- /dev/null
+++ b/static_src/elements/issue-detail/metadata/mr-metadata/mr-issue-metadata.js
@@ -0,0 +1,352 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {LitElement, html, css} from 'lit-element';
+
+import {connectStore} from 'reducers/base.js';
+import * as issueV0 from 'reducers/issueV0.js';
+import * as projectV0 from 'reducers/projectV0.js';
+import * as userV0 from 'reducers/userV0.js';
+import 'elements/framework/mr-star/mr-issue-star.js';
+import 'elements/framework/links/mr-user-link/mr-user-link.js';
+import 'elements/framework/links/mr-hotlist-link/mr-hotlist-link.js';
+import {SHARED_STYLES} from 'shared/shared-styles.js';
+import {pluralize} from 'shared/helpers.js';
+import './mr-metadata.js';
+
+
+/**
+ * `<mr-issue-metadata>`
+ *
+ * The metadata view for a single issue. Contains information such as the owner.
+ *
+ */
+export class MrIssueMetadata extends connectStore(LitElement) {
+  /** @override */
+  static get styles() {
+    return [
+      SHARED_STYLES,
+      css`
+        :host {
+          box-sizing: border-box;
+          padding: 0.25em 8px;
+          max-width: 100%;
+          display: block;
+        }
+        h3 {
+          display: block;
+          font-size: var(--chops-main-font-size);
+          margin: 0;
+          line-height: 160%;
+          width: 40%;
+          height: 100%;
+          overflow: ellipsis;
+          flex-grow: 0;
+          flex-shrink: 0;
+        }
+        a.label {
+          color: hsl(120, 100%, 25%);
+          text-decoration: none;
+        }
+        a.label[data-derived] {
+          font-style: italic;
+        }
+        button.linkify {
+          display: flex;
+          align-items: center;
+          text-decoration: none;
+          padding: 0.25em 0;
+        }
+        button.linkify i.material-icons {
+          margin-right: 4px;
+          font-size: var(--chops-icon-font-size);
+        }
+        mr-hotlist-link {
+          text-overflow: ellipsis;
+          overflow: hidden;
+          display: block;
+          width: 100%;
+        }
+        .bottom-section-cell, .labels-container {
+          padding: 0.5em 4px;
+          width: 100%;
+          box-sizing: border-box;
+        }
+        .bottom-section-cell {
+          display: flex;
+          flex-direction: row;
+          flex-wrap: nowrap;
+          align-items: flex-start;
+        }
+        .bottom-section-content {
+          max-width: 60%;
+        }
+        .star-line {
+          width: 100%;
+          text-align: center;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+        }
+        mr-issue-star {
+          margin-right: 4px;
+          padding-bottom: 2px;
+        }
+      `,
+    ];
+  }
+
+  /** @override */
+  render() {
+    const hotlistsByRole = this._hotlistsByRole;
+    return html`
+      <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
+      <div class="star-line">
+        <mr-issue-star
+          .issueRef=${this.issueRef}
+        ></mr-issue-star>
+        Starred by ${this.issue.starCount || 0} ${pluralize(this.issue.starCount, 'user')}
+      </div>
+      <mr-metadata
+        aria-label="Issue Metadata"
+        .owner=${this.issue.ownerRef}
+        .cc=${this.issue.ccRefs}
+        .issueStatus=${this.issue.statusRef}
+        .components=${this._components}
+        .fieldDefs=${this._fieldDefs}
+        .mergedInto=${this.mergedInto}
+        .modifiedTimestamp=${this.issue.modifiedTimestamp}
+      ></mr-metadata>
+
+      <div class="labels-container">
+        ${this.issue.labelRefs && this.issue.labelRefs.map((label) => html`
+          <a
+            title="${_labelTitle(this.labelDefMap, label)}"
+            href="/p/${this.issueRef.projectName}/issues/list?q=label:${label.label}"
+            class="label"
+            ?data-derived=${label.isDerived}
+          >${label.label}</a>
+          <br>
+        `)}
+      </div>
+
+      ${this.sortedBlockedOn.length ? html`
+        <div class="bottom-section-cell">
+          <h3>BlockedOn:</h3>
+            <div class="bottom-section-content">
+            ${this.sortedBlockedOn.map((issue) => html`
+              <mr-issue-link
+                .projectName=${this.issueRef.projectName}
+                .issue=${issue}
+              >
+              </mr-issue-link>
+              <br />
+            `)}
+            <button
+              class="linkify"
+              @click=${this.openViewBlockedOn}
+            >
+              <i class="material-icons" role="presentation">list</i>
+              View details
+            </button>
+          </div>
+        </div>
+      `: ''}
+
+      ${this.blocking.length ? html`
+        <div class="bottom-section-cell">
+          <h3>Blocking:</h3>
+          <div class="bottom-section-content">
+            ${this.blocking.map((issue) => html`
+              <mr-issue-link
+                .projectName=${this.issueRef.projectName}
+                .issue=${issue}
+              >
+              </mr-issue-link>
+              <br />
+            `)}
+          </div>
+        </div>
+      `: ''}
+
+      ${this._userId ? html`
+        <div class="bottom-section-cell">
+          <h3>Your Hotlists:</h3>
+          <div class="bottom-section-content" id="user-hotlists">
+            ${this._renderHotlists(hotlistsByRole.user)}
+            <button
+              class="linkify"
+              @click=${this.openUpdateHotlists}
+            >
+              <i class="material-icons" role="presentation">create</i> Update your hotlists
+            </button>
+          </div>
+        </div>
+      `: ''}
+
+      ${hotlistsByRole.participants.length ? html`
+        <div class="bottom-section-cell">
+          <h3>Participant's Hotlists:</h3>
+          <div class="bottom-section-content">
+            ${this._renderHotlists(hotlistsByRole.participants)}
+          </div>
+        </div>
+      ` : ''}
+
+      ${hotlistsByRole.others.length ? html`
+        <div class="bottom-section-cell">
+          <h3>Other Hotlists:</h3>
+          <div class="bottom-section-content">
+            ${this._renderHotlists(hotlistsByRole.others)}
+          </div>
+        </div>
+      ` : ''}
+    `;
+  }
+
+  /**
+   * Helper to render hotlists.
+   * @param {Array<Hotlist>} hotlists
+   * @return {Array<TemplateResult>}
+   * @private
+   */
+  _renderHotlists(hotlists) {
+    return hotlists.map((hotlist) => html`
+      <mr-hotlist-link .hotlist=${hotlist}></mr-hotlist-link>
+    `);
+  }
+
+  /** @override */
+  static get properties() {
+    return {
+      issue: {type: Object},
+      issueRef: {type: Object},
+      projectConfig: String,
+      user: {type: Object},
+      issueHotlists: {type: Array},
+      blocking: {type: Array},
+      sortedBlockedOn: {type: Array},
+      relatedIssues: {type: Object},
+      labelDefMap: {type: Object},
+      _components: {type: Array},
+      _fieldDefs: {type: Array},
+      _type: {type: String},
+    };
+  }
+
+  /** @override */
+  stateChanged(state) {
+    this.issue = issueV0.viewedIssue(state);
+    this.issueRef = issueV0.viewedIssueRef(state);
+    this.user = userV0.currentUser(state);
+    this.projectConfig = projectV0.viewedConfig(state);
+    this.blocking = issueV0.blockingIssues(state);
+    this.sortedBlockedOn = issueV0.sortedBlockedOn(state);
+    this.mergedInto = issueV0.mergedInto(state);
+    this.relatedIssues = issueV0.relatedIssues(state);
+    this.issueHotlists = issueV0.hotlists(state);
+    this.labelDefMap = projectV0.labelDefMap(state);
+    this._components = issueV0.components(state);
+    this._fieldDefs = issueV0.fieldDefs(state);
+    this._type = issueV0.type(state);
+  }
+
+  /**
+   * @return {string|number} The current user's userId.
+   * @private
+   */
+  get _userId() {
+    return this.user && this.user.userId;
+  }
+
+  /**
+   * @return {Object<string, Array<Hotlist>>}
+   * @private
+   */
+  get _hotlistsByRole() {
+    const issueHotlists = this.issueHotlists;
+    const owner = this.issue && this.issue.ownerRef;
+    const cc = this.issue && this.issue.ccRefs;
+
+    const hotlists = {
+      user: [],
+      participants: [],
+      others: [],
+    };
+    (issueHotlists || []).forEach((hotlist) => {
+      if (hotlist.ownerRef.userId === this._userId) {
+        hotlists.user.push(hotlist);
+      } else if (_userIsParticipant(hotlist.ownerRef, owner, cc)) {
+        hotlists.participants.push(hotlist);
+      } else {
+        hotlists.others.push(hotlist);
+      }
+    });
+    return hotlists;
+  }
+
+  /**
+   * Opens dialog for updating ths issue's hotlists.
+   * @fires CustomEvent#open-dialog
+   */
+  openUpdateHotlists() {
+    this.dispatchEvent(new CustomEvent('open-dialog', {
+      bubbles: true,
+      composed: true,
+      detail: {
+        dialogId: 'update-issue-hotlists',
+      },
+    }));
+  }
+
+  /**
+   * Opens dialog with detailed view of blocked on issues.
+   * @fires CustomEvent#open-dialog
+   */
+  openViewBlockedOn() {
+    this.dispatchEvent(new CustomEvent('open-dialog', {
+      bubbles: true,
+      composed: true,
+      detail: {
+        dialogId: 'reorder-related-issues',
+      },
+    }));
+  }
+}
+
+/**
+ * @param {UserRef} user
+ * @param {UserRef} owner
+ * @param {Array<UserRef>} cc
+ * @return {boolean} Whether a given user is a participant of
+ *   a given hotlist attached to an issue. Used to sort hotlists into
+ *   "My hotlists" and "Other hotlists".
+ * @private
+ */
+function _userIsParticipant(user, owner, cc) {
+  if (owner && owner.userId === user.userId) {
+    return true;
+  }
+  return cc && cc.some((ccUser) => ccUser && ccUser.userId === user.userId);
+}
+
+/**
+ * @param {Map.<string, LabelDef>} labelDefMap
+ * @param {LabelDef} label
+ * @return {string} Tooltip shown to the user when hovering over a
+ *   given label.
+ * @private
+ */
+function _labelTitle(labelDefMap, label) {
+  if (!label) return '';
+  let docstring = '';
+  const key = label.label.toLowerCase();
+  if (labelDefMap && labelDefMap.has(key)) {
+    docstring = labelDefMap.get(key).docstring;
+  }
+  return (label.isDerived ? 'Derived: ' : '') + label.label +
+    (docstring ? ` = ${docstring}` : '');
+}
+
+customElements.define('mr-issue-metadata', MrIssueMetadata);