Merge "Add tooltips to check result section categories"
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/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}