[status] Add gold-status-sk.

Change-Id: I8fdfb2369e574dd49f38886f85b879468e6e10d1
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/328356
Commit-Queue: Weston Tracey <westont@google.com>
Reviewed-by: Eric Boren <borenet@google.com>
diff --git a/status/modules/gold-status-sk/gold-status-sk-demo.html b/status/modules/gold-status-sk/gold-status-sk-demo.html
new file mode 100644
index 0000000..116e0a3
--- /dev/null
+++ b/status/modules/gold-status-sk/gold-status-sk-demo.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>gold-status-sk</title>
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  </head>
+  <body>
+    <h1>gold-status-sk</h1>
+    <theme-chooser-sk></theme-chooser-sk>
+    <div id="container" style="width: 200px"></div>
+  </body>
+</html>
diff --git a/status/modules/gold-status-sk/gold-status-sk-demo.ts b/status/modules/gold-status-sk/gold-status-sk-demo.ts
new file mode 100644
index 0000000..d4caf5a
--- /dev/null
+++ b/status/modules/gold-status-sk/gold-status-sk-demo.ts
@@ -0,0 +1,18 @@
+import fetchMock from 'fetch-mock';
+import './index';
+import '../../../infra-sk/modules/theme-chooser-sk';
+import { StatusResponse } from '../../../golden/modules/rpc_types';
+
+fetchMock.getOnce('https://gold.skia.org/json/v1/trstatus', <StatusResponse>{
+  corpStatus: [
+    { name: 'canvaskit', untriagedCount: 0 },
+    { name: 'colorImage', untriagedCount: 0 },
+    { name: 'gm', untriagedCount: 13 },
+    { name: 'image', untriagedCount: 0 },
+    { name: 'pathkit', untriagedCount: 0 },
+    { name: 'skp', untriagedCount: 0 },
+    { name: 'svg', untriagedCount: 27 },
+  ],
+});
+const el = document.createElement('gold-status-sk');
+document.querySelector('#container')?.appendChild(el);
diff --git a/status/modules/gold-status-sk/gold-status-sk.scss b/status/modules/gold-status-sk/gold-status-sk.scss
new file mode 100644
index 0000000..9d286d5
--- /dev/null
+++ b/status/modules/gold-status-sk/gold-status-sk.scss
@@ -0,0 +1,29 @@
+@import '../styles.scss';
+
+gold-status-sk {
+  .table {
+    width: 100%;
+  }
+
+  .tr:hover {
+    background-color: var(--primary);
+    color: var(--on-primary);
+    fill: var(--on-primary);
+
+    // Inherited color of the underline needs to be overriden so we don't end up with e.g. black
+    // text with white underline.
+    .value {
+      background-color: var(--surface);
+      color: var(--on-surface);
+      text-decoration: underline;
+    }
+  }
+
+  .value {
+    background-color: var(--secondary);
+    color: var(--on-secondary);
+    border-radius: 3px;
+    padding: 4px;
+    margin: 5px;
+  }
+}
diff --git a/status/modules/gold-status-sk/gold-status-sk.ts b/status/modules/gold-status-sk/gold-status-sk.ts
new file mode 100644
index 0000000..20f2f7d
--- /dev/null
+++ b/status/modules/gold-status-sk/gold-status-sk.ts
@@ -0,0 +1,64 @@
+/**
+ * @module modules/gold-status-sk
+ * @description <h2><code>gold-status-sk</code></h2>
+ *
+ * Custom element to display untriaged Gold iamges.
+ */
+import { define } from 'elements-sk/define';
+import { errorMessage } from 'elements-sk/errorMessage';
+import { html } from 'lit-html';
+import { ElementSk } from '../../../infra-sk/modules/ElementSk';
+import { jsonOrThrow } from 'common-sk/modules/jsonOrThrow';
+import { StatusResponse } from '../../../golden/modules/rpc_types';
+
+const goldUrl = 'https://gold.skia.org';
+
+export class GoldStatusSk extends ElementSk {
+  private resp?: StatusResponse;
+  private static template = (el: GoldStatusSk) => html`
+    <div class="table">
+      ${el.resp && el.resp.corpStatus
+        ? el.resp!.corpStatus!.map(
+            (c) => html`
+              <a
+                class="tr"
+                href="${goldUrl}${`/?corpus=${c!.name}`}"
+                target="_blank"
+                rel="noopener noreferrer"
+                title="Skia Gold: Untriaged ${c!.name} image count"
+              >
+                <div class="td">${c!.name}</div>
+                <div class="td number"><span class="value">${c!.untriagedCount}</span></div>
+              </a>
+            `
+          )
+        : html``}
+    </div>
+  `;
+
+  constructor() {
+    super(GoldStatusSk.template);
+  }
+
+  connectedCallback() {
+    super.connectedCallback();
+    this._render();
+    this.refresh();
+  }
+
+  private refresh() {
+    fetch(`${goldUrl}/json/v1/trstatus`, { method: 'GET' })
+      .then(jsonOrThrow)
+      .then((json: StatusResponse) => {
+        this.resp = json;
+        this.resp.corpStatus?.sort((a, b) => b!.untriagedCount - a!.untriagedCount);
+        this._render();
+      })
+      .catch(errorMessage)
+      .finally(() => {
+        window.setTimeout(() => this.refresh(), 60 * 1000);
+      });
+  }
+}
+
+define('gold-status-sk', GoldStatusSk);
diff --git a/status/modules/gold-status-sk/gold-status-sk_puppeteer_test.ts b/status/modules/gold-status-sk/gold-status-sk_puppeteer_test.ts
new file mode 100644
index 0000000..fa3bbeb
--- /dev/null
+++ b/status/modules/gold-status-sk/gold-status-sk_puppeteer_test.ts
@@ -0,0 +1,27 @@
+import * as path from 'path';
+import { expect } from 'chai';
+import {
+  setUpPuppeteerAndDemoPageServer,
+  takeScreenshot,
+} from '../../../puppeteer-tests/util';
+
+describe('gold-status-sk', () => {
+  const testBed = setUpPuppeteerAndDemoPageServer(
+    path.join(__dirname, '..', '..', 'webpack.config.ts')
+  );
+
+  beforeEach(async () => {
+    await testBed.page.goto(`${testBed.baseUrl}/dist/gold-status-sk.html`);
+    await testBed.page.setViewport({ width: 400, height: 550 });
+  });
+
+  it('should render the demo page (smoke test)', async () => {
+    expect(await testBed.page.$$('gold-status-sk')).to.have.length(1);
+  });
+
+  describe('screenshots', () => {
+    it('shows the default view', async () => {
+      await takeScreenshot(testBed.page, 'status', 'gold-status-sk');
+    });
+  });
+});
diff --git a/status/modules/gold-status-sk/gold-status-sk_test.ts b/status/modules/gold-status-sk/gold-status-sk_test.ts
new file mode 100644
index 0000000..ffbac5d
--- /dev/null
+++ b/status/modules/gold-status-sk/gold-status-sk_test.ts
@@ -0,0 +1,36 @@
+import './index';
+import { GoldStatusSk } from './gold-status-sk';
+import { StatusResponse } from '../../../golden/modules/rpc_types';
+
+import { setUpElementUnderTest } from '../../../infra-sk/modules/test_util';
+import { expect } from 'chai';
+import fetchMock from 'fetch-mock';
+import { $, $$ } from 'common-sk/modules/dom';
+
+describe('gold-status-sk', () => {
+  const newInstance = setUpElementUnderTest<GoldStatusSk>('gold-status-sk');
+
+  let element: GoldStatusSk;
+  beforeEach(async () => {
+    fetchMock.getOnce('https://gold.skia.org/json/v1/trstatus', <StatusResponse>{
+      corpStatus: [
+        { name: 'canvaskit', untriagedCount: 0 },
+        { name: 'colorImage', untriagedCount: 0 },
+        { name: 'gm', untriagedCount: 13 },
+        { name: 'image', untriagedCount: 0 },
+        { name: 'pathkit', untriagedCount: 0 },
+        { name: 'skp', untriagedCount: 0 },
+        { name: 'svg', untriagedCount: 27 },
+      ],
+    });
+    element = newInstance();
+    await fetchMock.flush(true);
+  });
+
+  describe('displays', () => {
+    it('untriaged', () => {
+      expect($('.tr', element)).to.have.length(7);
+      expect($$('.value', element)).to.have.property('innerText', '27');
+    });
+  });
+});
diff --git a/status/modules/gold-status-sk/index.ts b/status/modules/gold-status-sk/index.ts
new file mode 100644
index 0000000..9ef8f21
--- /dev/null
+++ b/status/modules/gold-status-sk/index.ts
@@ -0,0 +1,2 @@
+import './gold-status-sk';
+import './gold-status-sk.scss';
diff --git a/status/modules/perf-status-sk/perf-status-sk.ts b/status/modules/perf-status-sk/perf-status-sk.ts
index 8acf5c7..f055c16 100644
--- a/status/modules/perf-status-sk/perf-status-sk.ts
+++ b/status/modules/perf-status-sk/perf-status-sk.ts
@@ -8,6 +8,7 @@
 import { html } from 'lit-html';
 import { ElementSk } from '../../../infra-sk/modules/ElementSk';
 import { jsonOrThrow } from 'common-sk/modules/jsonOrThrow';
+import { errorMessage } from 'elements-sk/errorMessage';
 import { AlertsStatus } from '../../../perf/modules/json';
 
 export class PerfStatusSk extends ElementSk {
@@ -44,6 +45,7 @@
         this.resp = json;
         this._render();
       })
+      .catch(errorMessage)
       .finally(() => {
         window.setTimeout(() => this.refresh(), 60 * 1000);
       });
diff --git a/status/modules/status-sk/status-sk-demo.ts b/status/modules/status-sk/status-sk-demo.ts
index ba36c5f..2255237 100644
--- a/status/modules/status-sk/status-sk-demo.ts
+++ b/status/modules/status-sk/status-sk-demo.ts
@@ -4,6 +4,7 @@
 import { $$ } from 'common-sk/modules/dom';
 import { SetTestSettings } from '../settings';
 import fetchMock from 'fetch-mock';
+import { StatusResponse } from '../../../golden/modules/rpc_types';
 
 SetupMocks()
   .expectGetIncrementalCommits(incrementalResponse0)
@@ -19,6 +20,17 @@
   ]),
 });
 fetchMock.getOnce('https://perf.skia.org/_/alerts/', <AlertsStatus>{ alerts: 5 });
+fetchMock.getOnce('https://gold.skia.org/json/v1/trstatus', <StatusResponse>{
+  corpStatus: [
+    { name: 'canvaskit', untriagedCount: 0 },
+    { name: 'colorImage', untriagedCount: 0 },
+    { name: 'gm', untriagedCount: 13 },
+    { name: 'image', untriagedCount: 0 },
+    { name: 'pathkit', untriagedCount: 0 },
+    { name: 'skp', untriagedCount: 0 },
+    { name: 'svg', untriagedCount: 27 },
+  ],
+});
 const data = document.createElement('status-sk');
 ($$('#container') as HTMLElement).appendChild(data);
 (document.querySelector('#AllFilter') as HTMLElement).click();
diff --git a/status/modules/status-sk/status-sk.ts b/status/modules/status-sk/status-sk.ts
index 3c96516..a3c2048 100644
--- a/status/modules/status-sk/status-sk.ts
+++ b/status/modules/status-sk/status-sk.ts
@@ -13,6 +13,7 @@
 import '../../../infra-sk/modules/login-sk';
 import '../autoroller-status-sk';
 import '../commits-table-sk';
+import '../gold-status-sk';
 import '../navigation-sk';
 import '../perf-status-sk';
 import 'elements-sk/collapse-sk';
@@ -29,6 +30,7 @@
   private repo: string = defaultRepo();
   private autorollersOpen: boolean = true;
   private perfOpen: boolean = true;
+  private goldOpen: boolean = true;
   private static template = (el: StatusSk) =>
     html`
       <app-sk>
@@ -74,6 +76,23 @@
               <perf-status-sk></perf-status-sk>
             </collapse-sk>
           </div>
+          <div>
+            <button
+              class="collapser"
+              @click=${(e: Event) => {
+                el.goldOpen = !el.goldOpen;
+                el.toggle((<HTMLButtonElement>e.target).nextElementSibling);
+              }}
+            >
+              ${el.goldOpen
+                ? html`<expand-less-icon-sk></expand-less-icon-sk>`
+                : html`<expand-more-icon-sk></expand-more-icon-sk>`}
+              Gold
+            </button>
+            <collapse-sk>
+              <gold-status-sk></gold-status-sk>
+            </collapse-sk>
+          </div>
         </aside>
 
         <main>
diff --git a/status/modules/status-sk/status-sk_test.ts b/status/modules/status-sk/status-sk_test.ts
index 75efc62..2a2568b 100644
--- a/status/modules/status-sk/status-sk_test.ts
+++ b/status/modules/status-sk/status-sk_test.ts
@@ -8,6 +8,7 @@
 import { incrementalResponse0, SetupMocks } from '../rpc-mock';
 import fetchMock from 'fetch-mock';
 import { SetTestSettings } from '../settings';
+import { StatusResponse } from '../../../golden/modules/rpc_types';
 
 describe('status-sk', () => {
   const newInstance = setUpElementUnderTest<StatusSk>('status-sk');
@@ -26,6 +27,17 @@
     });
     fetchMock.getOnce('path:/loginstatus/', {});
     fetchMock.getOnce('https://perf.skia.org/_/alerts/', <AlertsStatus>{ alerts: 5 });
+    fetchMock.getOnce('https://gold.skia.org/json/v1/trstatus', <StatusResponse>{
+      corpStatus: [
+        { name: 'canvaskit', untriagedCount: 0 },
+        { name: 'colorImage', untriagedCount: 0 },
+        { name: 'gm', untriagedCount: 13 },
+        { name: 'image', untriagedCount: 0 },
+        { name: 'pathkit', untriagedCount: 0 },
+        { name: 'skp', untriagedCount: 0 },
+        { name: 'svg', untriagedCount: 27 },
+      ],
+    });
     SetupMocks().expectGetIncrementalCommits(incrementalResponse0);
     const ep = eventPromise('end-task');
     element = newInstance();