Push Notifications - metrics

We report about permission for notifications in browser.

We report when service worker is
 * polling
 * creating notification

Google-Bug-Id: b/202288468
Release-Notes: skip
Change-Id: Ia4727257262f0465e29b2ddd3983f8a73ac16739
diff --git a/polygerrit-ui/app/constants/reporting.ts b/polygerrit-ui/app/constants/reporting.ts
index 0e00d07..ae4aad9 100644
--- a/polygerrit-ui/app/constants/reporting.ts
+++ b/polygerrit-ui/app/constants/reporting.ts
@@ -14,6 +14,8 @@
   PLUGINS_INSTALLED = 'Plugins installed',
   PLUGINS_FAILED = 'Some plugins failed to load',
   USER_REFERRED_FROM = 'User referred from',
+  NOTIFICATION_PERMISSION = 'Notification Permission',
+  SERVICE_WORKER_UPDATE = 'Service worker update',
 }
 
 export enum Execution {
diff --git a/polygerrit-ui/app/elements/gr-app.ts b/polygerrit-ui/app/elements/gr-app.ts
index 0cec8dd..fc42240 100644
--- a/polygerrit-ui/app/elements/gr-app.ts
+++ b/polygerrit-ui/app/elements/gr-app.ts
@@ -65,9 +65,11 @@
       this.finalizables.push(service);
       provide(this, token, () => service);
     }
+    // TODO(milutin): Move inside app dependencies.
     if (!this.serviceWorkerInstaller) {
       this.serviceWorkerInstaller = new ServiceWorkerInstaller(
         appContext.flagsService,
+        appContext.reportingService,
         appContext.userModel
       );
     }
diff --git a/polygerrit-ui/app/services/service-worker-installer.ts b/polygerrit-ui/app/services/service-worker-installer.ts
index 53cd325..ffc5be2 100644
--- a/polygerrit-ui/app/services/service-worker-installer.ts
+++ b/polygerrit-ui/app/services/service-worker-installer.ts
@@ -12,11 +12,14 @@
 import {UserModel} from '../models/user/user-model';
 import {AccountDetailInfo} from '../api/rest-api';
 import {until} from '../utils/async-util';
+import {LifeCycle} from '../constants/reporting';
+import {ReportingService} from './gr-reporting/gr-reporting';
 
 /** Type of incoming messages for ServiceWorker. */
 export enum ServiceWorkerMessageType {
   TRIGGER_NOTIFICATIONS = 'TRIGGER_NOTIFICATIONS',
   USER_PREFERENCE_CHANGE = 'USER_PREFERENCE_CHANGE',
+  REPORTING = 'REPORTING',
 }
 
 export const TRIGGER_NOTIFICATION_UPDATES_MS = 5 * 60 * 1000;
@@ -30,6 +33,7 @@
 
   constructor(
     private readonly flagsService: FlagsService,
+    private readonly reportingService: ReportingService,
     private readonly userModel: UserModel
   ) {
     if (!this.flagsService.isEnabled(KnownExperimentId.PUSH_NOTIFICATIONS)) {
@@ -74,8 +78,19 @@
     }
     await registerServiceWorker('/service-worker.js');
     const permission = await Notification.requestPermission();
+    this.reportingService.reportLifeCycle(LifeCycle.NOTIFICATION_PERMISSION, {
+      permission,
+    });
     if (this.isPermitted(permission)) this.startTriggerTimer();
     this.initialized = true;
+    // Assumption: service worker will send event only to 1 client.
+    navigator.serviceWorker.onmessage = event => {
+      if (event.data?.type === ServiceWorkerMessageType.REPORTING) {
+        this.reportingService.reportLifeCycle(LifeCycle.SERVICE_WORKER_UPDATE, {
+          eventName: event.data.eventName as string | undefined,
+        });
+      }
+    };
   }
 
   areNotificationsEnabled() {
diff --git a/polygerrit-ui/app/services/service-worker-installer_test.ts b/polygerrit-ui/app/services/service-worker-installer_test.ts
index e8fd233..d982dee 100644
--- a/polygerrit-ui/app/services/service-worker-installer_test.ts
+++ b/polygerrit-ui/app/services/service-worker-installer_test.ts
@@ -14,9 +14,10 @@
   test('init', async () => {
     const registerStub = sinon.stub(window.navigator.serviceWorker, 'register');
     const flagsService = getAppContext().flagsService;
+    const reportingService = getAppContext().reportingService;
     const userModel = getAppContext().userModel;
     sinon.stub(flagsService, 'isEnabled').returns(true);
-    new ServiceWorkerInstaller(flagsService, userModel);
+    new ServiceWorkerInstaller(flagsService, reportingService, userModel);
     const prefs = {
       ...createDefaultPreferences(),
       allow_browser_notifications: true,
diff --git a/polygerrit-ui/app/workers/service-worker-class.ts b/polygerrit-ui/app/workers/service-worker-class.ts
index 218744d..ee85c0e 100644
--- a/polygerrit-ui/app/workers/service-worker-class.ts
+++ b/polygerrit-ui/app/workers/service-worker-class.ts
@@ -133,6 +133,7 @@
 
     // TODO(milutin): Add gerrit host icon
     this.ctx.registration.showNotification(change.subject, {body, data});
+    this.sendReport('notify about 1 change');
   }
 
   private showNotificationForDashboard(numOfChangesToNotifyAbout: number) {
@@ -140,6 +141,7 @@
     const dashboardUrl = createDashboardUrl({});
     const data = {url: `${self.location.origin}${dashboardUrl}`};
     this.ctx.registration.showNotification(title, {data});
+    this.sendReport(`notify about ${numOfChangesToNotifyAbout} changes`);
   }
 
   // private but used in test
@@ -154,6 +156,7 @@
     const prevLatestUpdateTimestampMs = this.latestUpdateTimestampMs;
     this.latestUpdateTimestampMs = Date.now();
     await this.saveState();
+    this.sendReport('polling');
     const changes = await this.getLatestAttentionSetChanges();
     const latestAttentionChanges = filterAttentionChangesAfter(
       changes,
@@ -173,4 +176,19 @@
     const changes = payload.parsed as unknown as ParsedChangeInfo[] | undefined;
     return changes ?? [];
   }
+
+  /**
+   * Send report event to 1 client (last focused one). The client will use
+   * gr-reporting service to send event to metric event collectors.
+   */
+  async sendReport(eventName: string) {
+    const clientsArr = await this.ctx.clients.matchAll({type: 'window'});
+    const lastFocusedClient = clientsArr?.[0];
+    if (!lastFocusedClient) return;
+
+    lastFocusedClient.postMessage({
+      type: ServiceWorkerMessageType.REPORTING,
+      eventName,
+    });
+  }
 }