Add tracking for all frontend API calls

Change-Id: If9da951fbaec2600ee5ca9bf0883cfdf92da51c5
diff --git a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.ts b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.ts
index 897be67..7a91c68 100644
--- a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.ts
@@ -16,6 +16,7 @@
  */
 import {EventType, PluginApi} from '../../../api/plugin';
 import {AdminPluginApi, MenuLink} from '../../../api/admin';
+import {appContext} from '../../../services/app-context';
 
 /**
  * GrAdminApi class.
@@ -26,15 +27,20 @@
   // TODO(TS): maybe define as enum if its a limited set
   private menuLinks: MenuLink[] = [];
 
+  private readonly reporting = appContext.reportingService;
+
   constructor(private readonly plugin: PluginApi) {
+    this.reporting.trackApi(this.plugin, 'admin', 'constructor');
     this.plugin.on(EventType.ADMIN_MENU_LINKS, this);
   }
 
   addMenuLink(text: string, url: string, capability?: string) {
+    this.reporting.trackApi(this.plugin, 'admin', 'addMenuLink');
     this.menuLinks.push({text, url, capability: capability || null});
   }
 
   getMenuLinks(): MenuLink[] {
+    this.reporting.trackApi(this.plugin, 'admin', 'getMenuLinks');
     return this.menuLinks.slice(0);
   }
 }
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
index e0b4ee9..ab2ce6a 100644
--- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
@@ -15,14 +15,20 @@
  * limitations under the License.
  */
 import {AttributeHelperPluginApi} from '../../../api/attribute-helper';
+import {PluginApi} from '../../../api/plugin';
+import {appContext} from '../../../services/app-context';
 
 export class GrAttributeHelper implements AttributeHelperPluginApi {
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   private readonly _promises = new Map<string, Promise<any>>();
 
+  private readonly reporting = appContext.reportingService;
+
   // TODO(TS): Change any to something more like HTMLElement.
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  constructor(public element: any) {}
+  constructor(readonly plugin: PluginApi, public element: any) {
+    this.reporting.trackApi(this.plugin, 'attribute', 'constructor');
+  }
 
   _getChangedEventName(name: string): string {
     return name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() + '-changed';
@@ -52,6 +58,7 @@
    */
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   bind(name: string, callback: (value: any) => void) {
+    this.reporting.trackApi(this.plugin, 'attribute', 'bind');
     const attributeChangedEventName = this._getChangedEventName(name);
     const changedHandler = (e: CustomEvent) =>
       this._reportValue(callback, e.detail.value);
@@ -72,6 +79,7 @@
    * to be initialized if it isn't defined.
    */
   get(name: string): Promise<unknown> {
+    this.reporting.trackApi(this.plugin, 'attribute', 'get');
     if (this._elementHasProperty(name)) {
       return Promise.resolve(this.element[name]);
     }
@@ -93,6 +101,7 @@
    */
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   set(name: string, value: any) {
+    this.reporting.trackApi(this.plugin, 'attribute', 'set');
     this.element[name] = value;
     this.element.dispatchEvent(
       new CustomEvent(this._getChangedEventName(name), {detail: {value}})
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.js b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.js
index 7ea3be3..2d83012 100644
--- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.js
+++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.js
@@ -17,7 +17,7 @@
 
 import '../../../test/common-test-setup-karma.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
-import {GrAttributeHelper} from './gr-attribute-helper.js';
+import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
 
 Polymer({
   is: 'gr-attribute-helper-some-element',
@@ -31,13 +31,18 @@
 
 const basicFixture = fixtureFromElement('gr-attribute-helper-some-element');
 
+const pluginApi = _testOnly_initGerritPluginApi();
+
 suite('gr-attribute-helper tests', () => {
   let element;
   let instance;
 
   setup(() => {
+    let plugin;
+    pluginApi.install(p => { plugin = p; }, '0.1',
+        'http://test.com/plugins/testplugin/static/test.js');
     element = basicFixture.instantiate();
-    instance = new GrAttributeHelper(element);
+    instance = plugin.attributeHelper(element);
   });
 
   test('resolved on value change from undefined', () => {
diff --git a/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.ts b/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.ts
index a03c5dc..bbb58fc 100644
--- a/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.ts
@@ -17,15 +17,19 @@
 import {PluginApi} from '../../../api/plugin';
 import {ChangeMetadataPluginApi} from '../../../api/change-metadata';
 import {HookApi} from '../../../api/hook';
+import {appContext} from '../../../services/app-context';
 
 export class GrChangeMetadataApi implements ChangeMetadataPluginApi {
   private hook: HookApi | null;
 
   public plugin: PluginApi;
 
+  private readonly reporting = appContext.reportingService;
+
   constructor(plugin: PluginApi) {
     this.plugin = plugin;
     this.hook = null;
+    this.reporting.trackApi(this.plugin, 'metadata', 'constructor');
   }
 
   _createHook() {
@@ -33,6 +37,7 @@
   }
 
   onLabelsChanged(callback: (value: unknown) => void) {
+    this.reporting.trackApi(this.plugin, 'metadata', 'onLabelsChanged');
     if (!this.hook) {
       this._createHook();
     }
diff --git a/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api.ts b/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api.ts
index 404fc71..39d3c8b 100644
--- a/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api.ts
@@ -43,13 +43,19 @@
 
   private readonly checksService = appContext.checksService;
 
-  constructor(readonly plugin: PluginApi) {}
+  private readonly reporting = appContext.reportingService;
+
+  constructor(readonly plugin: PluginApi) {
+    this.reporting.trackApi(this.plugin, 'checks', 'constructor');
+  }
 
   announceUpdate() {
+    this.reporting.trackApi(this.plugin, 'checks', 'announceUpdate');
     this.checksService.reload(this.plugin.getPluginName());
   }
 
   register(provider: ChecksProvider, config?: ChecksApiConfig): void {
+    this.reporting.trackApi(this.plugin, 'checks', 'register');
     if (this.state === State.REGISTERED)
       throw new Error('Only one provider can be registered per plugin.');
     this.state = State.REGISTERED;
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.ts b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.ts
index 4b34d56..0c2f412 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.ts
@@ -18,6 +18,8 @@
   EventHelperPluginApi,
   UnsubscribeCallback,
 } from '../../../api/event-helper';
+import {PluginApi} from '../../../api/plugin';
+import {appContext} from '../../../services/app-context';
 
 export interface ListenOptions {
   event?: string;
@@ -25,13 +27,18 @@
 }
 
 export class GrEventHelper implements EventHelperPluginApi {
-  constructor(readonly element: HTMLElement) {}
+  private readonly reporting = appContext.reportingService;
+
+  constructor(readonly plugin: PluginApi, readonly element: HTMLElement) {
+    this.reporting.trackApi(this.plugin, 'event', 'constructor');
+  }
 
   /**
    * Add a callback to arbitrary event.
    * The callback may return false to prevent event bubbling.
    */
   on(event: string, callback: (event: Event) => boolean) {
+    this.reporting.trackApi(this.plugin, 'event', 'on');
     return this._listen(this.element, callback, {event});
   }
 
@@ -39,6 +46,7 @@
    * Alias for @see onClick
    */
   onTap(callback: (event: Event) => boolean) {
+    this.reporting.trackApi(this.plugin, 'event', 'onTap');
     return this.onClick(callback);
   }
 
@@ -47,6 +55,7 @@
    * The callback may return false to prevent event bubbling.
    */
   onClick(callback: (event: Event) => boolean) {
+    this.reporting.trackApi(this.plugin, 'event', 'onClick');
     return this._listen(this.element, callback);
   }
 
@@ -54,6 +63,7 @@
    * Alias for @see captureClick
    */
   captureTap(callback: (event: Event) => boolean) {
+    this.reporting.trackApi(this.plugin, 'event', 'captureTap');
     return this.captureClick(callback);
   }
 
@@ -64,6 +74,7 @@
    * The callback may return false to cancel regular event listeners.
    */
   captureClick(callback: (event: Event) => boolean) {
+    this.reporting.trackApi(this.plugin, 'event', 'captureClick');
     const parent = this.element.parentElement!;
     return this._listen(parent, callback, {capture: true});
   }
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js
index 25c0d43..547b575 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js
@@ -17,8 +17,8 @@
 
 import '../../../test/common-test-setup-karma.js';
 import {addListener} from '@polymer/polymer/lib/utils/gestures.js';
-import {GrEventHelper} from './gr-event-helper.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
+import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
 
 Polymer({
   is: 'gr-event-helper-some-element',
@@ -33,13 +33,18 @@
 
 const basicFixture = fixtureFromElement('gr-event-helper-some-element');
 
+const pluginApi = _testOnly_initGerritPluginApi();
+
 suite('gr-event-helper tests', () => {
   let element;
   let instance;
 
   setup(() => {
+    let plugin;
+    pluginApi.install(p => { plugin = p; }, '0.1',
+        'http://test.com/plugins/testplugin/static/test.js');
     element = basicFixture.instantiate();
-    instance = new GrEventHelper(element);
+    instance = plugin.eventHelper(element);
   });
 
   test('onTap()', done => {
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts
index dcabc80..13d18b5 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts
@@ -19,6 +19,7 @@
 import {GrPluginPopup} from './gr-plugin-popup';
 import {PluginApi} from '../../../api/plugin';
 import {PopupPluginApi} from '../../../api/popup';
+import {appContext} from '../../../services/app-context';
 
 interface CustomPolymerPluginEl extends HTMLElement {
   plugin: PluginApi;
@@ -35,10 +36,14 @@
 
   private popup: GrPluginPopup | null = null;
 
+  private readonly reporting = appContext.reportingService;
+
   constructor(
     readonly plugin: PluginApi,
     private moduleName: string | null = null
-  ) {}
+  ) {
+    this.reporting.trackApi(this.plugin, 'popup', 'constructor');
+  }
 
   _getElement() {
     // TODO(TS): maybe consider removing this if no one is using
@@ -52,6 +57,7 @@
    * if it was provided with constructor.
    */
   open(): Promise<PopupPluginApi> {
+    this.reporting.trackApi(this.plugin, 'popup', 'open');
     if (!this.openingPromise) {
       this.openingPromise = this.plugin
         .hook('plugin-overlay')
@@ -76,6 +82,7 @@
    * Hides the popup.
    */
   close() {
+    this.reporting.trackApi(this.plugin, 'popup', 'close');
     if (!this.popup) {
       return;
     }
diff --git a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.ts b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.ts
index 0418edb..51e9112 100644
--- a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.ts
@@ -19,6 +19,7 @@
 import {PluginApi} from '../../../api/plugin';
 import {RepoCommandCallback, RepoPluginApi} from '../../../api/repo';
 import {HookApi} from '../../../api/hook';
+import {appContext} from '../../../services/app-context';
 
 /**
  * Parameters provided on repo-command endpoint
@@ -31,7 +32,11 @@
 export class GrRepoApi implements RepoPluginApi {
   private hook?: HookApi;
 
-  constructor(readonly plugin: PluginApi) {}
+  private readonly reporting = appContext.reportingService;
+
+  constructor(readonly plugin: PluginApi) {
+    this.reporting.trackApi(this.plugin, 'repo', 'constructor');
+  }
 
   // TODO(TS): should mark as public since used in gr-change-metadata-api
   _createHook(title: string) {
@@ -43,6 +48,7 @@
   }
 
   createCommand(title: string, callback: RepoCommandCallback) {
+    this.reporting.trackApi(this.plugin, 'repo', 'createCommand');
     if (this.hook) {
       console.warn('Already set up.');
       return this;
@@ -57,6 +63,7 @@
   }
 
   onTap(callback: (event: Event) => boolean) {
+    this.reporting.trackApi(this.plugin, 'repo', 'onTap');
     if (!this.hook) {
       console.warn('Call createCommand first.');
       return this;
diff --git a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.ts b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.ts
index 4bdd40e..3f75c0a 100644
--- a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.ts
@@ -18,6 +18,7 @@
 import '../../settings/gr-settings-view/gr-settings-menu-item';
 import {PluginApi} from '../../../api/plugin';
 import {SettingsPluginApi} from '../../../api/settings';
+import {appContext} from '../../../services/app-context';
 
 export class GrSettingsApi implements SettingsPluginApi {
   private _token: string;
@@ -26,27 +27,34 @@
 
   private _moduleName?: string;
 
+  private readonly reporting = appContext.reportingService;
+
   constructor(readonly plugin: PluginApi) {
+    this.reporting.trackApi(this.plugin, 'settings', 'constructor');
     // Generate default screen URL token, specific to plugin, and unique(ish).
     this._token = plugin.getPluginName() + Math.random().toString(36).substr(5);
   }
 
   title(newTitle: string) {
+    this.reporting.trackApi(this.plugin, 'settings', 'title');
     this._title = newTitle;
     return this;
   }
 
   token(newToken: string) {
+    this.reporting.trackApi(this.plugin, 'settings', 'token');
     this._token = newToken;
     return this;
   }
 
   module(newModuleName: string) {
+    this.reporting.trackApi(this.plugin, 'settings', 'module');
     this._moduleName = newModuleName;
     return this;
   }
 
   build() {
+    this.reporting.trackApi(this.plugin, 'settings', 'build');
     if (!this._moduleName) {
       throw new Error('Settings screen custom element not defined!');
     }
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.ts b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.ts
index a91b8d3..9a15bf5 100644
--- a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.ts
@@ -15,6 +15,8 @@
  * limitations under the License.
  */
 import {StyleObject, StylesPluginApi} from '../../../api/styles';
+import {appContext} from '../../../services/app-context';
+import {PluginApi} from '../../../api/plugin';
 
 /**
  * @fileoverview We should consider dropping support for this API:
@@ -77,10 +79,17 @@
  * TODO(TS): move to util
  */
 export class GrStylesApi implements StylesPluginApi {
+  private readonly reporting = appContext.reportingService;
+
+  constructor(readonly plugin: PluginApi) {
+    this.reporting.trackApi(this.plugin, 'styles', 'constructor');
+  }
+
   /**
    * Creates a new GrStyleObject with specified style properties.
    */
   css(ruleStr: string) {
+    this.reporting.trackApi(this.plugin, 'styles', 'css');
     return new GrStyleObject(ruleStr);
   }
 }
diff --git a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.ts b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.ts
index 894ec6c..c7be7f6 100644
--- a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.ts
@@ -18,14 +18,20 @@
 import {GrCustomPluginHeader} from './gr-custom-plugin-header';
 import {PluginApi} from '../../../api/plugin';
 import {ThemePluginApi} from '../../../api/theme';
+import {appContext} from '../../../services/app-context';
 
 /**
  * Defines api for theme, can be used to set header logo and title.
  */
 export class GrThemeApi implements ThemePluginApi {
-  constructor(private readonly plugin: PluginApi) {}
+  private readonly reporting = appContext.reportingService;
+
+  constructor(private readonly plugin: PluginApi) {
+    this.reporting.trackApi(this.plugin, 'theme', 'constructor');
+  }
 
   setHeaderLogoAndTitle(logoUrl: string, title: string) {
+    this.reporting.trackApi(this.plugin, 'theme', 'setHeaderLogoAndTitle');
     this.plugin.hook('header-title', {replace: true}).onAttached(element => {
       const customHeader: GrCustomPluginHeader = document.createElement(
         'gr-custom-plugin-header'
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts
index a3d038d..857d079 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts
@@ -41,10 +41,12 @@
   private readonly reporting = appContext.reportingService;
 
   constructor(private readonly plugin: PluginApi) {
+    this.reporting.trackApi(this.plugin, 'annotation', 'constructor');
     plugin.on(EventType.ANNOTATE_DIFF, this);
   }
 
   setLayer(annotationCallback: AnnotationCallback) {
+    this.reporting.trackApi(this.plugin, 'annotation', 'setLayer');
     if (this.annotationCallback) {
       console.warn('Overwriting an existing plugin annotation layer.');
     }
@@ -55,6 +57,7 @@
   setCoverageProvider(
     coverageProvider: CoverageProvider
   ): GrAnnotationActionsInterface {
+    this.reporting.trackApi(this.plugin, 'annotation', 'setCoverageProvider');
     if (this.coverageProvider) {
       console.warn('Overwriting an existing coverage provider.');
     }
@@ -74,6 +77,7 @@
     checkboxLabel: string,
     onAttached: (checkboxEl: Element | null) => void
   ) {
+    this.reporting.trackApi(this.plugin, 'annotation', 'enableToggleCheckbox');
     this.plugin.hook('annotation-toggler').onAttached(element => {
       if (!element.content) {
         this.reporting.error(new Error('plugin endpoint without content.'));
@@ -104,6 +108,7 @@
   }
 
   notify(path: string, start: number, end: number, side: Side) {
+    this.reporting.trackApi(this.plugin, 'annotation', 'notify');
     for (const annotationLayer of this.annotationLayers) {
       // Notify only the annotation layer that is associated with the specified
       // path.
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts
index a4c6974..15d4680 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts
@@ -68,7 +68,10 @@
 
   ActionType = ActionType;
 
+  private readonly reporting = appContext.reportingService;
+
   constructor(public plugin: PluginApi, el?: GrChangeActionsElement) {
+    this.reporting.trackApi(this.plugin, 'actions', 'constructor');
     this.setEl(el);
   }
 
@@ -100,6 +103,7 @@
   }
 
   addPrimaryActionKey(key: PrimaryActionKey) {
+    this.reporting.trackApi(this.plugin, 'actions', 'addPrimaryActionKey');
     const el = this.ensureEl();
     if (el.primaryActionKeys.includes(key)) {
       return;
@@ -109,63 +113,77 @@
   }
 
   removePrimaryActionKey(key: string) {
+    this.reporting.trackApi(this.plugin, 'actions', 'removePrimaryActionKey');
     const el = this.ensureEl();
     el.primaryActionKeys = el.primaryActionKeys.filter(k => k !== key);
   }
 
   hideQuickApproveAction() {
+    this.reporting.trackApi(this.plugin, 'actions', 'hideQuickApproveAction');
     this.ensureEl().hideQuickApproveAction();
   }
 
   setActionOverflow(type: ActionType, key: string, overflow: boolean) {
+    this.reporting.trackApi(this.plugin, 'actions', 'setActionOverflow');
     // TODO(TS): remove return, unclear why it was written
     return this.ensureEl().setActionOverflow(type, key, overflow);
   }
 
   setActionPriority(type: ActionType, key: string, priority: ActionPriority) {
+    this.reporting.trackApi(this.plugin, 'actions', 'setActionPriority');
     // TODO(TS): remove return, unclear why it was written
     return this.ensureEl().setActionPriority(type, key, priority);
   }
 
   setActionHidden(type: ActionType, key: string, hidden: boolean) {
+    this.reporting.trackApi(this.plugin, 'actions', 'setActionHidden');
     // TODO(TS): remove return, unclear why it was written
     return this.ensureEl().setActionHidden(type, key, hidden);
   }
 
   add(type: ActionType, label: string): string {
+    this.reporting.trackApi(this.plugin, 'actions', 'add');
     return this.ensureEl().addActionButton(type, label);
   }
 
   remove(key: string) {
+    this.reporting.trackApi(this.plugin, 'actions', 'remove');
     // TODO(TS): remove return, unclear why it was written
     return this.ensureEl().removeActionButton(key);
   }
 
   addTapListener(key: string, handler: EventListenerOrEventListenerObject) {
+    this.reporting.trackApi(this.plugin, 'actions', 'addTapListener');
     this.ensureEl().addEventListener(key + '-tap', handler);
   }
 
   removeTapListener(key: string, handler: EventListenerOrEventListenerObject) {
+    this.reporting.trackApi(this.plugin, 'actions', 'removeTapListener');
     this.ensureEl().removeEventListener(key + '-tap', handler);
   }
 
   setLabel(key: string, text: string) {
+    this.reporting.trackApi(this.plugin, 'actions', 'setLabel');
     this.ensureEl().setActionButtonProp(key, 'label', text);
   }
 
   setTitle(key: string, text: string) {
+    this.reporting.trackApi(this.plugin, 'actions', 'setTitle');
     this.ensureEl().setActionButtonProp(key, 'title', text);
   }
 
   setEnabled(key: string, enabled: boolean) {
+    this.reporting.trackApi(this.plugin, 'actions', 'setEnabled');
     this.ensureEl().setActionButtonProp(key, 'enabled', enabled);
   }
 
   setIcon(key: string, icon: string) {
+    this.reporting.trackApi(this.plugin, 'actions', 'setIcon');
     this.ensureEl().setActionButtonProp(key, 'icon', icon);
   }
 
   getActionDetails(action: string) {
+    this.reporting.trackApi(this.plugin, 'actions', 'getActionDetails');
     const el = this.ensureEl();
     return (
       el.getActionDetails(action) ||
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.ts
index effebe1..de57794 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.ts
@@ -25,15 +25,20 @@
   ReplyChangedCallback,
   ValueChangedDetail,
 } from '../../../api/change-reply';
+import {appContext} from '../../../services/app-context';
 
 /**
  * GrChangeReplyInterface, provides a set of handy methods on reply dialog.
  */
 export class GrChangeReplyInterface implements ChangeReplyPluginApi {
+  private readonly reporting = appContext.reportingService;
+
   constructor(
     readonly plugin: PluginApi,
     readonly sharedApiElement: JsApiService
-  ) {}
+  ) {
+    this.reporting.trackApi(this.plugin, 'reply', 'constructor');
+  }
 
   get _el(): GrReplyDialog {
     return (this.sharedApiElement.getElement(
@@ -42,18 +47,22 @@
   }
 
   getLabelValue(label: string): string {
+    this.reporting.trackApi(this.plugin, 'reply', 'getLabelValue');
     return this._el.getLabelValue(label);
   }
 
   setLabelValue(label: string, value: string) {
+    this.reporting.trackApi(this.plugin, 'reply', 'setLabelValue');
     this._el.setLabelValue(label, value);
   }
 
   send(includeComments?: boolean) {
+    this.reporting.trackApi(this.plugin, 'reply', 'send');
     this._el.send(includeComments);
   }
 
   addReplyTextChangedCallback(handler: ReplyChangedCallback) {
+    this.reporting.trackApi(this.plugin, 'reply', 'addReplyTextChangedCb');
     const hookApi = this.plugin.hook('reply-text');
     const registeredHandler = (e: Event) => {
       const ce = e as CustomEvent<ValueChangedDetail>;
@@ -74,6 +83,7 @@
   }
 
   addLabelValuesChangedCallback(handler: LabelsChangedCallback) {
+    this.reporting.trackApi(this.plugin, 'reply', 'addLabelValuesChangedCb');
     const hookApi = this.plugin.hook('reply-label-scores');
     const registeredHandler = (e: Event) => {
       const ce = e as CustomEvent<LabelsChangedDetail>;
@@ -95,6 +105,7 @@
   }
 
   showMessage(message: string) {
+    this.reporting.trackApi(this.plugin, 'reply', 'showMessage');
     this._el.setPluginMessage(message);
   }
 }
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.ts
index cd35d4e..150c45a 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.ts
@@ -18,6 +18,7 @@
 import {RequestPayload} from '../../../types/common';
 import {appContext} from '../../../services/app-context';
 import {ErrorCallback, RestPluginApi} from '../../../api/rest';
+import {PluginApi} from '../../../api/plugin';
 
 async function getErrorMessage(response: Response): Promise<string> {
   const text = await response.text();
@@ -36,33 +37,44 @@
 export class GrPluginRestApi implements RestPluginApi {
   private readonly restApi = appContext.restApiService;
 
-  constructor(private readonly prefix = '') {}
+  private readonly reporting = appContext.reportingService;
+
+  constructor(readonly plugin: PluginApi, private readonly prefix = '') {
+    this.reporting.trackApi(this.plugin, 'rest', 'constructor');
+  }
 
   getLoggedIn() {
+    this.reporting.trackApi(this.plugin, 'rest', 'getLoggedIn');
     return this.restApi.getLoggedIn();
   }
 
   getVersion() {
+    this.reporting.trackApi(this.plugin, 'rest', 'getVersion');
     return this.restApi.getVersion();
   }
 
   getConfig() {
+    this.reporting.trackApi(this.plugin, 'rest', 'getConfig');
     return this.restApi.getConfig();
   }
 
   invalidateReposCache() {
+    this.reporting.trackApi(this.plugin, 'rest', 'invalidateReposCache');
     this.restApi.invalidateReposCache();
   }
 
   getAccount() {
+    this.reporting.trackApi(this.plugin, 'rest', 'getAccount');
     return this.restApi.getAccount();
   }
 
   getAccountCapabilities(capabilities: string[]) {
+    this.reporting.trackApi(this.plugin, 'rest', 'getAccountCapabilities');
     return this.restApi.getAccountCapabilities(capabilities);
   }
 
   getRepos(filter: string, reposPerPage: number, offset?: number) {
+    this.reporting.trackApi(this.plugin, 'rest', 'getRepos');
     return this.restApi.getRepos(filter, reposPerPage, offset);
   }
 
@@ -100,6 +112,7 @@
     errFn?: ErrorCallback,
     contentType?: string
   ): Promise<Response | void> {
+    this.reporting.trackApi(this.plugin, 'rest', 'fetch');
     return this.restApi.send(
       method,
       this.prefix + url,
@@ -119,6 +132,7 @@
     errFn?: ErrorCallback,
     contentType?: string
   ) {
+    this.reporting.trackApi(this.plugin, 'rest', 'send');
     // Plugins typically don't want Gerrit to show error dialogs for failed
     // requests. So we are defining a default errFn here, even if it is not
     // explicitly set by the caller.
@@ -167,6 +181,7 @@
   }
 
   get(url: string) {
+    this.reporting.trackApi(this.plugin, 'rest', 'get');
     return this.send(HttpMethod.GET, url);
   }
 
@@ -176,6 +191,7 @@
     errFn?: ErrorCallback,
     contentType?: string
   ) {
+    this.reporting.trackApi(this.plugin, 'rest', 'post');
     return this.send(HttpMethod.POST, url, payload, errFn, contentType);
   }
 
@@ -185,10 +201,12 @@
     errFn?: ErrorCallback,
     contentType?: string
   ) {
+    this.reporting.trackApi(this.plugin, 'rest', 'put');
     return this.send(HttpMethod.PUT, url, payload, errFn, contentType);
   }
 
   delete(url: string) {
+    this.reporting.trackApi(this.plugin, 'rest', 'delete');
     return this.fetch(HttpMethod.DELETE, url).then(response => {
       if (response.status !== 204) {
         return response.text().then(text => {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts
index e7843af..68fb96b 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts
@@ -54,6 +54,7 @@
 import {ChangeReplyPluginApi} from '../../../api/change-reply';
 import {RestPluginApi} from '../../../api/rest';
 import {HookApi, RegisterOptions} from '../../../api/hook';
+import {AttributeHelperPluginApi} from '../../../api/attribute-helper';
 
 /**
  * Plugin-provided custom components can affect content in extension
@@ -84,6 +85,8 @@
 
   private readonly jsApi = appContext.jsApiService;
 
+  private readonly report = appContext.reportingService;
+
   constructor(url?: string) {
     this.domHooks = new GrDomHooksManager(this);
 
@@ -97,6 +100,7 @@
 
     this._url = new URL(url);
     this._name = getPluginNameFromUrl(this._url) ?? 'NULL';
+    this.report.trackApi(this, 'plugin', 'constructor');
   }
 
   getPluginName() {
@@ -104,6 +108,7 @@
   }
 
   registerStyleModule(endpoint: string, moduleName: string) {
+    this.report.trackApi(this, 'plugin', 'registerStyleModule');
     getPluginEndpoints().registerModule(this, {
       endpoint,
       type: EndpointType.STYLE,
@@ -119,6 +124,7 @@
     moduleName?: string,
     options?: RegisterOptions
   ): HookApi {
+    this.report.trackApi(this, 'plugin', 'registerCustomComponent');
     return this._registerCustomComponent(endpointName, moduleName, options);
   }
 
@@ -133,6 +139,7 @@
     moduleName?: string,
     options?: RegisterOptions
   ): HookApi {
+    this.report.trackApi(this, 'plugin', 'registerDynamicCustomComponent');
     const fullEndpointName = `${endpointName}-${this.getPluginName()}`;
     return this._registerCustomComponent(
       fullEndpointName,
@@ -169,19 +176,23 @@
    * element for the first call.
    */
   hook(endpointName: string, options?: RegisterOptions) {
+    this.report.trackApi(this, 'plugin', 'hook');
     return this.registerCustomComponent(endpointName, undefined, options);
   }
 
   getServerInfo() {
+    this.report.trackApi(this, 'plugin', 'getServerInfo');
     return appContext.restApiService.getConfig();
   }
 
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   on(eventName: EventType, callback: (...args: any[]) => any) {
+    this.report.trackApi(this, 'plugin', 'on');
     this.jsApi.addEventCallback(eventName, callback);
   }
 
   url(path?: string) {
+    this.report.trackApi(this, 'plugin', 'url');
     if (!this._url) throw new Error('plugin url not set');
     const relPath = '/plugins/' + this._name + (path || '/');
     const sameOriginPath = window.location.origin + `${getBaseUrl()}${relPath}`;
@@ -200,6 +211,7 @@
   }
 
   screenUrl(screenName?: string) {
+    this.report.trackApi(this, 'plugin', 'screenUrl');
     const origin = location.origin;
     const base = getBaseUrl();
     const tokenPart = screenName ? '/' + screenName : '';
@@ -216,21 +228,25 @@
   }
 
   get(url: string, callback?: SendCallback) {
+    this.report.trackApi(this, 'plugin', 'get');
     console.warn('.get() is deprecated! Use .restApi().get()');
     return this._send(HttpMethod.GET, url, callback);
   }
 
   post(url: string, payload: RequestPayload, callback?: SendCallback) {
+    this.report.trackApi(this, 'plugin', 'post');
     console.warn('.post() is deprecated! Use .restApi().post()');
     return this._send(HttpMethod.POST, url, callback, payload);
   }
 
   put(url: string, payload: RequestPayload, callback?: SendCallback) {
+    this.report.trackApi(this, 'plugin', 'put');
     console.warn('.put() is deprecated! Use .restApi().put()');
     return this._send(HttpMethod.PUT, url, callback, payload);
   }
 
   delete(url: string, callback?: SendCallback) {
+    this.report.trackApi(this, 'plugin', 'delete');
     console.warn('.delete() is deprecated! Use plugin.restApi().delete()');
     return this.restApi()
       .delete(this.url(url))
@@ -286,19 +302,19 @@
   }
 
   styles(): StylesPluginApi {
-    return new GrStylesApi();
+    return new GrStylesApi(this);
   }
 
   restApi(prefix?: string): RestPluginApi {
-    return new GrPluginRestApi(prefix);
+    return new GrPluginRestApi(this, prefix);
   }
 
-  attributeHelper(element: HTMLElement) {
-    return new GrAttributeHelper(element);
+  attributeHelper(element: HTMLElement): AttributeHelperPluginApi {
+    return new GrAttributeHelper(this, element);
   }
 
   eventHelper(element: HTMLElement): EventHelperPluginApi {
-    return new GrEventHelper(element);
+    return new GrEventHelper(this, element);
   }
 
   popup(): Promise<PopupPluginApi>;
@@ -314,6 +330,7 @@
   }
 
   screen(screenName: string, moduleName?: string) {
+    this.report.trackApi(this, 'plugin', 'screen');
     if (moduleName && typeof moduleName !== 'string') {
       console.error(
         '.screen(pattern, callback) deprecated, use ' +
@@ -328,6 +345,7 @@
   }
 
   _getScreenName(screenName: string) {
+    this.report.trackApi(this, 'plugin', '_getScreenName');
     return `${this.getPluginName()}-screen-${screenName}`;
   }
 }
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api.ts
index d4b51a8..205f0fe 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api.ts
@@ -25,9 +25,12 @@
 export class GrReportingJsApi implements ReportingPluginApi {
   private readonly reporting = appContext.reportingService;
 
-  constructor(private readonly plugin: PluginApi) {}
+  constructor(private readonly plugin: PluginApi) {
+    this.reporting.trackApi(this.plugin, 'reporting', 'constructor');
+  }
 
   reportInteraction(eventName: string, details?: EventDetails) {
+    this.reporting.trackApi(this.plugin, 'reporting', 'reportInteraction');
     this.reporting.reportInteraction(
       `${this.plugin.getPluginName()}-${eventName}`,
       details
@@ -35,6 +38,7 @@
   }
 
   reportLifeCycle(eventName: string, details?: EventDetails) {
+    this.reporting.trackApi(this.plugin, 'reporting', 'reportLifeCycle');
     this.reporting.reportLifeCycle(
       `${this.plugin.getPluginName()}-${eventName}`,
       details
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
index 4196513..1be1d63 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
@@ -17,6 +17,7 @@
 
 import {NumericChangeId} from '../../types/common';
 import {EventDetails} from '../../api/reporting';
+import {PluginApi} from '../../api/plugin';
 
 export type EventValue = string | number | {error?: Error};
 
@@ -97,6 +98,7 @@
    * Every execution is only reported once per session.
    */
   reportExecution(id: string, details: EventDetails): void;
+  trackApi(plugin: PluginApi, object: string, method: string): void;
   reportInteraction(eventName: string, details?: EventDetails): void;
   /**
    * A draft interaction was started. Update the time-between-draft-actions
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
index 631a4e0..e57670d 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
@@ -20,6 +20,7 @@
 import {hasOwnProperty} from '../../utils/common-util';
 import {NumericChangeId} from '../../types/common';
 import {EventDetails} from '../../api/reporting';
+import {PluginApi} from '../../api/plugin';
 
 // Latency reporting constants.
 
@@ -800,10 +801,18 @@
       id,
       undefined,
       details,
-      false
+      true // skip console log
     );
   }
 
+  trackApi(plugin: PluginApi, object: string, method: string) {
+    this.reportExecution('plugin-api', {
+      plugin: plugin.getPluginName(),
+      object,
+      method,
+    });
+  }
+
   /**
    * A draft interaction was started. Update the time-between-draft-actions
    * timer.
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
index 484ce45..7d66484 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
@@ -16,6 +16,7 @@
  */
 import {ReportingService, Timer} from './gr-reporting';
 import {EventDetails} from '../../api/reporting';
+import {PluginApi} from '../../api/plugin';
 
 export class MockTimer implements Timer {
   end(): this {
@@ -67,6 +68,9 @@
   reportExecution: (id: string, details: EventDetails) => {
     log(`reportExecution '${id}': ${JSON.stringify(details)}`);
   },
+  trackApi: (plugin: PluginApi, object: string, method: string) => {
+    log(`trackApi '${plugin}', ${object}, ${method}`);
+  },
   reportExtension: () => {},
   reportInteraction: (eventName: string, details?: EventDetails) => {
     log(`reportInteraction '${eventName}': ${JSON.stringify(details)}`);