Merge "Add check run hovercard also to results tables"
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts
index f6b3aa0..8e1ffef 100644
--- a/polygerrit-ui/app/api/diff.ts
+++ b/polygerrit-ui/app/api/diff.ts
@@ -302,6 +302,15 @@
   buttonType: ContextButtonType;
 }
 
+/**
+ * Details to be externally accessed when hovering context
+ * expansion buttons
+ */
+export declare interface DiffContextButtonHoveredDetail {
+  linesToExpand: number;
+  buttonType: ContextButtonType;
+}
+
 export declare type ImageDiffAction =
   | {type: 'overview-image-clicked'}
   | {type: 'overview-frame-dragged'}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
index ea5339e..2086270 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
@@ -108,6 +108,8 @@
 
   private readonly restApiService = appContext.restApiService;
 
+  private reporting = appContext.reportingService;
+
   constructor() {
     super();
     this.addEventListener('next-page', () => this._handleNextPage());
@@ -272,6 +274,9 @@
   }
 
   _handleToggleStar(e: CustomEvent<ChangeStarToggleStarDetail>) {
+    if (e.detail.starred) {
+      this.reporting.reportInteraction('change-starred-from-change-list');
+    }
     this.restApiService.saveChangeStarred(
       e.detail.change._number,
       e.detail.starred
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
index bde7d77..e93b293 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
@@ -372,6 +372,9 @@
       e.detail.change._number,
       e.detail.starred
     );
+    if (e.detail.starred) {
+      this.reporting.reportInteraction('change-starred-from-dashboard');
+    }
     // When a change is updated the same change may appear elsewhere in the
     // dashboard (but is not the same object), so we must update other
     // occurrences of the same change.
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 f05d665..a11b142 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
@@ -2527,6 +2527,7 @@
 
   _handleToggleStar(e: CustomEvent<{change: ChangeInfo; starred: boolean}>) {
     if (e.detail.starred) {
+      this.reporting.reportInteraction('change-starred-from-change-view');
       this.lastStarredTimestamp = Date.now();
     } else {
       if (
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 751de1dc..511837e 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -165,8 +165,8 @@
         tr.collapsed td .summary-cell .actions {
           display: none;
         }
-        tr.collapsed:hover .summary-cell .tags,
-        tr.collapsed:hover .summary-cell .label {
+        tr.collapsed:hover .summary-cell .hoverHide.tags,
+        tr.collapsed:hover .summary-cell .hoverHide.label {
           display: none;
         }
         td .summary-cell .tags .tag {
@@ -265,7 +265,7 @@
             <div class="message" @click="${this.toggleExpanded}">
               ${this.isExpanded ? '' : this.result.message}
             </div>
-            <div class="tags">
+            <div class="tags ${this.hasLinksOrActions() ? 'hoverHide' : ''}">
               ${(this.result.tags ?? []).map(t => this.renderTag(t))}
             </div>
             ${this.renderLabel()} ${this.renderLinks()} ${this.renderActions()}
@@ -295,6 +295,13 @@
     `;
   }
 
+  private hasLinksOrActions() {
+    const linkCount = this.result?.links?.length ?? 0;
+    const actionCount = this.result?.actions?.length ?? 0;
+    // The primary link is rendered somewhere else, so it does not count here.
+    return linkCount > 1 || actionCount > 0;
+  }
+
   private renderExpanded() {
     if (!this.isExpanded) return;
     return html`<gr-result-expanded
@@ -324,7 +331,11 @@
   renderLabel() {
     const label = this.result?.labelName;
     if (!label) return;
-    return html`<div class="label">${label}</div>`;
+    return html`
+      <div class="label ${this.hasLinksOrActions() ? 'hoverHide' : ''}">
+        ${label}
+      </div>
+    `;
   }
 
   renderLinks() {
@@ -456,6 +467,21 @@
 SHOW_ALL_THRESHOLDS.set(Category.INFO, 5);
 SHOW_ALL_THRESHOLDS.set(Category.SUCCESS, 5);
 
+const CATEGORY_TOOLTIPS: Map<Category, string> = new Map();
+CATEGORY_TOOLTIPS.set(Category.ERROR, 'Must be fixed and is blocking submit');
+CATEGORY_TOOLTIPS.set(
+  Category.WARNING,
+  'Should be checked but is not blocking submit'
+);
+CATEGORY_TOOLTIPS.set(
+  Category.INFO,
+  'Does not have to be checked, for your information only'
+);
+CATEGORY_TOOLTIPS.set(
+  Category.SUCCESS,
+  'Successful runs without results and individual successful results'
+);
+
 @customElement('gr-checks-results')
 export class GrChecksResults extends GrLitElement {
   @query('#filterInput')
@@ -608,6 +634,9 @@
           height: var(--line-height-h3);
           margin-right: var(--spacing-s);
         }
+        .categoryHeader .statusIconWrapper {
+          display: inline-block;
+        }
         .categoryHeader .statusIcon {
           position: relative;
           top: 2px;
@@ -892,11 +921,16 @@
           @click="${() => this.toggleExpanded(category)}"
         >
           <iron-icon class="expandIcon" icon="${icon}"></iron-icon>
-          <iron-icon
-            icon="gr-icons:${iconForCategory(category)}"
-            class="statusIcon ${catString}"
-          ></iron-icon>
-          <span class="title">${catString}</span>
+          <div class="statusIconWrapper">
+            <iron-icon
+              icon="gr-icons:${iconForCategory(category)}"
+              class="statusIcon ${catString}"
+            ></iron-icon>
+            <span class="title">${catString}</span>
+            <paper-tooltip offset="5"
+              >${CATEGORY_TOOLTIPS.get(category)}</paper-tooltip
+            >
+          </div>
           <span class="count"
             >${this.renderCount(all, selected, filtered)}</span
           >
diff --git a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts b/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
index 41a448e..e78945b 100644
--- a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
+++ b/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
@@ -23,6 +23,8 @@
 import '@polymer/paper-item/paper-item';
 import '@polymer/paper-listbox/paper-listbox';
 import '@polymer/paper-tooltip/paper-tooltip.js';
+import {of, EMPTY, Subject} from 'rxjs';
+import {switchMap, delay, takeUntil} from 'rxjs/operators';
 
 import '../../shared/gr-button/gr-button';
 import {pluralize} from '../../../utils/string-util';
@@ -40,12 +42,19 @@
 
 import {
   ContextButtonType,
+  DiffContextButtonHoveredDetail,
   RenderPreferences,
   SyntaxBlock,
 } from '../../../api/diff';
 
 import {GrDiffGroup, hideInContextControl} from '../gr-diff/gr-diff-group';
 
+declare global {
+  interface HTMLElementEventMap {
+    'diff-context-button-hovered': CustomEvent<DiffContextButtonHoveredDetail>;
+  }
+}
+
 const PARTIAL_CONTEXT_AMOUNT = 10;
 
 /**
@@ -88,6 +97,14 @@
 
   @property({type: Boolean}) showBelow = false;
 
+  private expandButtonsHover = new Subject<{
+    eventType: 'enter' | 'leave';
+    buttonType: ContextButtonType;
+    linesToExpand: number;
+  }>();
+
+  private disconnected$ = new Subject();
+
   static styles = css`
     :host {
       display: flex;
@@ -193,6 +210,36 @@
     </custom-style>
   `;
 
+  connectedCallback() {
+    super.connectedCallback();
+    this.setupButtonHoverHandler();
+  }
+
+  disconnectedCallback() {
+    this.disconnected$.next();
+  }
+
+  setupButtonHoverHandler() {
+    this.expandButtonsHover
+      .pipe(
+        switchMap(e => {
+          if (e.eventType === 'leave') {
+            // cancel any previous delay
+            // for mouse enter
+            return EMPTY;
+          }
+          return of(e).pipe(delay(500));
+        }),
+        takeUntil(this.disconnected$)
+      )
+      .subscribe(({buttonType, linesToExpand}) => {
+        fire(this, 'diff-context-button-hovered', {
+          buttonType,
+          linesToExpand,
+        });
+      });
+  }
+
   private numLines() {
     const {leftStart, leftEnd} = this.contextRange();
     return leftEnd - leftStart + 1;
@@ -276,12 +323,22 @@
       groups
     );
 
+    const mouseHander = (eventType: 'enter' | 'leave') => {
+      this.expandButtonsHover.next({
+        eventType,
+        buttonType: type,
+        linesToExpand,
+      });
+    };
+
     const button = html` <gr-button
       class="${classes}"
       link="true"
       no-uppercase="true"
       aria-label="${ariaLabel}"
       @click="${expandHandler}"
+      @mouseenter="${() => mouseHander('enter')}"
+      @mouseleave="${() => mouseHander('leave')}"
     >
       <span class="showContext">${text}</span>
       ${tooltip}