Merge "Expose the TokenHighlightLayer to embedders"
diff --git a/polygerrit-ui/app/api/checks.ts b/polygerrit-ui/app/api/checks.ts
index fdb785c..658a97e 100644
--- a/polygerrit-ui/app/api/checks.ts
+++ b/polygerrit-ui/app/api/checks.ts
@@ -82,6 +82,13 @@
    * Will be shown as buttons in the header of the Checks tab.
    */
   actions?: Action[];
+
+  /**
+   * Top-level links that are not associated with a specific run or result.
+   * Will be shown as icons in the header of the Checks tab.
+   */
+  links?: Link[];
+
   runs?: CheckRun[];
 }
 
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group_html.ts b/polygerrit-ui/app/elements/admin/gr-group/gr-group_html.ts
index ba089f6..98d21f9 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group_html.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group_html.ts
@@ -25,9 +25,6 @@
       color: var(--deemphasized-text-color);
       content: ' *';
     }
-    .inputUpdateBtn {
-      margin-top: var(--spacing-s);
-    }
   </style>
   <style include="gr-form-styles">
     /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
index 69f2954..3d55097 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
@@ -140,7 +140,7 @@
   change?: ParsedChangeInfo;
 
   @property({type: Object})
-  revertSubmittedChange?: ChangeInfo;
+  revertedChange?: ChangeInfo;
 
   @property({type: Object, notify: true})
   labels?: LabelNameToInfoMap;
@@ -584,9 +584,11 @@
 
   _getRevertSectionTitle(
     _change?: ParsedChangeInfo,
-    revertSubmittedChange?: ChangeInfo
+    revertedChange?: ChangeInfo
   ) {
-    return revertSubmittedChange ? 'Revert Submitted As' : 'Revert Created As';
+    return revertedChange?.status === ChangeStatus.MERGED
+      ? 'Revert Submitted As'
+      : 'Revert Created As';
   }
 
   _showRevertCreatedAs(change?: ParsedChangeInfo) {
@@ -594,18 +596,12 @@
     return getRevertCreatedChangeIds(change.messages).length > 0;
   }
 
-  _computeRevertCommit(
-    change?: ParsedChangeInfo,
-    revertSubmittedChange?: ChangeInfo
-  ) {
-    if (
-      revertSubmittedChange?.current_revision &&
-      revertSubmittedChange?.revisions
-    ) {
+  _computeRevertCommit(change?: ParsedChangeInfo, revertedChange?: ChangeInfo) {
+    if (revertedChange?.current_revision && revertedChange?.revisions) {
       return {
         commit: this._computeMergedCommitInfo(
-          revertSubmittedChange.current_revision,
-          revertSubmittedChange.revisions
+          revertedChange.current_revision,
+          revertedChange.revisions
         ),
       };
     }
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
index 50bb5ac..9f0c780 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
@@ -356,12 +356,12 @@
         class$="[[_computeDisplayState(_showAllSections, change, _SECTION.REVERT_CREATED_AS)]]"
       >
         <span class="title"
-          >[[_getRevertSectionTitle(change, revertSubmittedChange)]]</span
+          >[[_getRevertSectionTitle(change, revertedChange)]]</span
         >
         <span class="value">
           <gr-commit-info
             change="[[change]]"
-            commit-info="[[_computeRevertCommit(change, revertSubmittedChange)]]"
+            commit-info="[[_computeRevertCommit(change, revertedChange)]]"
             server-config="[[serverConfig]]"
           ></gr-commit-info>
         </span>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index 2db74dbb..f56209b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -515,7 +515,7 @@
   _tabState?: TabState;
 
   @property({type: Object})
-  revertSubmittedChange?: ChangeInfo;
+  revertedChange?: ChangeInfo;
 
   restApiService = appContext.restApiService;
 
@@ -1864,14 +1864,15 @@
         change => change?.status !== ChangeStatus.ABANDONED
       );
       if (!changes.length) return;
-      const change = changes.find(
+      const submittedRevert = changes.find(
         change => change?.status === ChangeStatus.MERGED
       );
       if (!this._changeStatuses) return;
-      if (change) {
-        this.revertSubmittedChange = change;
+      if (submittedRevert) {
+        this.revertedChange = submittedRevert;
         this.push('_changeStatuses', ChangeStates.REVERT_SUBMITTED);
       } else {
+        if (changes[0]) this.revertedChange = changes[0];
         this.push('_changeStatuses', ChangeStates.REVERT_CREATED);
       }
     });
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index ac43c59..132833f 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -113,8 +113,7 @@
       --collapsed-max-height: 300px;
     }
     .changeStatuses,
-    .commitActions,
-    .statusText {
+    .commitActions {
       align-items: center;
       display: flex;
     }
@@ -324,7 +323,7 @@
             <template is="dom-repeat" items="[[_changeStatuses]]" as="status">
               <gr-change-status
                 change="[[_change]]"
-                revert-submitted-change="[[revertSubmittedChange]]"
+                reverted-change="[[revertedChange]]"
                 max-width="100"
                 status="[[status]]"
               ></gr-change-status>
@@ -386,7 +385,7 @@
           <gr-change-metadata
             id="metadata"
             change="{{_change}}"
-            revert-submitted-change="[[revertSubmittedChange]]"
+            reverted-change="[[revertedChange]]"
             account="[[_account]]"
             revision="[[_selectedRevision]]"
             commit-info="[[_commitInfo]]"
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
index 8ebb029..878caea 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
@@ -21,9 +21,6 @@
     .prefsButton {
       float: right;
     }
-    .collapseToggleButton {
-      text-decoration: none;
-    }
     .patchInfoOldPatchSet.patchInfo-header {
       background-color: var(--emphasis-color);
     }
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts
index 64179f7..da91095 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts
@@ -44,10 +44,6 @@
       border-top: 1px solid var(--border-color);
       margin-top: var(--spacing-xl);
     }
-    .resolved-comments-message {
-      color: var(--link-color);
-      cursor: pointer;
-    }
     .show-resolved-comments {
       box-shadow: none;
       padding-left: var(--spacing-m);
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 03d50cc..57bed1c 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -24,6 +24,7 @@
   property,
   PropertyValues,
   query,
+  TemplateResult,
 } from 'lit-element';
 import {GrLitElement} from '../lit/gr-lit-element';
 import '@polymer/paper-tooltip/paper-tooltip';
@@ -42,6 +43,7 @@
   someProvidersAreLoading$,
   RunResult,
   CheckRun,
+  allLinks$,
 } from '../../services/checks/checks-model';
 import {
   allResults,
@@ -457,6 +459,12 @@
   }
 }
 
+const SHOW_ALL_THRESHOLDS: Map<Category | 'SUCCESS', number> = new Map();
+SHOW_ALL_THRESHOLDS.set(Category.ERROR, 20);
+SHOW_ALL_THRESHOLDS.set(Category.WARNING, 20);
+SHOW_ALL_THRESHOLDS.set(Category.INFO, 5);
+SHOW_ALL_THRESHOLDS.set('SUCCESS', 5);
+
 @customElement('gr-checks-results')
 export class GrChecksResults extends GrLitElement {
   @query('#filterInput')
@@ -480,6 +488,9 @@
   actions: Action[] = [];
 
   @property()
+  links: Link[] = [];
+
+  @property()
   tabState?: ChecksTabState;
 
   @property()
@@ -498,6 +509,10 @@
     number | undefined
   >();
 
+  /** Maintains the state of which result sections should show all results. */
+  @internalProperty()
+  isShowAll: Map<Category | 'SUCCESS', boolean> = new Map();
+
   /**
    * This is the current state of whether a section is expanded or not. As long
    * as isSectionExpandedByUser is false this will be computed by a default rule
@@ -517,6 +532,7 @@
   constructor() {
     super();
     this.subscribe('actions', allActions$);
+    this.subscribe('links', allLinks$);
     this.subscribe('checksPatchsetNumber', checksPatchsetNumber$);
     this.subscribe('latestPatchsetNumber', latestPatchNum$);
     this.subscribe('someProvidersAreLoading', someProvidersAreLoading$);
@@ -555,6 +571,10 @@
           display: flex;
           align-items: center;
         }
+        .headerBottomRow .links iron-icon {
+          color: var(--link-color);
+          margin-right: var(--spacing-l);
+        }
         #moreActions iron-icon {
           color: var(--link-color);
         }
@@ -645,6 +665,12 @@
           font-weight: var(--font-weight-bold);
           padding: var(--spacing-s);
         }
+        gr-button.showAll {
+          margin: var(--spacing-m);
+        }
+        tr {
+          border-top: 1px solid var(--border-color);
+        }
       `,
     ];
   }
@@ -697,7 +723,7 @@
         </div>
         <div class="headerBottomRow">
           <div class="left">${this.renderFilter()}</div>
-          <div class="right">${this.renderActions()}</div>
+          <div class="right">${this.renderLinks()}${this.renderActions()}</div>
         </div>
       </div>
       <div class="body">
@@ -708,6 +734,24 @@
     `;
   }
 
+  private renderLinks() {
+    const links = (this.links ?? []).slice(0, 4);
+    if (links.length === 0) return;
+    return html`<div class="links">${links.map(this.renderLink)}</div>`;
+  }
+
+  private renderLink(link: Link) {
+    const tooltipText = link.tooltip ?? tooltipForLink(link.icon);
+    return html`<a href="${link.url}" target="_blank"
+      ><iron-icon
+        aria-label="${tooltipText}"
+        class="link"
+        icon="gr-icons:${iconForLink(link.icon)}"
+      ></iron-icon
+      ><paper-tooltip offset="5">${tooltipText}</paper-tooltip></a
+    >`;
+  }
+
   private renderActions() {
     const overflowItems = this.actions.slice(2).map(action => {
       return {...action, id: action.name};
@@ -843,6 +887,16 @@
     }
     const expandedClass = expanded ? 'expanded' : 'collapsed';
     const icon = expanded ? 'gr-icons:expand-less' : 'gr-icons:expand-more';
+    const isShowAll = this.isShowAll.get(category) ?? false;
+    const showAllThreshold = SHOW_ALL_THRESHOLDS.get(category) ?? 5;
+    const resultCount = filtered.length;
+    const resultLimit = isShowAll ? 1000 : showAllThreshold;
+    const showAllButton = this.renderShowAllButton(
+      category,
+      isShowAll,
+      showAllThreshold,
+      resultCount
+    );
     return html`
       <div class="${expandedClass}">
         <h3
@@ -859,15 +913,49 @@
             >${this.renderCount(all, selected, filtered)}</span
           >
         </h3>
-        ${this.renderResults(all, selected, filtered)}
+        ${this.renderResults(
+          all,
+          selected,
+          filtered,
+          resultLimit,
+          showAllButton
+        )}
       </div>
     `;
   }
 
+  renderShowAllButton(
+    category: Category | 'SUCCESS',
+    isShowAll: boolean,
+    showAllThreshold: number,
+    resultCount: number
+  ) {
+    if (resultCount <= showAllThreshold) return;
+    const message = isShowAll ? 'Show Less' : `Show All (${resultCount})`;
+    const handler = () => this.toggleShowAll(category);
+    return html`
+      <tr class="showAllRow">
+        <td colspan="4">
+          <gr-button class="showAll" link @click="${handler}"
+            >${message}</gr-button
+          >
+        </td>
+      </tr>
+    `;
+  }
+
+  toggleShowAll(category: Category | 'SUCCESS') {
+    const current = this.isShowAll.get(category) ?? false;
+    this.isShowAll.set(category, !current);
+    this.requestUpdate();
+  }
+
   renderResults(
     all: RunResult[],
     selected: RunResult[],
-    filtered: RunResult[]
+    filtered: RunResult[],
+    limit: number,
+    showAll: TemplateResult | undefined
   ) {
     if (all.length === 0) {
       return html`<div class="noResultsMessage">No results</div>`;
@@ -882,6 +970,7 @@
         No results match the regular expression
       </div>`;
     }
+    filtered = filtered.slice(0, limit);
     return html`
       <table class="resultsTable">
         <thead>
@@ -903,6 +992,7 @@
               ></gr-result-row>
             `
           )}
+          ${showAll}
         </tbody>
       </table>
     `;
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
index dc09479..de9c5fb 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
@@ -26,7 +26,7 @@
   query,
 } from 'lit-element';
 import {GrLitElement} from '../lit/gr-lit-element';
-import {Action, RunStatus} from '../../api/checks';
+import {Action, Link, RunStatus} from '../../api/checks';
 import {sharedStyles} from '../../styles/shared-styles';
 import {
   AttemptDetail,
@@ -50,6 +50,7 @@
   fakeRun4_3,
   fakeRun4_4,
   updateStateSetResults,
+  fakeLinks,
 } from '../../services/checks/checks-model';
 import {assertIsDefined} from '../../utils/common-util';
 import {whenVisible} from '../../utils/dom-util';
@@ -436,7 +437,8 @@
         <gr-button link @click="${this.none}">none</gr-button>
         <gr-button
           link
-          @click="${() => this.toggle('f0', [fakeRun0], fakeActions)}"
+          @click="${() =>
+            this.toggle('f0', [fakeRun0], fakeActions, fakeLinks)}"
           >0</gr-button
         >
         <gr-button link @click="${() => this.toggle('f1', [fakeRun1])}"
@@ -474,7 +476,7 @@
   }
 
   all() {
-    updateStateSetResults('f0', [fakeRun0], fakeActions);
+    updateStateSetResults('f0', [fakeRun0], fakeActions, fakeLinks);
     updateStateSetResults('f1', [fakeRun1]);
     updateStateSetResults('f2', [fakeRun2]);
     updateStateSetResults('f3', [fakeRun3]);
@@ -486,9 +488,14 @@
     ]);
   }
 
-  toggle(plugin: string, runs: CheckRun[], actions: Action[] = []) {
+  toggle(
+    plugin: string,
+    runs: CheckRun[],
+    actions: Action[] = [],
+    links: Link[] = []
+  ) {
     const newRuns = this.runs.includes(runs[0]) ? [] : runs;
-    updateStateSetResults(plugin, newRuns, actions);
+    updateStateSetResults(plugin, newRuns, actions, links);
   }
 
   renderSection(status: RunStatus) {
diff --git a/polygerrit-ui/app/elements/checks/gr-hovercard-run.ts b/polygerrit-ui/app/elements/checks/gr-hovercard-run.ts
index 5ebd05c..ae6408d 100644
--- a/polygerrit-ui/app/elements/checks/gr-hovercard-run.ts
+++ b/polygerrit-ui/app/elements/checks/gr-hovercard-run.ts
@@ -20,8 +20,14 @@
 import {htmlTemplate} from './gr-hovercard-run_html';
 import {customElement, property} from '@polymer/decorators';
 import {CheckRun} from '../../services/checks/checks-model';
-import {iconForRun} from '../../services/checks/checks-util';
-import {fromNow} from '../../utils/date-util';
+import {
+  iconForCategory,
+  iconForStatus,
+  runActions,
+  worstCategory,
+} from '../../services/checks/checks-util';
+import {durationString, fromNow} from '../../utils/date-util';
+import {RunStatus} from '../../api/checks';
 
 @customElement('gr-hovercard-run')
 export class GrHovercardRun extends hovercardBehaviorMixin(PolymerElement) {
@@ -32,13 +38,78 @@
   @property({type: Object})
   run?: CheckRun;
 
-  computeIcon(run: CheckRun) {
-    return iconForRun(run);
+  computeIcon(run?: CheckRun) {
+    if (!run) return '';
+    const category = worstCategory(run);
+    if (category) return iconForCategory(category);
+    return run.status === RunStatus.COMPLETED
+      ? iconForStatus(RunStatus.COMPLETED)
+      : '';
   }
 
-  computeDuration(date: Date) {
+  computeActions(run?: CheckRun) {
+    return runActions(run);
+  }
+
+  computeChipIcon(run?: CheckRun) {
+    if (run?.status === RunStatus.COMPLETED) return 'check';
+    if (run?.status === RunStatus.RUNNING) return 'timelapse';
+    return '';
+  }
+
+  computeCompletionDuration(run?: CheckRun) {
+    if (!run?.finishedTimestamp || !run?.startedTimestamp) return '';
+    return durationString(run.startedTimestamp, run.finishedTimestamp, true);
+  }
+
+  computeDuration(date?: Date) {
     return date ? fromNow(date) : '';
   }
+
+  computeHostName(link?: string) {
+    return link ? new URL(link).hostname : '';
+  }
+
+  hideChip(run?: CheckRun) {
+    return !run || run.status === RunStatus.RUNNABLE;
+  }
+
+  hideHeaderSectionIcon(run?: CheckRun) {
+    return this.computeIcon(run).length === 0;
+  }
+
+  hideStatusSection(run?: CheckRun) {
+    if (!run) return true;
+    return !run.statusLink && !run.statusDescription;
+  }
+
+  hideAttemptSection(run?: CheckRun) {
+    if (!run) return true;
+    return (
+      !run.startedTimestamp &&
+      !run.scheduledTimestamp &&
+      !run.finishedTimestamp &&
+      this.hideAttempts(run)
+    );
+  }
+
+  hideAttempts(run?: CheckRun) {
+    const attemptCount = run?.attemptDetails?.length;
+    return attemptCount === undefined || attemptCount < 2;
+  }
+
+  hideScheduled(run?: CheckRun) {
+    return !run?.scheduledTimestamp || !!run?.startedTimestamp;
+  }
+
+  hideCompletion(run?: CheckRun) {
+    return !run?.startedTimestamp || !run?.finishedTimestamp;
+  }
+
+  hideDescriptionSection(run?: CheckRun) {
+    if (!run) return true;
+    return !run.checkLink && !run.checkDescription;
+  }
 }
 
 declare global {
diff --git a/polygerrit-ui/app/elements/checks/gr-hovercard-run_html.ts b/polygerrit-ui/app/elements/checks/gr-hovercard-run_html.ts
index cb7e464..08ceefe 100644
--- a/polygerrit-ui/app/elements/checks/gr-hovercard-run_html.ts
+++ b/polygerrit-ui/app/elements/checks/gr-hovercard-run_html.ts
@@ -22,74 +22,187 @@
   </style>
   <style include="gr-hovercard-shared-style">
     #container {
-      padding: var(--spacing-xl);
-    }
-    h3 iron-icon {
-      position: relative;
-      top: 2px;
+      min-width: 356px;
+      max-width: 356px;
+      padding: var(--spacing-xl) 0 var(--spacing-m) 0;
     }
     .row {
+      display: flex;
       margin-top: var(--spacing-s);
     }
+    .chipRow {
+      display: flex;
+      margin-top: var(--spacing-s);
+    }
+    .chip {
+      background: var(--gray-background);
+      color: var(--gray-foreground);
+      border-radius: 20px;
+      padding: var(--spacing-xs) var(--spacing-m) var(--spacing-xs)
+        var(--spacing-s);
+    }
     .title {
       color: var(--deemphasized-text-color);
-      margin-right: var(--spacing-s);
+      margin-right: var(--spacing-m);
     }
-    iron-icon.launch {
+    div.section {
+      margin: 0 var(--spacing-xl) var(--spacing-m) var(--spacing-xl);
+      display: flex;
+    }
+    div.sectionIcon {
+      flex: 0 0 30px;
+    }
+    div.chip iron-icon {
+      width: 16px;
+      height: 16px;
+      /* Positioning of a 16px icon in the middle of a 20px line. */
+      position: relative;
+      top: 2px;
+    }
+    div.sectionIcon iron-icon {
+      position: relative;
+      top: 2px;
+      width: 20px;
+      height: 20px;
+    }
+    div.sectionIcon iron-icon.small {
+      position: relative;
+      top: 6px;
+      width: 16px;
+      height: 16px;
+    }
+    div.sectionContent iron-icon.link {
       color: var(--link-color);
     }
+    div.sectionContent .attemptIcon iron-icon,
+    div.sectionContent iron-icon.small {
+      width: 16px;
+      height: 16px;
+      margin-right: var(--spacing-s);
+      /* Positioning of a 16px icon in the middle of a 20px line. */
+      position: relative;
+      top: 2px;
+    }
+    .attemptNumber {
+      margin-right: var(--spacing-s);
+      color: var(--deemphasized-text-color);
+      text-align: center;
+    }
+    div.action {
+      border-top: 1px solid var(--border-color);
+      margin-top: var(--spacing-m);
+      padding: var(--spacing-m) var(--spacing-xl) 0;
+    }
   </style>
   <div id="container" role="tooltip" tabindex="-1">
-    <h3 class="name heading-3">
-      <iron-icon
-        class$="[[computeIcon(run)]]"
-        icon="gr-icons:[[computeIcon(run)]]"
-      ></iron-icon>
-      <span>[[run.checkName]]</span>
-    </h3>
-    <div hidden$="[[!run.checkDescription]]" class="row">
-      <span class="title">Description</span>
-      <span>[[run.checkDescription]]</span>
+    <div class="section">
+      <div hidden$="[[hideChip(run)]]" class="chipRow">
+        <div class="chip">
+          <iron-icon icon="gr-icons:[[computeChipIcon(run)]]"></iron-icon>
+          <span>[[run.status]]</span>
+        </div>
+      </div>
     </div>
-    <div hidden$="[[!run.checkLink]]" class="row">
-      <span class="title">Documentation</span>
-      <a href="[[run.checkLink]]" target="_blank">
+    <div class="section">
+      <div class="sectionIcon" hidden$="[[hideHeaderSectionIcon(run)]]">
         <iron-icon
-          aria-label="external link to check documentation"
-          class="launch"
-          icon="gr-icons:launch"
+          class$="[[computeIcon(run)]]"
+          icon="gr-icons:[[computeIcon(run)]]"
         ></iron-icon>
-      </a>
+      </div>
+      <div class="sectionContent">
+        <h3 class="name heading-3">
+          <span>[[run.checkName]]</span>
+        </h3>
+      </div>
     </div>
-    <div hidden$="[[!run.statusDescription]]" class="row">
-      <span class="title">Status</span>
-      <span>[[run.statusDescription]]</span>
+    <div class="section" hidden$="[[hideStatusSection(run)]]">
+      <div class="sectionIcon">
+        <iron-icon class="small" icon="gr-icons:info-outline"></iron-icon>
+      </div>
+      <div class="sectionContent">
+        <div hidden$="[[!run.statusLink]]" class="row">
+          <div class="title">Status</div>
+          <div>
+            <a href="[[run.statusLink]]" target="_blank"
+              ><iron-icon
+                aria-label="external link to check status"
+                class="small link"
+                icon="gr-icons:launch"
+              ></iron-icon
+              >[[computeHostName(run.statusLink)]]
+            </a>
+          </div>
+        </div>
+        <div hidden$="[[!run.statusDescription]]" class="row">
+          <div class="title">Message</div>
+          <div>[[run.statusDescription]]</div>
+        </div>
+      </div>
     </div>
-    <div hidden$="[[!run.statusLink]]" class="row">
-      <span class="title">Status Link</span>
-      <a href="[[run.statusLink]]" target="_blank">
-        <iron-icon
-          aria-label="external link to check status"
-          class="launch"
-          icon="gr-icons:launch"
-        ></iron-icon>
-      </a>
+    <div class="section" hidden$="[[hideAttemptSection(run)]]">
+      <div class="sectionIcon">
+        <iron-icon class="small" icon="gr-icons:schedule"></iron-icon>
+      </div>
+      <div class="sectionContent">
+        <div hidden$="[[hideAttempts(run)]]" class="row">
+          <div class="title">Attempt</div>
+          <template is="dom-repeat" items="[[run.attemptDetails]]">
+            <div>
+              <div class="attemptIcon">
+                <iron-icon
+                  class$="[[item.icon]]"
+                  icon="gr-icons:[[item.icon]]"
+                ></iron-icon>
+              </div>
+              <div class="attemptNumber">[[item.attempt]]</div>
+            </div>
+          </template>
+        </div>
+        <div hidden$="[[hideScheduled(run)]]" class="row">
+          <div class="title">Scheduled</div>
+          <div>[[computeDuration(run.scheduledTimestamp)]]</div>
+        </div>
+        <div hidden$="[[!run.startedTimestamp]]" class="row">
+          <div class="title">Started</div>
+          <div>[[computeDuration(run.startedTimestamp)]]</div>
+        </div>
+        <div hidden$="[[!run.finishedTimestamp]]" class="row">
+          <div class="title">Ended</div>
+          <div>[[computeDuration(run.finishedTimestamp)]]</div>
+        </div>
+        <div hidden$="[[hideCompletion(run)]]" class="row">
+          <div class="title">Completion</div>
+          <div>[[computeCompletionDuration(run)]]</div>
+        </div>
+      </div>
     </div>
-    <div hidden$="[[!run.attempt]]" class="row">
-      <span class="title">Attempt</span>
-      <span>[[run.attempt]]</span>
+    <div class="section" hidden$="[[hideDescriptionSection(run)]]">
+      <div class="sectionIcon">
+        <iron-icon class="small" icon="gr-icons:link"></iron-icon>
+      </div>
+      <div class="sectionContent">
+        <div hidden$="[[!run.checkDescription]]" class="row">
+          <div class="title">Description</div>
+          <div>[[run.checkDescription]]</div>
+        </div>
+        <div hidden$="[[!run.checkLink]]" class="row">
+          <div class="title">Documentation</div>
+          <div>
+            <a href="[[run.checkLink]]" target="_blank"
+              ><iron-icon
+                aria-label="external link to check documentation"
+                class="small link"
+                icon="gr-icons:launch"
+              ></iron-icon
+              >[[computeHostName(run.checkLink)]]
+            </a>
+          </div>
+        </div>
+      </div>
     </div>
-    <div hidden$="[[!run.scheduledTimestamp]]" class="row">
-      <span class="title">Scheduled</span>
-      <span>[[computeDuration(run.scheduledTimestamp)]]</span>
-    </div>
-    <div hidden$="[[!run.startedTimestamp]]" class="row">
-      <span class="title">Started</span>
-      <span>[[computeDuration(run.startedTimestamp)]]</span>
-    </div>
-    <div hidden$="[[!run.finishedTimestamp]]" class="row">
-      <span class="title">Finished</span>
-      <span>[[computeDuration(run.finishedTimestamp)]]</span>
-    </div>
+    <template is="dom-repeat" items="[[computeActions(run)]]">
+      <div class="action"><gr-button link>[[item.name]]</gr-button></div>
+    </template>
   </div>
 `;
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_html.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_html.ts
index 2fb1fa0..b0716dd 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_html.ts
@@ -31,10 +31,6 @@
       background-color: var(--background-color-secondary);
       border-bottom: 1px solid var(--border-color);
     }
-    .fixActions {
-      display: flex;
-      justify-content: flex-end;
-    }
     gr-button {
       margin-left: var(--spacing-m);
     }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
index 30c6f49..0412779 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
@@ -112,10 +112,6 @@
     .prefsButton {
       text-align: right;
     }
-    .noOverflow {
-      display: block;
-      overflow: auto;
-    }
     .editMode .hideOnEdit {
       display: none;
     }
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_html.ts b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_html.ts
index 69ac702..ab24168 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_html.ts
@@ -31,14 +31,6 @@
       padding: var(--spacing-xxl);
       width: 50em;
     }
-    .publicKey {
-      font-family: var(--monospace-font-family);
-      font-size: var(--font-size-mono);
-      line-height: var(--line-height-mono);
-      overflow-x: scroll;
-      overflow-wrap: break-word;
-      width: 30em;
-    }
     .closeButton {
       bottom: 2em;
       position: absolute;
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
index 89cab8a..71c1add 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
@@ -20,7 +20,6 @@
 import {htmlTemplate} from './gr-change-status_html';
 import {customElement, property} from '@polymer/decorators';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
-import {getRevertCreatedChangeIds} from '../../../utils/message-util';
 import {ChangeInfo} from '../../../types/common';
 import {ParsedChangeInfo} from '../../../types/types';
 
@@ -67,7 +66,7 @@
   tooltipText = '';
 
   @property({type: Object})
-  revertSubmittedChange?: ChangeInfo;
+  revertedChange?: ChangeInfo;
 
   _computeStatusString(status: ChangeStates) {
     if (status === ChangeStates.WIP && !this.flat) {
@@ -80,26 +79,15 @@
     return str ? str.toLowerCase().replace(/\s/g, '-') : '';
   }
 
-  hasStatusLink(status: ChangeStates) {
-    return (
-      status === ChangeStates.REVERT_CREATED ||
-      status === ChangeStates.REVERT_SUBMITTED
-    );
+  hasStatusLink(revertedChange?: ChangeInfo) {
+    return revertedChange !== undefined;
   }
 
-  getStatusLink(change?: ParsedChangeInfo, status?: ChangeStates) {
-    if (!change?.messages) return;
-    if (status === ChangeStates.REVERT_CREATED) {
-      const revertChangeId = getRevertCreatedChangeIds(change.messages)?.[0];
-      if (!revertChangeId) return;
-      return GerritNav.getUrlForSearchQuery(revertChangeId);
-    }
-    if (this.revertSubmittedChange) {
-      return GerritNav.getUrlForSearchQuery(
-        `${this.revertSubmittedChange._number}`
-      );
-    }
-    return;
+  getStatusLink(revertedChange?: ChangeInfo) {
+    return (
+      revertedChange &&
+      GerritNav.getUrlForSearchQuery(`${revertedChange._number}`)
+    );
   }
 
   _updateChipDetails(status?: ChangeStates, previousStatus?: ChangeStates) {
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_html.ts b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_html.ts
index 2a96cdf..3d227a9 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_html.ts
@@ -81,14 +81,14 @@
     title="[[tooltipText]]"
     max-width="40em"
   >
-    <template is="dom-if" if="[[hasStatusLink(status)]]">
-      <a class="status-link" href="[[getStatusLink(change, status)]]">
+    <template is="dom-if" if="[[!!hasStatusLink(revertedChange)]]">
+      <a class="status-link" href="[[getStatusLink(revertedChange)]]">
         <div class="chip" aria-label$="Label: [[status]]">
           [[_computeStatusString(status)]]
         </div>
       </a>
     </template>
-    <template is="dom-if" if="[[!hasStatusLink(status)]]">
+    <template is="dom-if" if="[[!hasStatusLink(revertedChange)]]">
       <div class="chip" aria-label$="Label: [[status]]">
         [[_computeStatusString(status)]]
       </div>
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
index a1644e7..1f0ce3d 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
@@ -80,10 +80,6 @@
       justify-content: space-between;
       padding: 0 var(--spacing-s) var(--spacing-s);
     }
-    .descriptionText {
-      margin-left: var(--spacing-m);
-      font-style: italic;
-    }
     .fileName {
       padding: var(--spacing-m) var(--spacing-s) var(--spacing-m);
     }
diff --git a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
index 2957320..e324cf25 100644
--- a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
+++ b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
@@ -140,8 +140,10 @@
       <g id="download"><path d="M0 0h24v24H0z" fill="none"/><path d="M5,20h14v-2H5V20z M19,9h-4V3H9v6H5l7,7L19,9z"/></g>
       <!-- This SVG is a copy from material.io https://material.io/icons/#system_update-->
       <g id="system-update"><path d="M0 0h24v24H0z" fill="none"/><path d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14zm-1-6h-3V8h-2v5H8l4 4 4-4z"/></g>
-      <!-- This SVG is a copy from material.io https://fonts.google.com/icons?selected=Material%20Icons%3Aswap_horiz-->
+      <!-- This SVG is a copy from material.io https://material.io/icons/#swap_horiz-->
       <g id="swapHoriz"><path d="M0 0h24v24H0z" fill="none"/><path d="M6.99 11L3 15l3.99 4v-3H14v-2H6.99v-3zM21 9l-3.99-4v3H10v2h7.01v3L21 9z"/></g>
+      <!-- This SVG is a copy from material.io https://material.io/icons/#link-->
+      <g id="link"><path d="M0 0h24v24H0z" fill="none"/><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/></g>
     </defs>
   </svg>
 </iron-iconset-svg>`;
diff --git a/polygerrit-ui/app/services/checks/checks-model.ts b/polygerrit-ui/app/services/checks/checks-model.ts
index b7b8bda..ecd56cb 100644
--- a/polygerrit-ui/app/services/checks/checks-model.ts
+++ b/polygerrit-ui/app/services/checks/checks-model.ts
@@ -22,6 +22,7 @@
   CheckResult as CheckResultApi,
   CheckRun as CheckRunApi,
   ChecksApiConfig,
+  Link,
   LinkIcon,
   RunStatus,
 } from '../../api/checks';
@@ -76,6 +77,7 @@
   config?: ChecksApiConfig;
   runs: CheckRun[];
   actions: Action[];
+  links: Link[];
 }
 
 interface ChecksState {
@@ -148,6 +150,18 @@
   )
 );
 
+export const allLinks$ = checksProviderState$.pipe(
+  map(state =>
+    Object.values(state).reduce(
+      (allActions: Link[], providerState: ChecksProviderState) => [
+        ...allActions,
+        ...providerState.links,
+      ],
+      []
+    )
+  )
+);
+
 export const allRuns$ = checksProviderState$.pipe(
   map(state =>
     Object.values(state).reduce(
@@ -208,6 +222,7 @@
     config,
     runs: [],
     actions: [],
+    links: [],
   };
   privateState$.next(nextState);
 }
@@ -303,9 +318,35 @@
 export const fakeRun2: CheckRun = {
   internalRunId: 'f2',
   checkName: 'FAKE Mega Analysis',
+  statusDescription: 'This run is nearly completed, but not quite.',
+  statusLink: 'https://www.google.com/',
+  checkDescription:
+    'From what the title says you can tell that this check analyses.',
+  checkLink: 'https://www.google.com/',
+  scheduledTimestamp: new Date('2021-04-01T03:14:15'),
+  startedTimestamp: new Date('2021-04-01T04:24:25'),
+  finishedTimestamp: new Date('2021-04-01T04:44:44'),
   isSingleAttempt: true,
   isLatestAttempt: true,
   attemptDetails: [],
+  actions: [
+    {
+      name: 'Re-Run',
+      tooltip: 'More powerful run than before',
+      primary: true,
+      callback: () => undefined,
+    },
+    {
+      name: 'Monetize',
+      primary: true,
+      callback: () => undefined,
+    },
+    {
+      name: 'Delete',
+      primary: true,
+      callback: () => undefined,
+    },
+  ],
   results: [
     {
       internalResultId: 'f2r0',
@@ -380,8 +421,15 @@
 export const fakeRun4_4: CheckRun = {
   internalRunId: 'f4',
   checkName: 'FAKE Elimination',
+  checkDescription: 'Shows you the possible eliminations.',
+  checkLink: 'https://www.google.com',
   status: RunStatus.RUNNING,
+  statusDescription: 'Everything was eliminated already.',
+  statusLink: 'https://www.google.com',
   attempt: 4,
+  scheduledTimestamp: new Date('2021-04-02T03:14:15'),
+  startedTimestamp: new Date('2021-04-02T04:24:25'),
+  finishedTimestamp: new Date('2021-04-02T04:25:44'),
   isSingleAttempt: false,
   isLatestAttempt: true,
   attemptDetails: [],
@@ -424,6 +472,21 @@
   },
 ];
 
+export const fakeLinks: Link[] = [
+  {
+    url: 'https://www.google.com',
+    primary: false,
+    tooltip: 'Tooltip for Bug Report Fake Link',
+    icon: LinkIcon.REPORT_BUG,
+  },
+  {
+    url: 'https://www.google.com',
+    primary: false,
+    tooltip: 'Tooltip for External Fake Link',
+    icon: LinkIcon.EXTERNAL,
+  },
+];
+
 export function updateStateSetLoading(pluginName: string) {
   const nextState = {...privateState$.getValue()};
   nextState.providerNameToState = {...nextState.providerNameToState};
@@ -468,7 +531,8 @@
 export function updateStateSetResults(
   pluginName: string,
   runs: CheckRunApi[],
-  actions: Action[] = []
+  actions: Action[] = [],
+  links: Link[] = []
 ) {
   const attemptMap = createAttemptMap(runs);
   for (const attemptInfo of attemptMap.values()) {
@@ -502,6 +566,7 @@
       };
     }),
     actions: [...actions],
+    links: [...links],
   };
   privateState$.next(nextState);
 }
diff --git a/polygerrit-ui/app/services/checks/checks-service.ts b/polygerrit-ui/app/services/checks/checks-service.ts
index c7e42ba..250cea5 100644
--- a/polygerrit-ui/app/services/checks/checks-service.ts
+++ b/polygerrit-ui/app/services/checks/checks-service.ts
@@ -176,7 +176,8 @@
             updateStateSetResults(
               pluginName,
               response.runs ?? [],
-              response.actions
+              response.actions ?? [],
+              response.links ?? []
             );
             break;
         }
diff --git a/polygerrit-ui/app/services/checks/checks-util.ts b/polygerrit-ui/app/services/checks/checks-util.ts
index 27da2a8..15841f3 100644
--- a/polygerrit-ui/app/services/checks/checks-util.ts
+++ b/polygerrit-ui/app/services/checks/checks-util.ts
@@ -128,9 +128,14 @@
 }
 
 export function primaryRunAction(run: CheckRun): Action | undefined {
-  return (run.actions ?? [])
-    .map(action => toCanonicalAction(action, run.status))
-    .filter(action => action.name === primaryActionName(run.status))[0];
+  return runActions(run).filter(
+    action => action.name === primaryActionName(run.status)
+  )[0];
+}
+
+export function runActions(run?: CheckRun): Action[] {
+  if (!run?.actions) return [];
+  return run.actions.map(action => toCanonicalAction(action, run.status));
 }
 
 export function iconForRun(run: CheckRun) {