Refactoring of the Checks JavaScript plugin code

Remove `reboot`.
Rename `gr-checks.js` to `plugin.js`.
Move fetcher into `plugin.js`.
Move utility functions into separate `util.js` file.

Change-Id: I859baf98e7366e9b3297811142524e141d1bbaea
diff --git a/gr-checks/BUILD b/gr-checks/BUILD
index 8eee11e..7a1143b 100644
--- a/gr-checks/BUILD
+++ b/gr-checks/BUILD
@@ -10,5 +10,5 @@
 gerrit_js_bundle(
     name = "checks",
     srcs = glob(["*.js"]),
-    entry_point = "gr-checks.js",
+    entry_point = "plugin.js",
 )
diff --git a/gr-checks/gr-checks-reboot.js b/gr-checks/gr-checks-reboot.js
deleted file mode 100644
index 1e7bdc2..0000000
--- a/gr-checks/gr-checks-reboot.js
+++ /dev/null
@@ -1,164 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * Heads up! Everything in this file is still in flux. The new reboot checks API
- * is still in development. So everything in this file can change. And it is
- * expected that the amount of comments and tests is limited for the time being.
- */
-
-function pluralize(count, noun) {
-  if (count === 0) return '';
-  return `${count} ${noun}` + (count > 1 ? 's' : '');
-}
-
-function generateDurationString(startTime, endTime) {
-  const secondsAgo = Math.round((endTime - startTime) / 1000);
-
-  if (secondsAgo === 0) {
-    return ZERO_SECONDS;
-  }
-
-  const durationSegments = [];
-  if (secondsAgo % 60 !== 0) {
-    durationSegments.push(`${secondsAgo % 60} sec`);
-  }
-  const minutesAgo = Math.floor(secondsAgo / 60);
-  if (minutesAgo % 60 !== 0) {
-    durationSegments.push(`${minutesAgo % 60} min`);
-  }
-  const hoursAgo = Math.floor(minutesAgo / 60);
-  if (hoursAgo % 24 !== 0) {
-    const hours = pluralize(hoursAgo % 24, 'hour', 'hours');
-    durationSegments.push(`${hours}`);
-  }
-  const daysAgo = Math.floor(hoursAgo / 24);
-  if (daysAgo % 30 !== 0) {
-    const days = pluralize(daysAgo % 30, 'day', 'days');
-    durationSegments.push(`${days}`);
-  }
-  const monthsAgo = Math.floor(daysAgo / 30);
-  if (monthsAgo > 0) {
-    const months = pluralize(monthsAgo, 'month', 'months');
-    durationSegments.push(`${months}`);
-  }
-  return durationSegments.reverse().slice(0, 2).join(' ');
-}
-
-function computeDuration(check) {
-  if (!check.started || !check.finished) {
-    return '-';
-  }
-  const startTime = new Date(check.started);
-  const finishTime = check.finished ? new Date(check.finished) : new Date();
-  return generateDurationString(startTime, finishTime);
-}
-
-export class RebootFetcher {
-  constructor(restApi) {
-    this.restApi = restApi;
-  }
-
-  async fetchCurrent() {
-    return this.fetch(this.changeNumber, this.patchsetNumber);
-  }
-
-  async fetch(changeData) {
-    const {changeNumber, patchsetNumber} = changeData;
-    this.changeNumber = changeNumber;
-    this.patchsetNumber = patchsetNumber;
-    const checks = await this.apiGet('?o=CHECKER');
-    return {
-      responseCode: 'OK',
-      runs: checks.map(check => this.convert(check)),
-    };
-  }
-
-  async apiGet(suffix) {
-    return this.restApi.get(
-        '/changes/' + this.changeNumber + '/revisions/' + this.patchsetNumber
-        + '/checks' + suffix);
-  }
-
-  async apiPost(suffix) {
-    return this.restApi.post(
-        '/changes/' + this.changeNumber + '/revisions/' + this.patchsetNumber
-        + '/checks' + suffix);
-  }
-
-  /**
-   * Converts a Checks Plugin CheckInfo object into a Reboot Checks API Run
-   * object.
-   *
-   * TODO(brohlfs): Refine this conversion and add tests.
-   */
-  convert(check) {
-    let status = 'RUNNABLE';
-    if (check.state === 'RUNNING' || check.state === 'SCHEDULED') {
-      status = 'RUNNING';
-    } else if (check.state === 'FAILED' || check.state === 'SUCCESSFUL') {
-      status = 'COMPLETED';
-    }
-    const run = {
-      checkName: check.checker_name,
-      checkDescription: check.checker_description,
-      externalId: check.checker_uuid,
-      status,
-    };
-    if (check.started) run.startedTimestamp = new Date(check.started);
-    if (check.finished) run.finishedTimestamp = new Date(check.finished);
-    if (status === 'RUNNING') {
-      run.statusDescription = check.message;
-      if (check.url) {
-        run.statusLink = check.url;
-      }
-    } else if (check.state === 'SUCCESSFUL') {
-      run.statusDescription =
-          check.message || `Passed (${computeDuration(check)})`;
-      if (check.url) {
-        run.statusLink = check.url;
-      }
-    } else if (check.state === 'FAILED') {
-      run.results = [{
-        category: 'ERROR',
-        summary: check.message || `Failed (${computeDuration(check)})`,
-      }];
-      if (check.url) {
-        run.results[0].links = [{
-          url: check.url,
-          primary: true,
-          icon: 'EXTERNAL',
-        }];
-      }
-    }
-    if (status !== 'RUNNING') {
-      run.actions = [{
-        name: 'Run',
-        primary: true,
-        callback: () => this.run(check.checker_uuid),
-      }];
-    }
-    return run;
-  }
-
-  run(uuid) {
-    return this.apiPost('/' + uuid + '/rerun')
-        .catch(e => {
-          return {errorMessage: `Triggering the run failed: ${e.message}`};
-        });
-  }
-}
diff --git a/gr-checks/gr-checks.js b/gr-checks/gr-checks.js
deleted file mode 100644
index d72e920..0000000
--- a/gr-checks/gr-checks.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {RebootFetcher} from './gr-checks-reboot.js';
-
-Gerrit.install(plugin => {
-  const checksApi = plugin.checks();
-  const fetcher = new RebootFetcher(plugin.restApi());
-  checksApi.register({
-    fetch: data => fetcher.fetch(data),
-  });
-});
diff --git a/gr-checks/plugin.js b/gr-checks/plugin.js
new file mode 100644
index 0000000..f8a0a35
--- /dev/null
+++ b/gr-checks/plugin.js
@@ -0,0 +1,117 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {computeDuration} from './util.js';
+
+class ChecksFetcher {
+  constructor(restApi) {
+    this.restApi = restApi;
+  }
+
+  async fetchCurrent() {
+    return this.fetch(this.changeNumber, this.patchsetNumber);
+  }
+
+  async fetch(changeData) {
+    const {changeNumber, patchsetNumber} = changeData;
+    this.changeNumber = changeNumber;
+    this.patchsetNumber = patchsetNumber;
+    const checks = await this.apiGet('?o=CHECKER');
+    return {
+      responseCode: 'OK',
+      runs: checks.map(check => this.convert(check)),
+    };
+  }
+
+  async apiGet(suffix) {
+    return this.restApi.get(
+        '/changes/' + this.changeNumber + '/revisions/' + this.patchsetNumber +
+        '/checks' + suffix);
+  }
+
+  async apiPost(suffix) {
+    return this.restApi.post(
+        '/changes/' + this.changeNumber + '/revisions/' + this.patchsetNumber +
+        '/checks' + suffix);
+  }
+
+  /**
+   * Converts a Checks Plugin CheckInfo object into a Checks API Run object.
+   */
+  convert(check) {
+    let status = 'RUNNABLE';
+    if (check.state === 'RUNNING' || check.state === 'SCHEDULED') {
+      status = 'RUNNING';
+    } else if (check.state === 'FAILED' || check.state === 'SUCCESSFUL') {
+      status = 'COMPLETED';
+    }
+    const run = {
+      checkName: check.checker_name,
+      checkDescription: check.checker_description,
+      externalId: check.checker_uuid,
+      status,
+    };
+    if (check.started) run.startedTimestamp = new Date(check.started);
+    if (check.finished) run.finishedTimestamp = new Date(check.finished);
+    if (status === 'RUNNING') {
+      run.statusDescription = check.message;
+      if (check.url) {
+        run.statusLink = check.url;
+      }
+    } else if (check.state === 'SUCCESSFUL') {
+      run.statusDescription =
+          check.message || `Passed (${computeDuration(check)})`;
+      if (check.url) {
+        run.statusLink = check.url;
+      }
+    } else if (check.state === 'FAILED') {
+      run.results = [{
+        category: 'ERROR',
+        summary: check.message || `Failed (${computeDuration(check)})`,
+      }];
+      if (check.url) {
+        run.results[0].links = [{
+          url: check.url,
+          primary: true,
+          icon: 'EXTERNAL',
+        }];
+      }
+    }
+    if (status !== 'RUNNING') {
+      run.actions = [{
+        name: 'Run',
+        primary: true,
+        callback: () => this.run(check.checker_uuid),
+      }];
+    }
+    return run;
+  }
+
+  run(uuid) {
+    return this.apiPost('/' + uuid + '/rerun')
+        .catch(e => {
+          return {errorMessage: `Triggering the run failed: ${e.message}`};
+        });
+  }
+}
+
+Gerrit.install(plugin => {
+  const checksApi = plugin.checks();
+  const fetcher = new ChecksFetcher(plugin.restApi());
+  checksApi.register({
+    fetch: data => fetcher.fetch(data),
+  });
+});
diff --git a/gr-checks/util.js b/gr-checks/util.js
new file mode 100644
index 0000000..478124c
--- /dev/null
+++ b/gr-checks/util.js
@@ -0,0 +1,60 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+function pluralize(count, noun) {
+  if (count === 0) return '';
+  return `${count} ${noun}` + (count > 1 ? 's' : '');
+}
+
+function generateDurationString(startTime, endTime) {
+  const secondsAgo = Math.round((endTime - startTime) / 1000);
+  if (secondsAgo === 0) {
+    return '0 sec';
+  }
+  const durationSegments = [];
+  if (secondsAgo % 60 !== 0) {
+    durationSegments.push(`${secondsAgo % 60} sec`);
+  }
+  const minutesAgo = Math.floor(secondsAgo / 60);
+  if (minutesAgo % 60 !== 0) {
+    durationSegments.push(`${minutesAgo % 60} min`);
+  }
+  const hoursAgo = Math.floor(minutesAgo / 60);
+  if (hoursAgo % 24 !== 0) {
+    const hours = pluralize(hoursAgo % 24, 'hour', 'hours');
+    durationSegments.push(`${hours}`);
+  }
+  const daysAgo = Math.floor(hoursAgo / 24);
+  if (daysAgo % 30 !== 0) {
+    const days = pluralize(daysAgo % 30, 'day', 'days');
+    durationSegments.push(`${days}`);
+  }
+  const monthsAgo = Math.floor(daysAgo / 30);
+  if (monthsAgo > 0) {
+    const months = pluralize(monthsAgo, 'month', 'months');
+    durationSegments.push(`${months}`);
+  }
+  return durationSegments.reverse().slice(0, 2).join(' ');
+}
+
+export function computeDuration(check) {
+  if (!check.started || !check.finished) {
+    return '-';
+  }
+  const startTime = new Date(check.started);
+  const finishTime = check.finished ? new Date(check.finished) : new Date();
+  return generateDurationString(startTime, finishTime);
+}