Remove 'Binary size changes' panel

This CL makes the Checks tab the only way to view binary size changes.

Bug: 1228935
Change-Id: I5b55fe9ff25160d7817cc25a2af6c72994a5dc8c
diff --git a/README.md b/README.md
index 3449c05..ad987e0 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
 The plugin queries tryjobs for the current patchset on Gerrit, and gets the
 `binary_sizes` property from each of them. It also queries the equivalent CI
 jobs for the same base revision, and gets the same property from them. It then
-displays the results in a table, with rows being the files (keys in
+displays the results in a table under the Checks tab, with rows being the files (keys in
 `binary_sizes` dict), and the columns being:
 
 Size before | Size after | Size delta (including %) | Budget info | File | Builder
diff --git a/web/binary-size.ts b/web/binary-size.ts
index eaf93a6..9586961 100644
--- a/web/binary-size.ts
+++ b/web/binary-size.ts
@@ -5,9 +5,7 @@
  * found in the LICENSE file.
  */
 
-import {css, html, LitElement, PropertyValues} from 'lit';
-import {customElement, property, state} from 'lit/decorators';
-import {installChecksResult, DATA_SYMBOL} from './checks-result';
+import {DATA_SYMBOL} from './checks-result';
 import './binary-size-table';
 import {humanByteSizeDelta} from './binary-size-table';
 import {PluginApi} from '@gerritcodereview/typescript-api/plugin';
@@ -15,7 +13,6 @@
   ChangeInfo,
   ChangeStatus,
   PatchSetNum,
-  RevisionInfo,
   RevisionKind,
 } from '@gerritcodereview/typescript-api/rest-api';
 import {
@@ -123,226 +120,36 @@
   }
 }
 
-const DEFAULT_UPDATE_INTERVAL_MS = 1000 * 15;
-const MAX_UPDATE_INTERVAL_MS = 1000 * 60 * 5;
-
-const BUILDBUCKET_HOST = 'cr-buildbucket.appspot.com';
-
-// This is used to prevent the plugin from registering on Checks multiple times.
-let checksRegistered = false;
-
-@customElement('binary-size')
-export class BinarySize extends LitElement {
-  static override styles = css`
-    :host {
-      border-top: 1px solid #ddd;
-      display: block;
-      margin: 1em 0;
-    }
-    .container {
-      align-items: center;
-      display: flex;
-      flex-wrap: wrap;
-    }
-    .header {
-      color: var(--primary-text-color);
-      background-color: var(--table-header-background-color);
-      justify-content: space-between;
-      padding: var(--spacing-m) var(--spacing-l);
-      border-bottom: 1px solid var(--border-color);
-    }
-    .header .label {
-      font-family: var(--header-font-family);
-      font-size: var(--font-size-h3);
-      font-weight: var(--font-weight-h3);
-      line-height: var(--line-height-h3);
-      margin: 0 var(--spacing-l) 0 0;
-    }
-    .header .update {
-      color: var(--secondary-text-color);
-    }
-  `;
-
-  override render() {
-    if (!this.binarySizeInfo?.rows?.length) {
-      return;
-    }
-    return html`
-      <div class="header container">
-        <div class="container">
-          <h3 class="label">Binary size changes</h3>
-          <div class="update">${this.statusString}</div>
-        </div>
-      </div>
-      <section class="builds">
-        <binary-size-table
-          .binarySizeInfo=${this.binarySizeInfo}
-        ></binary-size-table>
-      </section>
-    `;
-  }
-
-  @property()
-  plugin!: PluginApi;
-
-  @property()
-  change!: ChangeInfo;
-
-  @property()
-  revision!: RevisionInfo;
-
-  @state()
-  binarySizeInfo: BinarySizeInfo | null = null;
-
-  @state()
-  statusString = '';
-
-  @state()
-  needsUpdate = false;
-
-  @property()
+export class ChecksFetcher {
   pluginConfig: BinarySizeConfig | null = null;
 
-  @state()
-  updateIntervalMs = DEFAULT_UPDATE_INTERVAL_MS;
+  private enabledCache: Map<string, BinarySizeConfig> = new Map();
 
-  @state()
-  updateTimeoutID: number | null = null;
+  constructor(public plugin: PluginApi, private buildbucketHost: string) {}
 
-  @state()
-  validPatchNums: PatchSetNum[] = [];
-
-  @state()
-  boundListener: () => void;
-
-  override update(changedProperties: PropertyValues) {
-    if (changedProperties.has('change') || changedProperties.has('revision')) {
-      this.validPatchNums = this.computeValidPatchNums(
-        this.change,
-        this.revision._number
-      );
-      this.refresh();
-    }
-    super.update(changedProperties);
-  }
-
-  constructor() {
-    super();
-    this.boundListener = () => this.handleVisibilityChange();
-  }
-
-  override connectedCallback() {
-    super.connectedCallback();
-    document.addEventListener('visibilitychange', this.boundListener);
-    if (!this.change) {
-      console.warn('element attached without change property set.');
-      return;
-    }
-    this.reload();
-  }
-
-  override disconnectedCallback() {
-    super.disconnectedCallback();
-    document.removeEventListener('visibilitychange', this.boundListener);
-    this.clearUpdateTimeout();
-  }
-
-  private clearUpdateTimeout() {
-    if (this.updateTimeoutID) {
-      window.clearTimeout(this.updateTimeoutID);
-      this.updateTimeoutID = null;
-    }
-  }
-
-  /**
-   * Reset the state of the element and fetch and display builds.
-   */
-  async reload() {
-    const pluginName = this.plugin.getPluginName();
-    const project = this.change.project;
-
-    this.pluginConfig = await this.plugin
-      .restApi()
-      .get(`/projects/${encodeURIComponent(project)}/${pluginName}~config`);
-    if (!this.pluginConfig) {
-      console.info('binary-size plugin not configured for this project.');
-      return;
-    }
-
+  async isEnabled(project: string): Promise<boolean> {
     if (!window.buildbucket) {
       console.error(
-        'The "binary-size" plugin requires the "buildbucket"' +
-          ' plugin for searching builds. Please activate both.'
+        'The "binary-size" plugin requires the "buildbucket" plugin for ' +
+        'searching builds. Please activate both.'
       );
-      return;
+      return false;
     }
 
-    const {CacheObject, CHECKS_OPT_CACHE_KEY} = window.buildbucket;
-    const optCache = new CacheObject(CHECKS_OPT_CACHE_KEY);
-    if (optCache.read().optedIn && !checksRegistered) {
-      checksRegistered = true;
-      this.plugin.checks().register({
-        fetch: changeData => this.fetchChecks(changeData),
-      });
-      this.plugin.hook('check-result-expanded').onAttached(installChecksResult);
-    }
-
-    await this.refresh();
-  }
-
-  private handleVisibilityChange() {
-    if (!document.hidden && this.needsUpdate) {
-      this.refresh();
-      this.needsUpdate = false;
-    }
-  }
-
-  private updateTimerFired() {
-    if (document.hidden) {
-      this.needsUpdate = true;
-      return;
-    }
-    this.refresh();
-  }
-
-  /**
-   * Fetch builds from Buildbucket, update the view with information about
-   * binary size, and update the timeout depending on success/failure.
-   */
-  private async refresh() {
-    if (!this.pluginConfig) {
-      return;
-    }
-
-    const [tryBuilds, ciBuilds] = await this.tryGetNewBinarySizeBuilds(
-      this.change,
-      this.validPatchNums
-    );
-    const newBinarySizeInfo = this.processBinarySizeInfo(
-      this.pluginConfig.builders,
-      tryBuilds,
-      ciBuilds
-    );
-
-    if (newBinarySizeInfo?.rows?.length) {
-      this.binarySizeInfo = newBinarySizeInfo;
-      this.statusString = this.getStatusString(this.validPatchNums);
-      this.updateIntervalMs = DEFAULT_UPDATE_INTERVAL_MS;
-      if (checksRegistered) {
-        this.plugin.checks().announceUpdate();
+    const path =
+      `/projects/${encodeURIComponent(project)}/` +
+      `${encodeURIComponent(this.plugin.getPluginName())}~config`;
+    if (!this.enabledCache.has(path)) {
+      let config = {} as BinarySizeConfig;
+      try {
+        config = await this.plugin.restApi().get(path);
+      } catch (e) {
+        console.log(`The binary-size plugin is not enabled on ${project}`);
       }
-    } else {
-      this.updateIntervalMs = Math.min(
-        MAX_UPDATE_INTERVAL_MS,
-        (1 + Math.random()) * this.updateIntervalMs * 2
-      );
+      this.enabledCache.set(path, config);
     }
-
-    this.clearUpdateTimeout();
-    this.updateTimeoutID = window.setTimeout(
-      this.updateTimerFired.bind(this),
-      this.updateIntervalMs
-    );
+    this.pluginConfig = this.enabledCache.get(path)!;
+    return Object.keys(this.pluginConfig).length > 0;
   }
 
   /**
@@ -352,9 +159,11 @@
    *   https://gerrit.googlesource.com/gerrit/+/refs/heads/master/polygerrit-ui/app/api/checks.ts
    */
   async fetchChecks(changeData: ChangeData) {
-    const {changeNumber, patchsetNumber, changeInfo} = changeData;
+    const {changeNumber, patchsetNumber, repo, changeInfo} = changeData;
+    if (!(await this.isEnabled(repo))) {
+      return {responseCode: ResponseCode.OK};
+    }
 
-    // TODO(gavinmak): Clean up code and dedupe from refresh().
     const patchsets = this.computeValidPatchNums(
       changeInfo,
       patchsetNumber as PatchSetNum
@@ -423,7 +232,7 @@
     }
     message += 'Expand to view more.';
 
-    const runId = `//${BUILDBUCKET_HOST}/binary-size-plugin`;
+    const runId = `//${this.buildbucketHost}/binary-size-plugin`;
     return {
       responseCode: ResponseCode.OK,
       runs: [
@@ -507,7 +316,7 @@
     let ciBuilds = await this.getBuilds(
       ciBuilders,
       [],
-      this.revisionTags(tryBuildToBuilderPair)
+      this.revisionTags(tryBuildToBuilderPair, change)
     );
     ciBuilds = this.selectRelevantBuilds(ciBuilds);
 
@@ -556,12 +365,13 @@
    * of the provided builds.
    */
   revisionTags(
-    tryBuildToBuilderPair: Map<BuildbucketBuild, BuilderPair>
+    tryBuildToBuilderPair: Map<BuildbucketBuild, BuilderPair>,
+    change: ChangeInfo,
   ): BuildbucketTag[] {
     const tags: Map<string, BuildbucketTag> = new Map();
     tryBuildToBuilderPair.forEach((builderPair, build) => {
       const host = builderPair.ciBuilderGitHost || this.pluginConfig!.gitHost;
-      const project = builderPair.ciBuilderRepo || this.change.project;
+      const project = builderPair.ciBuilderRepo || change.project;
       const revision = build.output.properties.got_revision;
       const value = `commit/gitiles/${host}/${project}/+/${revision}`;
       tags.set(value, {
@@ -586,7 +396,7 @@
     ) {
       return [];
     }
-    const bb = new window.buildbucket.BuildbucketV2Client(BUILDBUCKET_HOST);
+    const bb = new window.buildbucket.BuildbucketV2Client(this.buildbucketHost);
     const fields = [
       'builder',
       'id',
@@ -799,23 +609,6 @@
   }
 
   /**
-   * Construct a description string for the given patchset numbers.
-   */
-  getStatusString(validPatchNums: PatchSetNum[]): string {
-    let patchStatus = '';
-    if (validPatchNums) {
-      if (validPatchNums.length === 1) {
-        patchStatus = `Showing results from patch set ${validPatchNums[0]}. `;
-      } else {
-        const begin = Math.min(...(validPatchNums as number[]));
-        const end = Math.max(...(validPatchNums as number[]));
-        patchStatus = `Showing results from patch sets ${begin}-${end}. `;
-      }
-    }
-    return `${patchStatus}Last updated at ${new Date().toLocaleTimeString()}`;
-  }
-
-  /**
    * List numbers of patchsets (revisions) that are applicable.
    *
    * The reason why this is not just the current patchset number is because
diff --git a/web/binary-size_test.ts b/web/binary-size_test.ts
index 1dd323e..75af3cb 100644
--- a/web/binary-size_test.ts
+++ b/web/binary-size_test.ts
@@ -7,7 +7,7 @@
 
 import './test/test-setup';
 import {
-  BinarySize,
+  ChecksFetcher,
   BuildbucketBuild,
   BuildbucketBuilder,
   BinarySizeRow,
@@ -21,9 +21,10 @@
   RevisionKind,
 } from '@gerritcodereview/typescript-api/rest-api';
 import {Category, ChangeData} from '@gerritcodereview/typescript-api/checks';
+import {PluginApi} from '@gerritcodereview/typescript-api/plugin';
 
 suite('binary-size basic tests', () => {
-  let element: BinarySize;
+  let fetcher: ChecksFetcher;
 
   function stubSearch(searchPromise: object) {
     class MockBuildbucketV2Client {
@@ -67,75 +68,19 @@
     return sinon.spy(MockBuildbucketV2Client.prototype, 'searchBuilds');
   }
 
-  function stubReload(element: BinarySize, stub: sinon.SinonStub) {
-    class fakeCache {
-      constructor() {
-        return;
-      }
-
-      read() {
-        return {optedIn: true};
-      }
-    }
-
-    Object.assign(window.buildbucket, {
-      CacheObject: fakeCache,
-      CHECKS_OPT_CACHE_KEY: 'foo',
-    });
-
-    Object.assign(element, {
-      plugin: {
-        getPluginName: () => 'binary-size',
-        restApi: () => {
-          return {
-            get: () =>
-              Promise.resolve({
-                builders: [],
-                _account_id: 123,
-              }),
-          };
-        },
-        checks: () => {
-          return {register: stub};
-        },
-        hook: () => {
-          return {onAttached: () => {}};
-        },
-      },
-    });
-  }
-
   setup(() => {
-    element = new BinarySize();
-    Object.assign(element, {
-      plugin: {
-        getPluginName() {
-          return 'binary-size';
-        },
-        restApi() {
-          return {
-            getLoggedIn: async () => true,
-          };
-        },
+    const plugin = {
+      getPluginName() {
+        return 'binary-size';
       },
-      change: {
-        project: 'project-foo',
-        _number: 1,
-        revisions: {
-          deadbeef: {
-            _number: 1,
-          },
-        },
+      restApi() {
+        return {
+          getLoggedIn: async () => true,
+        };
       },
-      pluginConfig: {
-        gerritHost: 'gerrit.example.com',
-        buckets: [],
-        builders: [],
-      },
-      revision: {
-        _number: 1,
-      },
-    });
+    };
+
+    fetcher = new ChecksFetcher(plugin as PluginApi, 'host');
   });
 
   teardown(() => {
@@ -148,7 +93,7 @@
       {project: 'project', bucket: 'some.bucket', builder: 'builder1'},
       {project: 'project', bucket: 'a.b.c', builder: 'builder2'},
     ];
-    assert.deepEqual(await element.getBuilds(builders, [], []), []);
+    assert.deepEqual(await fetcher.getBuilds(builders, [], []), []);
   });
 
   test('getBuilds', async () => {
@@ -168,7 +113,7 @@
       {project: 'project', bucket: 'a.b.c', builder: 'builder2'},
     ];
     assert.deepEqual(
-      await element.getBuilds(
+      await fetcher.getBuilds(
         builders,
         [],
         [{key: 'buildset', value: 'foo/bar'}]
@@ -184,7 +129,7 @@
       {output: {}},
       {},
     ] as BuildbucketBuild[];
-    assert.deepEqual(element.selectRelevantBuilds(builds), [builds[0]]);
+    assert.deepEqual(fetcher.selectRelevantBuilds(builds), [builds[0]]);
   });
 
   test('sortUniqueInfoRows sorts', () => {
@@ -192,7 +137,7 @@
       {builder: 'abc', binary: 'b.so', id: '2'},
       {builder: 'abc', binary: 'a.so', id: '1'},
     ] as BinarySizeRow[];
-    assert.deepEqual(element.sortUniqueInfoRows(rows), [rows[1], rows[0]]);
+    assert.deepEqual(fetcher.sortUniqueInfoRows(rows), [rows[1], rows[0]]);
   });
 
   test('sortUniqueInfoRows keeps only latest', () => {
@@ -202,11 +147,11 @@
       {builder: 'abc', binary: 'a.so', id: '1'},
     ] as BinarySizeRow[];
     // Remember: build IDs are monotonically decreasing.
-    assert.deepEqual(element.sortUniqueInfoRows(rows), [rows[2], rows[1]]);
+    assert.deepEqual(fetcher.sortUniqueInfoRows(rows), [rows[2], rows[1]]);
   });
 
   test('getUniqueTryAndCiBuilders', () => {
-    element.pluginConfig = {
+    fetcher.pluginConfig = {
       builders: [
         {
           tryBucket: 'luci.project.try',
@@ -223,7 +168,7 @@
         },
       ],
     } as BinarySizeConfig;
-    assert.deepEqual(element.getUniqueTryAndCiBuilders(), [
+    assert.deepEqual(fetcher.getUniqueTryAndCiBuilders(), [
       [
         {bucket: 'luci.project.try', builder: 'builder1'},
         {bucket: 'luci.project.try', builder: 'builder2'},
@@ -233,8 +178,8 @@
   });
 
   test('revisionTags', () => {
-    element.change = {project: 'src'} as ChangeInfo;
-    element.pluginConfig = {gitHost: 'host'} as BinarySizeConfig;
+    fetcher.pluginConfig = {gitHost: 'host'} as BinarySizeConfig;
+    const change = {project: 'src'} as ChangeInfo;
     const tryBuildToBuilderPair = new Map([
       [{output: {properties: {got_revision: 'beef'}}}, {ciBuilderRepo: ''}],
       [{output: {properties: {got_revision: 'a3ee'}}}, {ciBuilderRepo: ''}],
@@ -242,7 +187,7 @@
       [{output: {properties: {got_revision: 'a3ee'}}}, {ciBuilderRepo: 'src/other'}],
       [{output: {properties: {got_revision: 'a3ee'}}}, {ciBuilderGitHost: 'other-host'}],
     ]) as Map<BuildbucketBuild, BuilderPair>;
-    assert.deepEqual(element.revisionTags(tryBuildToBuilderPair), [
+    assert.deepEqual(fetcher.revisionTags(tryBuildToBuilderPair, change), [
       {key: 'buildset', value: 'commit/gitiles/host/src/+/beef'},
       {key: 'buildset', value: 'commit/gitiles/host/src/+/a3ee'},
       {key: 'buildset', value: 'commit/gitiles/host/src/other/+/a3ee'},
@@ -274,7 +219,7 @@
 
   test('binarySizeInfo basic', () => {
     assert.deepEqual(
-      element.processBinarySizeInfo(
+      fetcher.processBinarySizeInfo(
         BUILDERS,
         [
           {
@@ -334,7 +279,7 @@
 
   test('binarySizeInfo with no budgets', () => {
     assert.deepEqual(
-      element.processBinarySizeInfo(
+      fetcher.processBinarySizeInfo(
         BUILDERS,
         [
           {
@@ -392,7 +337,7 @@
 
   test('binarySizeInfo with exceeded budget', () => {
     assert.deepEqual(
-      element.processBinarySizeInfo(
+      fetcher.processBinarySizeInfo(
         BUILDERS,
         [
           {
@@ -452,7 +397,7 @@
 
   test('binarySizeInfo with creep budget', () => {
     assert.deepEqual(
-      element.processBinarySizeInfo(
+      fetcher.processBinarySizeInfo(
         BUILDERS,
         [
           {
@@ -514,7 +459,7 @@
 
   test('binarySizeInfo with exceeded creep budget', () => {
     assert.deepEqual(
-      element.processBinarySizeInfo(
+      fetcher.processBinarySizeInfo(
         BUILDERS,
         [
           {
@@ -576,7 +521,7 @@
 
   test('binarySizeInfo no match on bucket', () => {
     assert.deepEqual(
-      element.processBinarySizeInfo(
+      fetcher.processBinarySizeInfo(
         BUILDERS,
         [
           {
@@ -625,7 +570,7 @@
 
   test('binarySizeInfo no match on binary', () => {
     assert.deepEqual(
-      element.processBinarySizeInfo(
+      fetcher.processBinarySizeInfo(
         BUILDERS,
         [
           {
@@ -669,7 +614,7 @@
   });
 
   test('binarySizeInfo multiple try builds', () => {
-    const result = element.processBinarySizeInfo(
+    const result = fetcher.processBinarySizeInfo(
       BUILDERS,
       [
         {
@@ -757,35 +702,6 @@
     assert.equal(result[1].ciSize, 2223);
   });
 
-  test('reload registers checks only once', async () => {
-    const stub = sinon.stub();
-    stubReload(element, stub);
-    // Wrap in try/catch since we're only testing checks registration.
-    try {
-      await element.reload();
-    } catch (e) {
-      // Continue regardless of error
-    }
-    sinon.assert.calledOnce(stub);
-
-    try {
-      await element.reload();
-    } catch (e) {
-      // Continue regardless of error
-    }
-    sinon.assert.calledOnce(stub);
-  });
-
-  test('exponential backoff', async () => {
-    assert.isNotOk(element.updateTimeoutID);
-    stubSearch(Promise.reject(new Error('nope')));
-    const timeoutMs = element.updateIntervalMs;
-    stubReload(element, sinon.stub());
-    await element.reload();
-    assert.isAbove(element.updateIntervalMs, 2 * timeoutMs);
-    assert.isOk(element.updateTimeoutID);
-  });
-
   test('valid patch numbers', () => {
     let change = {
       revisions: {
@@ -794,15 +710,15 @@
         rev3: {_number: 3, kind: RevisionKind.TRIVIAL_REBASE},
       },
     } as any; // eslint-disable-line  @typescript-eslint/no-explicit-any
-    assert.deepEqual(element.computeValidPatchNums(change, 3 as PatchSetNum), [
+    assert.deepEqual(fetcher.computeValidPatchNums(change, 3 as PatchSetNum), [
       3, 2, 1,
     ] as PatchSetNum[]);
-    assert.deepEqual(element.computeValidPatchNums(change, 2 as PatchSetNum), [
+    assert.deepEqual(fetcher.computeValidPatchNums(change, 2 as PatchSetNum), [
       2, 1,
     ] as PatchSetNum[]);
 
     change.status = ChangeStatus.MERGED;
-    assert.deepEqual(element.computeValidPatchNums(change, 3 as PatchSetNum), [
+    assert.deepEqual(fetcher.computeValidPatchNums(change, 3 as PatchSetNum), [
       2, 1,
     ] as PatchSetNum[]);
 
@@ -813,18 +729,29 @@
         rev3: {_number: 3, kind: RevisionKind.TRIVIAL_REBASE},
       },
     };
-    assert.deepEqual(element.computeValidPatchNums(change, 3 as PatchSetNum), [
+    assert.deepEqual(fetcher.computeValidPatchNums(change, 3 as PatchSetNum), [
       3, 2,
     ] as PatchSetNum[]);
   });
 
   test('fetchChecks creates results with messages and summaries', async () => {
+    sinon.stub(fetcher, 'isEnabled').returns(Promise.resolve(true));
+    fetcher.pluginConfig = {
+      builders: [
+        {
+          tryBucket: 'luci.project.try',
+          tryBuilder: 'builder1',
+          ciBucket: 'luci.project.ci',
+          ciBuilder: 'Builder 1',
+        },
+      ],
+    } as BinarySizeConfig;
     const changeData = {
       changeNumber: 1,
       patchsetNumber: 1,
       changeInfo: {},
     } as ChangeData;
-    const stub = sinon.stub(element, 'processBinarySizeInfo');
+    const stub = sinon.stub(fetcher, 'processBinarySizeInfo');
 
     // No size changes, no budgets exceeded.
     stub.returns({
@@ -834,7 +761,7 @@
         {binary: 'baz', budgetExceeded: false, trySize: 100, ciSize: 100},
       ],
     } as any); // eslint-disable-line  @typescript-eslint/no-explicit-any
-    let fetchRes = await element.fetchChecks(changeData);
+    let fetchRes = await fetcher.fetchChecks(changeData);
     let res = fetchRes.runs![0].results![0];
     assert.strictEqual(
       res.summary,
@@ -850,7 +777,7 @@
         {binary: 'baz', budgetExceeded: false, trySize: 123, ciSize: 100},
       ],
     } as any); // eslint-disable-line  @typescript-eslint/no-explicit-any
-    fetchRes = await element.fetchChecks(changeData);
+    fetchRes = await fetcher.fetchChecks(changeData);
     res = fetchRes.runs![0].results![0];
     assert.strictEqual(
       res.summary,
@@ -869,7 +796,7 @@
         {binary: 'baz', creepExceeded: true, trySize: 123, ciSize: 100},
       ],
     } as any); // eslint-disable-line  @typescript-eslint/no-explicit-any
-    fetchRes = await element.fetchChecks(changeData);
+    fetchRes = await fetcher.fetchChecks(changeData);
     res = fetchRes.runs![0].results![0];
     assert.strictEqual(
       res.summary,
@@ -883,20 +810,20 @@
 
   test('getCheckRunAttempt', () => {
     assert.deepEqual(
-      element.getCheckRunAttempt([
+      fetcher.getCheckRunAttempt([
         {builder: {builder: 'a'}},
       ] as BuildbucketBuild[]),
       1
     );
     assert.deepEqual(
-      element.getCheckRunAttempt([
+      fetcher.getCheckRunAttempt([
         {builder: {builder: 'a'}},
         {builder: {builder: 'b'}},
       ] as BuildbucketBuild[]),
       1
     );
     assert.deepEqual(
-      element.getCheckRunAttempt([
+      fetcher.getCheckRunAttempt([
         {builder: {builder: 'a'}},
         {builder: {builder: 'a'}},
         {builder: {builder: 'b'}},
@@ -904,7 +831,7 @@
       2
     );
     assert.deepEqual(
-      element.getCheckRunAttempt([
+      fetcher.getCheckRunAttempt([
         {builder: {builder: 'a'}},
         {builder: {builder: 'a'}},
         {builder: {builder: 'b'}},
@@ -922,14 +849,14 @@
       {budgetExceeded: false},
     ] as BinarySizeRow[];
     assert.deepEqual(
-      element.getCheckResultCategory(
+      fetcher.getCheckResultCategory(
         [{status: 'SUCCESS'}] as BuildbucketBuild[],
         binarySizeRows
       ),
       Category.INFO
     );
     assert.deepEqual(
-      element.getCheckResultCategory(
+      fetcher.getCheckResultCategory(
         [{status: 'FAILURE'}] as BuildbucketBuild[],
         binarySizeRows
       ),
@@ -943,14 +870,14 @@
       {budgetExceeded: false},
     ] as BinarySizeRow[];
     assert.deepEqual(
-      element.getCheckResultCategory(
+      fetcher.getCheckResultCategory(
         [{status: 'SUCCESS'}] as BuildbucketBuild[],
         binarySizeRows
       ),
       Category.WARNING
     );
     assert.deepEqual(
-      element.getCheckResultCategory(
+      fetcher.getCheckResultCategory(
         [{status: 'SUCCESS'}, {status: 'FAILURE'}] as BuildbucketBuild[],
         binarySizeRows
       ),
@@ -963,14 +890,14 @@
       {creepExceeded: false},
     ] as BinarySizeRow[];
     assert.deepEqual(
-      element.getCheckResultCategory(
+      fetcher.getCheckResultCategory(
         [{status: 'SUCCESS'}] as BuildbucketBuild[],
         binarySizeRows
       ),
       Category.INFO
     );
     assert.deepEqual(
-      element.getCheckResultCategory(
+      fetcher.getCheckResultCategory(
         [{status: 'FAILURE'}] as BuildbucketBuild[],
         binarySizeRows
       ),
@@ -984,14 +911,14 @@
       {creepExceeded: true},
     ] as BinarySizeRow[];
     assert.deepEqual(
-      element.getCheckResultCategory(
+      fetcher.getCheckResultCategory(
         [{status: 'SUCCESS'}] as BuildbucketBuild[],
         binarySizeRows
       ),
       Category.WARNING
     );
     assert.deepEqual(
-      element.getCheckResultCategory(
+      fetcher.getCheckResultCategory(
         [{status: 'SUCCESS'}, {status: 'FAILURE'}] as BuildbucketBuild[],
         binarySizeRows
       ),
@@ -1005,14 +932,14 @@
       {creepExceeded: false, budgetExceeded: false},
     ] as BinarySizeRow[];
     assert.deepEqual(
-      element.getCheckResultCategory(
+      fetcher.getCheckResultCategory(
         [{status: 'SUCCESS'}] as BuildbucketBuild[],
         binarySizeRows
       ),
       Category.INFO
     );
     assert.deepEqual(
-      element.getCheckResultCategory(
+      fetcher.getCheckResultCategory(
         [{status: 'FAILURE'}] as BuildbucketBuild[],
         binarySizeRows
       ),
@@ -1026,14 +953,14 @@
       {creepExceeded: true, budgetExceeded: false},
     ] as BinarySizeRow[];
     assert.deepEqual(
-      element.getCheckResultCategory(
+      fetcher.getCheckResultCategory(
         [{status: 'SUCCESS'}] as BuildbucketBuild[],
         binarySizeRows
       ),
       Category.WARNING
     );
     assert.deepEqual(
-      element.getCheckResultCategory(
+      fetcher.getCheckResultCategory(
         [{status: 'SUCCESS'}, {status: 'FAILURE'}] as BuildbucketBuild[],
         binarySizeRows
       ),
@@ -1047,14 +974,14 @@
       {creepExceeded: false, budgetExceeded: true},
     ] as BinarySizeRow[];
     assert.deepEqual(
-      element.getCheckResultCategory(
+      fetcher.getCheckResultCategory(
         [{status: 'SUCCESS'}] as BuildbucketBuild[],
         binarySizeRows
       ),
       Category.WARNING
     );
     assert.deepEqual(
-      element.getCheckResultCategory(
+      fetcher.getCheckResultCategory(
         [{status: 'SUCCESS'}, {status: 'FAILURE'}] as BuildbucketBuild[],
         binarySizeRows
       ),
diff --git a/web/plugin.ts b/web/plugin.ts
index b153452..7a383e1 100644
--- a/web/plugin.ts
+++ b/web/plugin.ts
@@ -6,8 +6,14 @@
  */
 
 import '@gerritcodereview/typescript-api/gerrit';
-import './binary-size';
+import {ChecksFetcher} from './binary-size';
+import {installChecksResult} from './checks-result';
 
 window.Gerrit.install(async plugin => {
-  plugin.registerCustomComponent('change-view-integration', 'binary-size');
+  const fetcher = new ChecksFetcher(plugin, 'cr-buildbucket.appspot.com');
+  console.warn('registerd twice!!!')
+  plugin.checks().register({
+    fetch: changeData => fetcher.fetchChecks(changeData),
+  });
+  plugin.hook('check-result-expanded').onAttached(installChecksResult);
 });