[Pinpoint] Implement Simplified Tab for New Job and Scaffold Testing

Couple things I address for this CL

1. Bug ID is now an available parameter (detailed tab)
2. Simplified tab has hard set defaulted options for casual users
3. Simple unit testing for UI Scaffold

Change-Id: I3dd81eb3e1b5bf74eee9f340a906bf7a3c71852b
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/1028667
Commit-Queue: Natnael Alemayehu <natnaelal@google.com>
Reviewed-by: Maggie Dong <jiaxindong@google.com>
diff --git a/pinpoint/ui/modules/pinpoint-new-job-sk/pinpoint-new-job-sk.ts b/pinpoint/ui/modules/pinpoint-new-job-sk/pinpoint-new-job-sk.ts
index f4b316c..b52f3bc 100644
--- a/pinpoint/ui/modules/pinpoint-new-job-sk/pinpoint-new-job-sk.ts
+++ b/pinpoint/ui/modules/pinpoint-new-job-sk/pinpoint-new-job-sk.ts
@@ -66,6 +66,14 @@
       color: var(--md-sys-color-on-surface-variant);
     }
 
+    .help-section ul {
+      padding-left: 20px;
+      margin: 1em 0;
+    }
+    .help-section li {
+      margin-bottom: 0.5em;
+    }
+
     .form-section {
       display: flex;
       flex-direction: column;
@@ -87,6 +95,30 @@
       color: var(--md-sys-color-on-surface);
     }
 
+    .form-section p {
+      margin: 0 0 1em 0;
+      color: var(--md-sys-color-on-surface-variant);
+      font-size: 0.9em;
+      line-height: 1.4;
+    }
+
+    .form-section ul {
+      list-style: none;
+      padding-left: 0;
+      margin: 8px 0;
+      color: var(--md-sys-color-on-surface-variant);
+    }
+
+    .form-section li {
+      padding: 4px 0;
+    }
+
+    .form-section li b {
+      color: var(--md-sys-color-on-surface);
+      min-width: 120px;
+      display: inline-block;
+    }
+
     md-outlined-text-field,
     md-outlined-select {
       width: 100%;
@@ -129,6 +161,8 @@
 
   @state() private _iterationCount = '10';
 
+  @state() private _bugId = '';
+
   async connectedCallback() {
     super.connectedCallback();
     try {
@@ -242,10 +276,16 @@
             @input=${(e: InputEvent) =>
               (this._iterationCount = (
                 e.target as HTMLInputElement
-              ).value)}></md-outlined-text-field>
+              ).value)}></md-outlined-text-field
+          ><md-outlined-text-field
+            label="Bug ID (optional)"
+            type="number"
+            .value=${this._bugId}
+            @input=${(e: InputEvent) =>
+              (this._bugId = (e.target as HTMLInputElement).value)}></md-outlined-text-field>
         </div>
         <div class="help-section">
-          <h3>Job Name & Iterations</h3>
+          <h3>Job Name, Iterations & Bug ID</h3>
           <p>
             Give your job a memorable name for easier identification later. If left blank, a name
             will be generated.
@@ -254,6 +294,9 @@
             The number of iterations to run the benchmark. Higher iterations usually yield more
             granular benchmark results. This value defaults to 10.
           </p>
+          <p>
+            If this job is related to a bug, you can provide the bug ID here for tracking purposes.
+          </p>
         </div>
       </div>
     `;
@@ -261,8 +304,60 @@
 
   private renderSimplifiedView() {
     return html`
-      <div class="simplified-view">
-        <p>A simplified job creation flow is coming soon.</p>
+      <div class="detailed-grid">
+        <div class="form-section">
+          <h2>1. Define Commit Range</h2>
+          <p>
+            A Pinpoint job can either be a <b>bisection</b> to find a commit that caused a
+            performance regression, or a <b>try job</b> to compare performance between two commits.
+            Provide two commit points to define the range for the job.
+          </p>
+          <h3>Base Commit</h3>
+          <md-outlined-text-field
+            label="Commit Hash"
+            placeholder="Commit Hash"></md-outlined-text-field>
+          <h3>Experimental Commit</h3>
+          <md-outlined-text-field
+            label="Commit Hash"
+            placeholder="Commit Hash"></md-outlined-text-field>
+
+          <h2>2. Review Test Configuration</h2>
+          <p>
+            This simplified flow uses a standard test configuration for general performance
+            analysis. For custom settings, use the "Detailed" tab.
+          </p>
+          <ul>
+            <li><b>Benchmark:</b> <span>speedometer3.crossbench</span></li>
+            <li><b>Device:</b> <span>mac-m1-pro-perf</span></li>
+            <li><b>Story:</b> <span>default</span></li>
+            <li><b>Iteration Count:</b> <span>20</span></li>
+          </ul>
+        </div>
+        <div class="help-section">
+          <h3>What is Pinpoint?</h3>
+          <p>
+            Pinpoint is a performance testing tool for Chrome that helps diagnose regressions and
+            evaluate performance changes. It automates the process of building Chrome at different
+            revisions, running benchmarks, and comparing the results.
+          </p>
+          <p>
+            You can run two main types of jobs:
+            <ul>
+              <li><b>Try Job:</b> An A/B test that compares performance between two specific commits.</li>
+              <li><b>Bisection:</b> A binary search across a range of commits to automatically find the one that introduced a performance regression.</li>
+            </ul>
+          </p>
+          <h3>Commit Range</h3>
+          <p>
+            Provide two commit points (as git hashes) to define the job's scope. For a try job, these are your A and B points. For a bisection, this is the range to search.
+          </p>
+          <h3>Test Configuration</h3>
+          <p>
+            This simplified view uses a common, pre-selected configuration for quick testing. The
+            benchmark, device, and other parameters are fixed. If you need to test on different
+            devices or run other benchmarks, please use the "Detailed" tab.
+          </p>
+        </div>
       </div>
     `;
   }
diff --git a/pinpoint/ui/modules/pinpoint-scaffold-sk/BUILD.bazel b/pinpoint/ui/modules/pinpoint-scaffold-sk/BUILD.bazel
index 7ad7860..fd08c47 100644
--- a/pinpoint/ui/modules/pinpoint-scaffold-sk/BUILD.bazel
+++ b/pinpoint/ui/modules/pinpoint-scaffold-sk/BUILD.bazel
@@ -1,8 +1,11 @@
-load("//infra-sk:index.bzl", "sk_element")
+load("//infra-sk:index.bzl", "karma_test", "sk_element")
 
 sk_element(
     name = "pinpoint-scaffold-sk",
-    sk_element_deps = ["//elements-sk/modules/icons/filter-list-icon-sk"],
+    sk_element_deps = [
+        "//elements-sk/modules/icons/filter-list-icon-sk",
+        "//pinpoint/ui/modules/pinpoint-new-job-sk",
+    ],
     ts_deps = [
         "//:node_modules/lit",
         "//:node_modules/lit-html",  # keep
@@ -16,3 +19,18 @@
     ],
     visibility = ["//visibility:public"],
 )
+
+karma_test(
+    name = "pinpoint-scaffold-sk_test",
+    src = "pinpoint-scaffold-sk_test.ts",
+    deps = [
+        ":pinpoint-scaffold-sk",
+        "//:node_modules/@types/chai",
+        "//:node_modules/@types/sinon",
+        "//:node_modules/@vaadin/combo-box",
+        "//:node_modules/chai",
+        "//:node_modules/fetch-mock",
+        "//:node_modules/sinon",
+        "//pinpoint/ui/modules/pinpoint-new-job-sk",
+    ],
+)
diff --git a/pinpoint/ui/modules/pinpoint-scaffold-sk/pinpoint-scaffold-sk.ts b/pinpoint/ui/modules/pinpoint-scaffold-sk/pinpoint-scaffold-sk.ts
index 61f9eaa..c2bfbdd 100644
--- a/pinpoint/ui/modules/pinpoint-scaffold-sk/pinpoint-scaffold-sk.ts
+++ b/pinpoint/ui/modules/pinpoint-scaffold-sk/pinpoint-scaffold-sk.ts
@@ -1,5 +1,5 @@
 import { LitElement, html, css } from 'lit';
-import { customElement, state } from 'lit/decorators.js';
+import { customElement, state, query } from 'lit/decorators.js';
 import { listBenchmarks, listBots } from '../../services/api';
 import '@material/web/button/filled-button.js';
 import '@material/web/textfield/outlined-text-field.js';
@@ -10,6 +10,10 @@
 import '@vaadin/combo-box/vaadin-combo-box.js';
 import { Menu } from '@material/web/menu/internal/menu.js';
 
+import '../pinpoint-new-job-sk';
+
+import type { PinpointNewJobSk } from '../pinpoint-new-job-sk/pinpoint-new-job-sk';
+
 /**
  * @element pinpoint-scaffold-sk
  *
@@ -74,6 +78,8 @@
     }
   `;
 
+  @query('pinpoint-new-job-sk') private _newJobModal!: PinpointNewJobSk;
+
   private onSearchInput(e: InputEvent) {
     const value = (e.target as HTMLInputElement).value;
     this.dispatchEvent(
@@ -85,6 +91,10 @@
     );
   }
 
+  private openNewJobModal() {
+    this._newJobModal.show();
+  }
+
   private onBenchmarkFilterChange(e: CustomEvent) {
     this._selectedBenchmark = e.detail.value || '';
   }
@@ -157,12 +167,13 @@
               </div>
             </md-menu>
           </div>
-          <md-filled-button>Create new job</md-filled-button>
+          <md-filled-button @click=${this.openNewJobModal}>Create a new job</md-filled-button>
         </div>
       </header>
       <main>
         <slot></slot>
       </main>
+      <pinpoint-new-job-sk></pinpoint-new-job-sk>
     `;
   }
 }
diff --git a/pinpoint/ui/modules/pinpoint-scaffold-sk/pinpoint-scaffold-sk_test.ts b/pinpoint/ui/modules/pinpoint-scaffold-sk/pinpoint-scaffold-sk_test.ts
new file mode 100644
index 0000000..19aea44
--- /dev/null
+++ b/pinpoint/ui/modules/pinpoint-scaffold-sk/pinpoint-scaffold-sk_test.ts
@@ -0,0 +1,121 @@
+import { expect } from 'chai';
+import fetchMock from 'fetch-mock';
+import sinon from 'sinon';
+import { PinpointScaffoldSk } from './pinpoint-scaffold-sk';
+import './index';
+import { PinpointNewJobSk } from '../pinpoint-new-job-sk/pinpoint-new-job-sk';
+import { ComboBox } from '@vaadin/combo-box';
+
+describe('PinpointScaffoldSk', () => {
+  const benchmarks = ['benchmark1', 'benchmark2'];
+  const bots = ['bot1', 'bot2'];
+
+  let container: HTMLElement;
+  let element: PinpointScaffoldSk;
+
+  beforeEach(async () => {
+    fetchMock.get('/benchmarks', benchmarks);
+    fetchMock.get('/bots?benchmark=', bots);
+
+    container = document.createElement('div');
+    document.body.appendChild(container);
+    element = document.createElement('pinpoint-scaffold-sk') as PinpointScaffoldSk;
+    container.appendChild(element);
+
+    // Wait for element to connect and fetch initial data.
+    await fetchMock.flush(true);
+    await element.updateComplete;
+  });
+
+  afterEach(() => {
+    fetchMock.reset();
+    sinon.restore();
+    document.body.removeChild(container);
+  });
+
+  it('loads initial data on connectedCallback', () => {
+    // @ts-expect-error - access private property for testing
+    expect(element._benchmarks).to.deep.equal(benchmarks);
+    // @ts-expect-error - access private property for testing
+    expect(element._bots).to.deep.equal(bots);
+  });
+
+  it('dispatches search-changed event on search input', () => {
+    const spy = sinon.spy();
+    element.addEventListener('search-changed', spy);
+
+    const searchField = element.shadowRoot!.querySelector(
+      'md-outlined-text-field[label="Search by job name"]'
+    ) as HTMLElement;
+    const inputElement = searchField.shadowRoot!.querySelector('input')!;
+    inputElement.value = 'test search';
+    inputElement.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
+
+    // eslint-disable-next-line @typescript-eslint/no-unused-expressions
+    expect(spy.calledOnce).to.be.true;
+    expect(spy.firstCall.args[0].detail).to.deep.equal({ value: 'test search' });
+  });
+
+  it('dispatches filters-changed event when filters are applied', async () => {
+    const spy = sinon.spy();
+    element.addEventListener('filters-changed', spy);
+
+    // Open the filter menu
+    const filterButton = element.shadowRoot!.querySelector('#filter-anchor') as HTMLElement;
+    filterButton.click();
+    await element.updateComplete;
+
+    // Set filter values
+    const benchmarkComboBox = element.shadowRoot!.querySelector(
+      'vaadin-combo-box[label="Benchmark"]'
+    ) as ComboBox;
+    benchmarkComboBox.value = 'benchmark1';
+    benchmarkComboBox.dispatchEvent(
+      new CustomEvent('value-changed', { detail: { value: 'benchmark1' } })
+    );
+
+    const botComboBox = element.shadowRoot!.querySelector(
+      'vaadin-combo-box[label="Device"]'
+    ) as ComboBox;
+    botComboBox.value = 'bot1';
+    botComboBox.dispatchEvent(new CustomEvent('value-changed', { detail: { value: 'bot1' } }));
+
+    const userFilter = element.shadowRoot!.querySelector(
+      '.filter-menu-items md-outlined-text-field[label="User"]'
+    ) as HTMLElement;
+    const inputElement = userFilter.shadowRoot!.querySelector('input')!;
+    inputElement.value = 'test-user';
+    inputElement.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
+
+    await element.updateComplete;
+
+    // Apply filters
+    const applyButton = element.shadowRoot!.querySelector(
+      '.filter-actions md-filled-button'
+    ) as HTMLElement;
+    applyButton.click();
+
+    // eslint-disable-next-line @typescript-eslint/no-unused-expressions
+    expect(spy.calledOnce).to.be.true;
+    expect(spy.firstCall.args[0].detail).to.deep.equal({
+      benchmark: 'benchmark1',
+      botName: 'bot1',
+      user: 'test-user',
+    });
+  });
+
+  it('opens the new job modal when "Create a new job" is clicked', () => {
+    const newJobModal = element.shadowRoot!.querySelector(
+      'pinpoint-new-job-sk'
+    ) as PinpointNewJobSk;
+    const showSpy = sinon.spy(newJobModal, 'show');
+
+    const createButton = element.shadowRoot!.querySelector(
+      '.header-actions > md-filled-button'
+    ) as HTMLElement;
+    createButton.click();
+
+    // eslint-disable-next-line @typescript-eslint/no-unused-expressions
+    expect(showSpy.calledOnce).to.be.true;
+  });
+});