[gold] Port list-page-sk to TypeScript.
Bug: skia:10246
Change-Id: Idce01410281eccaebd95831990f8bf8795fb149a
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/403836
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Commit-Queue: Kevin Lubick <kjlubick@google.com>
diff --git a/golden/go/web/frontend/generate_typescript_rpc_types/main.go b/golden/go/web/frontend/generate_typescript_rpc_types/main.go
index c95fc4c..9ff9629 100644
--- a/golden/go/web/frontend/generate_typescript_rpc_types/main.go
+++ b/golden/go/web/frontend/generate_typescript_rpc_types/main.go
@@ -70,6 +70,9 @@
// Response for the /json/v1/ignores RPC endpoint.
generator.Add(frontend.IgnoresResponse{})
+ // Response for the /json/v1/list RPC endpoint.
+ generator.Add(frontend.ListTestsResponse{})
+
generator.AddUnionWithName(expectations.AllLabel, "Label")
generator.AddUnionWithName(common.AllRefClosest, "RefClosest")
}
diff --git a/golden/go/web/frontend/types.go b/golden/go/web/frontend/types.go
index 9ff1e18..2df0cf7 100644
--- a/golden/go/web/frontend/types.go
+++ b/golden/go/web/frontend/types.go
@@ -357,4 +357,10 @@
PositiveDigests int `json:"positive_digests"`
NegativeDigests int `json:"negative_digests"`
UntriagedDigests int `json:"untriaged_digests"`
+ TotalDigests int `json:"total_digests"`
+}
+
+// ListTestsResponse is the response for /json/v1/list.
+type ListTestsResponse struct {
+ Tests []TestSummary `json:"tests"`
}
diff --git a/golden/go/web/web.go b/golden/go/web/web.go
index bf71344..fb65325 100644
--- a/golden/go/web/web.go
+++ b/golden/go/web/web.go
@@ -1219,6 +1219,7 @@
PositiveDigests: s.Pos,
NegativeDigests: s.Neg,
UntriagedDigests: s.Untriaged,
+ TotalDigests: s.Pos + s.Neg + s.Untriaged,
})
}
}
@@ -1226,9 +1227,10 @@
sort.Slice(tests, func(i, j int) bool {
return tests[i].Name < tests[j].Name
})
- // Outputs: []frontend.TestSummary
- // Frontend will have option to hide tests with no digests.
- sendJSONResponse(w, tests)
+
+ // Frontend will have option to hide tests with no digests.
+ response := frontend.ListTestsResponse{Tests: tests}
+ sendJSONResponse(w, response)
}
// TriageLogHandler returns the entries in the triagelog paginated
diff --git a/golden/go/web/web_test.go b/golden/go/web/web_test.go
index 93d4c06..8962700 100644
--- a/golden/go/web/web_test.go
+++ b/golden/go/web/web_test.go
@@ -2570,35 +2570,35 @@
}
test("all GM tests from all traces", "/json/list?corpus=gm&include_ignored_traces=true",
- `[{"name":"test_one","positive_digests":1,"negative_digests":0,"untriaged_digests":1},`+
- `{"name":"test_two","positive_digests":2,"negative_digests":0,"untriaged_digests":2}]`)
+ `{"tests":[{"name":"test_one","positive_digests":1,"negative_digests":0,"untriaged_digests":1,"total_digests":2},`+
+ `{"name":"test_two","positive_digests":2,"negative_digests":0,"untriaged_digests":2,"total_digests":4}]}`)
test("all GM tests at head from all traces", "/json/list?corpus=gm&at_head_only=true&include_ignored_traces=true",
- `[{"name":"test_one","positive_digests":1,"negative_digests":0,"untriaged_digests":0},`+
- `{"name":"test_two","positive_digests":2,"negative_digests":0,"untriaged_digests":1}]`)
+ `{"tests":[{"name":"test_one","positive_digests":1,"negative_digests":0,"untriaged_digests":0,"total_digests":1},`+
+ `{"name":"test_two","positive_digests":2,"negative_digests":0,"untriaged_digests":1,"total_digests":3}]}`)
test("all GM tests for device beta from all traces", "/json/list?corpus=gm&trace_values=device%3Dbeta&include_ignored_traces=true",
- `[{"name":"test_one","positive_digests":1,"negative_digests":0,"untriaged_digests":1},`+
- `{"name":"test_two","positive_digests":1,"negative_digests":0,"untriaged_digests":0}]`)
+ `{"tests":[{"name":"test_one","positive_digests":1,"negative_digests":0,"untriaged_digests":1,"total_digests":2},`+
+ `{"name":"test_two","positive_digests":1,"negative_digests":0,"untriaged_digests":0,"total_digests":1}]}`)
test("all GM tests for device delta at head from all traces", "/json/list?corpus=gm&trace_values=device%3Ddelta&at_head_only=true&include_ignored_traces=true",
- `[{"name":"test_one","positive_digests":1,"negative_digests":0,"untriaged_digests":0},`+
- `{"name":"test_two","positive_digests":0,"negative_digests":0,"untriaged_digests":1}]`)
+ `{"tests":[{"name":"test_one","positive_digests":1,"negative_digests":0,"untriaged_digests":0,"total_digests":1},`+
+ `{"name":"test_two","positive_digests":0,"negative_digests":0,"untriaged_digests":1,"total_digests":1}]}`)
// Reminder that device delta and test_two match ignore rules
test("all GM tests", "/json/list?corpus=gm",
- `[{"name":"test_one","positive_digests":1,"negative_digests":0,"untriaged_digests":1}]`)
+ `{"tests":[{"name":"test_one","positive_digests":1,"negative_digests":0,"untriaged_digests":1,"total_digests":2}]}`)
test("all GM tests at head", "/json/list?corpus=gm&at_head_only=true",
- `[{"name":"test_one","positive_digests":1,"negative_digests":0,"untriaged_digests":0}]`)
+ `{"tests":[{"name":"test_one","positive_digests":1,"negative_digests":0,"untriaged_digests":0,"total_digests":1}]}`)
test("all GM tests for device beta", "/json/list?corpus=gm&trace_values=device%3Dbeta",
- `[{"name":"test_one","positive_digests":1,"negative_digests":0,"untriaged_digests":1}]`)
+ `{"tests":[{"name":"test_one","positive_digests":1,"negative_digests":0,"untriaged_digests":1,"total_digests":2}]}`)
test("all GM tests for device delta at head", "/json/list?corpus=gm&trace_values=device%3Ddelta&at_head_only=true",
- "[]")
+ `{"tests":[]}`)
- test("non existent corpus", "/json/list?corpus=notthere", "[]")
+ test("non existent corpus", "/json/list?corpus=notthere", `{"tests":[]}`)
}
func TestListTestsHandler_InvalidQueries_BadRequestError(t *testing.T) {
diff --git a/golden/modules/list-page-sk/index.js b/golden/modules/list-page-sk/index.ts
similarity index 100%
rename from golden/modules/list-page-sk/index.js
rename to golden/modules/list-page-sk/index.ts
diff --git a/golden/modules/list-page-sk/list-page-sk-demo.js b/golden/modules/list-page-sk/list-page-sk-demo.ts
similarity index 78%
rename from golden/modules/list-page-sk/list-page-sk-demo.js
rename to golden/modules/list-page-sk/list-page-sk-demo.ts
index c390573..aaf2f68 100644
--- a/golden/modules/list-page-sk/list-page-sk-demo.js
+++ b/golden/modules/list-page-sk/list-page-sk-demo.ts
@@ -1,12 +1,12 @@
import './index';
import '../gold-scaffold-sk';
-import { $$ } from 'common-sk/modules/dom';
import { delay } from '../demo_util';
import { manyParams } from '../shared_demo_data';
import { testOnlySetSettings } from '../settings';
import { sampleByTestList } from './test_data';
import { exampleStatusData } from '../last-commit-sk/demo_data';
import fetchMock from 'fetch-mock';
+import { ListPageSk } from './list-page-sk';
testOnlySetSettings({
title: 'Testing Gold',
@@ -21,7 +21,6 @@
// By adding these elements after all the fetches are mocked out, they should load ok.
const newScaf = document.createElement('gold-scaffold-sk');
newScaf.setAttribute('testing_offline', 'true');
-const body = $$('body');
-body.insertBefore(newScaf, body.childNodes[0]); // Make it the first element in body.
-const page = document.createElement('list-page-sk');
-newScaf.appendChild(page);
+// Make it the first element in body.
+document.body.insertBefore(newScaf, document.body.childNodes[0]);
+newScaf.appendChild(new ListPageSk());
diff --git a/golden/modules/list-page-sk/list-page-sk.js b/golden/modules/list-page-sk/list-page-sk.js
deleted file mode 100644
index 0d3f503..0000000
--- a/golden/modules/list-page-sk/list-page-sk.js
+++ /dev/null
@@ -1,283 +0,0 @@
-/**
- * @module module/list-page-sk
- * @description <h2><code>list-page-sk</code></h2>
- *
- * This page summarizes the outputs of various tests. It shows the amount of digests produced,
- * as well as a few options to configure what range of traces to enumerate.
- *
- * It is a top level element.
- */
-import { define } from 'elements-sk/define';
-import { html } from 'lit-html';
-import { $$ } from 'common-sk/modules/dom';
-import { jsonOrThrow } from 'common-sk/modules/jsonOrThrow';
-import { stateReflector } from 'common-sk/modules/stateReflector';
-import { ElementSk } from '../../../infra-sk/modules/ElementSk';
-import { sendBeginTask, sendEndTask, sendFetchError } from '../common';
-import { defaultCorpus } from '../settings';
-
-import '../corpus-selector-sk';
-import '../query-dialog-sk';
-import '../sort-toggle-sk';
-import 'elements-sk/checkbox-sk';
-import 'elements-sk/icon/group-work-icon-sk';
-import 'elements-sk/icon/tune-icon-sk';
-import { SearchCriteriaToHintableObject } from '../search-controls-sk';
-import { fromObject } from 'common-sk/modules/query';
-
-const template = (ele) => html`
-<div>
- <corpus-selector-sk .corpora=${ele._corpora}
- .selectedCorpus=${ele._currentCorpus} @corpus-selected=${ele._currentCorpusChanged}>
- </corpus-selector-sk>
-
- <div class=query_params>
- <button class=show_query_dialog @click=${ele._showQueryDialog}>
- <tune-icon-sk></tune-icon-sk>
- </button>
- <pre>${searchQuery(ele._currentCorpus, ele._currentQuery)}</pre>
- <checkbox-sk label="Digests at Head Only" class=head_only
- ?checked=${!ele._showAllDigests} @click=${ele._toggleAllDigests}></checkbox-sk>
- <checkbox-sk label="Disregard Ignore Rules" class=ignore_rules
- ?checked=${ele._disregardIgnoreRules} @click=${ele._toggleIgnoreRules}></checkbox-sk>
- </div>
-</div>
-
-<!-- lit-html (or maybe html in general) doesn't like sort-toggle-sk to go inside the table.-->
-<sort-toggle-sk id=sort_table .data=${ele._byTestCounts} @sort-changed=${ele._render}>
- <table>
- <thead>
- <tr>
- <th data-key=name data-sort-toggle-sk=up>Test name</th>
- <th data-key=positive_digests>Positive</th>
- <th data-key=negative_digests>Negative</th>
- <th data-key=untriaged_digests>Untriaged</th>
- <th data-key=total_digests>Total</th>
- <th>Cluster View</th>
- </tr>
- </thead>
- <tbody>
- <!-- repeat was tested here; map is about twice as fast as using the repeat directive
- (which moves the existing elements). This is because reusing the existing templates
- is pretty fast because there isn't a lot to change.-->
- ${ele._byTestCounts.map((row) => testRow(row, ele))}
- </tbody>
- </table>
-</sort-toggle-sk>
-
-<query-dialog-sk @edit=${ele._currentQueryChanged}></query-dialog-sk>
-`;
-
-const testRow = (row, ele) => {
- // Returns a HintableObject for building the GET parameters to the search page.
- const makeSearchCriteria = (opts) => SearchCriteriaToHintableObject({
- corpus: ele._currentCorpus,
- leftHandTraceFilter: {'name': [row.name]},
- includePositiveDigests: opts.positive,
- includeNegativeDigests: opts.negative,
- includeUntriagedDigests: opts.untriaged,
- includeDigestsNotAtHead: ele._showAllDigests ? 'true' : 'false',
- includeIgnoredDigests: ele._disregardIgnoreRules ? 'true' : 'false',
- });
-
- const searchPageHref = (opts) => {
- const searchCriteria = makeSearchCriteria(opts);
- const queryParameters = fromObject(searchCriteria);
- return `/search?${queryParameters}`;
- };
-
- const clusterPageHref = () => {
- const hintableObject = {
- ...makeSearchCriteria({positive: true, negative: true, untriaged: true}),
- left_filter: '',
- grouping: row.name,
- };
- return `/cluster?${fromObject(hintableObject)}`;
- }
-
- return html`
-<tr>
- <td>
- <a href="${searchPageHref({positive: true, negative: true, untriaged: true})}"
- target=_blank rel=noopener>
- ${row.name}
- </a>
- </td>
- <td class=center>
- <a href="${searchPageHref({positive: true, negative: false, untriaged: false})}"
- target=_blank rel=noopener>
- ${row.positive_digests}
- </a>
- </td>
- <td class=center>
- <a href="${searchPageHref({positive: false, negative: true, untriaged: false})}"
- target=_blank rel=noopener>
- ${row.negative_digests}
- </a>
- </td>
- <td class=center>
- <a href="${searchPageHref({positive: false, negative: false, untriaged: true})}"
- target=_blank rel=noopener>
- ${row.untriaged_digests}
- </a>
- </td>
- <td class=center>
- <a href="${searchPageHref({positive: true, negative: true, untriaged: true})}"
- target=_blank rel=noopener>
- ${row.total_digests}
- </a>
- </td>
- <td class=center>
- <a href="${clusterPageHref()}" target=_blank rel=noopener>
- <group-work-icon-sk></group-work-icon-sk>
- </a>
- </td>
-</tr>`;
-};
-
-const searchQuery = (corpus, query) => {
- if (!query) {
- return `source_type=${corpus}`;
- }
- return `source_type=${corpus}, \n${query.split('&').join(',\n')}`;
-};
-
-define('list-page-sk', class extends ElementSk {
- constructor() {
- super(template);
-
- this._corpora = [];
- this._paramset = {};
-
- this._currentQuery = '';
- this._currentCorpus = '';
-
- this._showAllDigests = false;
- this._disregardIgnoreRules = false;
-
- this._stateChanged = stateReflector(
- /* getState */() => ({
- // provide empty values
- all_digests: this._showAllDigests,
- disregard_ignores: this._disregardIgnoreRules,
- corpus: this._currentCorpus,
- query: this._currentQuery,
- }), /* setState */(newState) => {
- if (!this._connected) {
- return;
- }
- // default values if not specified.
- this._showAllDigests = newState.all_digests || false;
- this._disregardIgnoreRules = newState.disregard_ignores || false;
- this._currentCorpus = newState.corpus || defaultCorpus();
- this._currentQuery = newState.query || '';
- this._fetch();
- this._render();
- },
- );
-
- this._byTestCounts = [];
-
- // Allows us to abort fetches if we fetch again.
- this._fetchController = null;
- }
-
- connectedCallback() {
- super.connectedCallback();
- this._render();
- }
-
- _currentCorpusChanged(e) {
- e.stopPropagation();
- this._currentCorpus = e.detail;
- this._stateChanged();
- this._render();
- this._fetch();
- }
-
- _currentQueryChanged(e) {
- e.stopPropagation();
- this._currentQuery = e.detail;
- this._stateChanged();
- this._render();
- this._fetch();
- }
-
- _fetch() {
- if (this._fetchController) {
- // Kill any outstanding requests
- this._fetchController.abort();
- }
-
- // Make a fresh abort controller for each set of fetches.
- // They cannot be re-used once aborted.
- this._fetchController = new AbortController();
- const extra = {
- signal: this._fetchController.signal,
- };
-
- sendBeginTask(this);
- sendBeginTask(this);
-
- let url = `/json/v1/list?corpus=${encodeURIComponent(this._currentCorpus)}`;
- if (!this._showAllDigests) {
- url += '&at_head_only=true';
- }
- if (this._disregardIgnoreRules) {
- url += '&include_ignored_traces=true';
- }
- if (this._currentQuery) {
- url += `&trace_values=${encodeURIComponent(this._currentQuery)}`;
- }
- fetch(url, extra)
- .then(jsonOrThrow)
- .then((jsonList) => {
- this._byTestCounts = jsonList;
- this._byTestCounts.forEach((row) => {
- row.total_digests = row.positive_digests + row.negative_digests + row.untriaged_digests;
- });
- this._render();
- // By default, sort the data by name in ascending order (to match the direction set above).
- $$('#sort_table', this).sort('name', 'up');
- sendEndTask(this);
- })
- .catch((e) => sendFetchError(this, e, 'list'));
-
- // TODO(kjlubick) when the search page gets a makeover to have just the params for the given
- // corpus show up, we should do the same here. First idea is to have a separate corpora
- // endpoint and then make paramset take a corpus.
- fetch('/json/v1/paramset', extra)
- .then(jsonOrThrow)
- .then((paramset) => {
- // We split the paramset into a list of corpora...
- this._corpora = paramset.source_type || [];
- // ...and the rest of the keys. This is to make it so the layout is
- // consistent with other pages (e.g. the search page, the by blame page, etc).
- delete paramset.source_type;
- this._paramset = paramset;
- this._render();
- sendEndTask(this);
- })
- .catch((e) => sendFetchError(this, e, 'paramset'));
- }
-
- _showQueryDialog() {
- $$('query-dialog-sk').open(this._paramset, this._currentQuery);
- }
-
- _toggleAllDigests(e) {
- e.preventDefault();
- this._showAllDigests = !this._showAllDigests;
- this._stateChanged();
- this._render();
- this._fetch();
- }
-
- _toggleIgnoreRules(e) {
- e.preventDefault();
- this._disregardIgnoreRules = !this._disregardIgnoreRules;
- this._stateChanged();
- this._render();
- this._fetch();
- }
-});
diff --git a/golden/modules/list-page-sk/list-page-sk.ts b/golden/modules/list-page-sk/list-page-sk.ts
new file mode 100644
index 0000000..4c1a84f
--- /dev/null
+++ b/golden/modules/list-page-sk/list-page-sk.ts
@@ -0,0 +1,302 @@
+/**
+ * @module module/list-page-sk
+ * @description <h2><code>list-page-sk</code></h2>
+ *
+ * This page summarizes the outputs of various tests. It shows the amount of digests produced,
+ * as well as a few options to configure what range of traces to enumerate.
+ *
+ * It is a top level element.
+ */
+import { define } from 'elements-sk/define';
+import { html } from 'lit-html';
+import { $$ } from 'common-sk/modules/dom';
+import { jsonOrThrow } from 'common-sk/modules/jsonOrThrow';
+import { stateReflector } from 'common-sk/modules/stateReflector';
+import { ElementSk } from '../../../infra-sk/modules/ElementSk';
+import { sendBeginTask, sendEndTask, sendFetchError } from '../common';
+import { defaultCorpus } from '../settings';
+
+import '../corpus-selector-sk';
+import '../query-dialog-sk';
+import '../sort-toggle-sk';
+import 'elements-sk/checkbox-sk';
+import 'elements-sk/icon/group-work-icon-sk';
+import 'elements-sk/icon/tune-icon-sk';
+import { SearchCriteriaToHintableObject } from '../search-controls-sk';
+import { fromObject } from 'common-sk/modules/query';
+import { QueryDialogSk } from '../query-dialog-sk/query-dialog-sk';
+import { SortToggleSk } from '../sort-toggle-sk/sort-toggle-sk';
+import { SearchCriteriaHintableObject } from '../search-controls-sk/search-controls-sk';
+import { HintableObject } from 'common-sk/modules/hintable';
+import { ListTestsResponse, ParamSet, TestSummary } from '../rpc_types';
+
+const searchQuery = (corpus: string, query: string): string => {
+ if (!query) {
+ return `source_type=${corpus}`;
+ }
+ return `source_type=${corpus}, \n${query.split('&').join(',\n')}`;
+};
+
+export class ListPageSk extends ElementSk {
+ private static template = (ele: ListPageSk) => html`
+ <div>
+ <corpus-selector-sk .corpora=${ele.corpora}
+ .selectedCorpus=${ele.currentCorpus} @corpus-selected=${ele.currentCorpusChanged}>
+ </corpus-selector-sk>
+
+ <div class=query_params>
+ <button class=show_query_dialog @click=${ele.showQueryDialog}>
+ <tune-icon-sk></tune-icon-sk>
+ </button>
+ <pre>${searchQuery(ele.currentCorpus, ele.currentQuery)}</pre>
+ <checkbox-sk label="Digests at Head Only" class=head_only
+ ?checked=${!ele.showAllDigests} @click=${ele.toggleAllDigests}></checkbox-sk>
+ <checkbox-sk label="Disregard Ignore Rules" class=ignore_rules
+ ?checked=${ele.disregardIgnoreRules} @click=${ele.toggleIgnoreRules}></checkbox-sk>
+ </div>
+ </div>
+
+ <!-- lit-html (or maybe html in general) doesn't like sort-toggle-sk to go inside the table.-->
+ <sort-toggle-sk id=sort_table .data=${ele.byTestCounts} @sort-changed=${ele._render}>
+ <table>
+ <thead>
+ <tr>
+ <th data-key=name data-sort-toggle-sk=up>Test name</th>
+ <th data-key=positive_digests>Positive</th>
+ <th data-key=negative_digests>Negative</th>
+ <th data-key=untriaged_digests>Untriaged</th>
+ <th data-key=total_digests>Total</th>
+ <th>Cluster View</th>
+ </tr>
+ </thead>
+ <tbody>
+ <!-- repeat was tested here; map is about twice as fast as using the repeat directive
+ (which moves the existing elements). This is because reusing the existing templates
+ is pretty fast because there isn't a lot to change.-->
+ ${ele.byTestCounts.map((row) => ListPageSk.testRow(row, ele))}
+ </tbody>
+ </table>
+ </sort-toggle-sk>
+
+ <query-dialog-sk @edit=${ele.currentQueryChanged}></query-dialog-sk>
+ `;
+
+ private static testRow = (row: TestSummary, ele: ListPageSk) => {
+ interface MakeSearchCriteriaOpts {
+ positive: boolean;
+ negative: boolean;
+ untriaged: boolean;
+ }
+
+ // Returns a HintableObject for building the GET parameters to the search page.
+ const makeSearchCriteria = (opts: MakeSearchCriteriaOpts): SearchCriteriaHintableObject =>
+ SearchCriteriaToHintableObject({
+ corpus: ele.currentCorpus,
+ leftHandTraceFilter: {'name': [row.name]},
+ includePositiveDigests: opts.positive,
+ includeNegativeDigests: opts.negative,
+ includeUntriagedDigests: opts.untriaged,
+ includeDigestsNotAtHead: ele.showAllDigests,
+ includeIgnoredDigests: ele.disregardIgnoreRules,
+ });
+
+ const searchPageHref = (opts: MakeSearchCriteriaOpts) => {
+ const searchCriteria = makeSearchCriteria(opts);
+ const queryParameters = fromObject(searchCriteria as HintableObject);
+ return `/search?${queryParameters}`;
+ };
+
+ const clusterPageHref = () => {
+ const hintableObject: HintableObject = {
+ ...makeSearchCriteria({
+ positive: true,
+ negative: true,
+ untriaged: true
+ }),
+ left_filter: '',
+ grouping: row.name,
+ };
+ return `/cluster?${fromObject(hintableObject)}`;
+ }
+
+ return html`
+ <tr>
+ <td>
+ <a href="${searchPageHref({positive: true, negative: true, untriaged: true})}"
+ target=_blank rel=noopener>
+ ${row.name}
+ </a>
+ </td>
+ <td class=center>
+ <a href="${searchPageHref({positive: true, negative: false, untriaged: false})}"
+ target=_blank rel=noopener>
+ ${row.positive_digests}
+ </a>
+ </td>
+ <td class=center>
+ <a href="${searchPageHref({positive: false, negative: true, untriaged: false})}"
+ target=_blank rel=noopener>
+ ${row.negative_digests}
+ </a>
+ </td>
+ <td class=center>
+ <a href="${searchPageHref({positive: false, negative: false, untriaged: true})}"
+ target=_blank rel=noopener>
+ ${row.untriaged_digests}
+ </a>
+ </td>
+ <td class=center>
+ <a href="${searchPageHref({positive: true, negative: true, untriaged: true})}"
+ target=_blank rel=noopener>
+ ${row.total_digests}
+ </a>
+ </td>
+ <td class=center>
+ <a href="${clusterPageHref()}" target=_blank rel=noopener>
+ <group-work-icon-sk></group-work-icon-sk>
+ </a>
+ </td>
+ </tr>
+ `;
+ };
+
+ private corpora: string[] = [];
+ private paramset: ParamSet = {};
+
+ private currentQuery = '';
+ private currentCorpus = '';
+
+ private showAllDigests = false;
+ private disregardIgnoreRules = false;
+
+ private byTestCounts: TestSummary[] = [];
+
+ private readonly stateChanged: () => void;
+
+ // Allows us to abort fetches if we fetch again.
+ private fetchController?: AbortController;
+
+ constructor() {
+ super(ListPageSk.template);
+
+ this.stateChanged = stateReflector(
+ /* getState */() => ({
+ // provide empty values
+ all_digests: this.showAllDigests,
+ disregard_ignores: this.disregardIgnoreRules,
+ corpus: this.currentCorpus,
+ query: this.currentQuery,
+ }), /* setState */(newState) => {
+ if (!this._connected) {
+ return;
+ }
+ // default values if not specified.
+ this.showAllDigests = newState.all_digests as boolean || false;
+ this.disregardIgnoreRules = newState.disregard_ignores as boolean || false;
+ this.currentCorpus = newState.corpus as string || defaultCorpus();
+ this.currentQuery = newState.query as string || '';
+ this.fetch();
+ this._render();
+ },
+ );
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this._render();
+ }
+
+ private currentCorpusChanged(e: CustomEvent<string>) {
+ e.stopPropagation();
+ this.currentCorpus = e.detail;
+ this.stateChanged();
+ this._render();
+ this.fetch();
+ }
+
+ private currentQueryChanged(e: CustomEvent<string>) {
+ e.stopPropagation();
+ this.currentQuery = e.detail;
+ this.stateChanged();
+ this._render();
+ this.fetch();
+ }
+
+ private fetch() {
+ if (this.fetchController) {
+ // Kill any outstanding requests
+ this.fetchController.abort();
+ }
+
+ // Make a fresh abort controller for each set of fetches.
+ // They cannot be re-used once aborted.
+ this.fetchController = new AbortController();
+ const extra = {
+ signal: this.fetchController.signal,
+ };
+
+ sendBeginTask(this);
+ sendBeginTask(this);
+
+ let url = `/json/v1/list?corpus=${encodeURIComponent(this.currentCorpus)}`;
+ if (!this.showAllDigests) {
+ url += '&at_head_only=true';
+ }
+ if (this.disregardIgnoreRules) {
+ url += '&include_ignored_traces=true';
+ }
+ if (this.currentQuery) {
+ url += `&trace_values=${encodeURIComponent(this.currentQuery)}`;
+ }
+ fetch(url, extra)
+ .then(jsonOrThrow)
+ .then((response: ListTestsResponse) => {
+ this.byTestCounts = response.tests || [];
+ this._render();
+ // By default, sort the data by name in ascending order (to match the direction set
+ // above).
+ $$<SortToggleSk<TestSummary>>('#sort_table', this)!.sort('name', 'up');
+ sendEndTask(this);
+ })
+ .catch((e) => sendFetchError(this, e, 'list'));
+
+ // TODO(kjlubick) when the search page gets a makeover to have just the params for the given
+ // corpus show up, we should do the same here. First idea is to have a separate corpora
+ // endpoint and then make paramset take a corpus.
+ fetch('/json/v1/paramset', extra)
+ .then(jsonOrThrow)
+ .then((paramset: ParamSet) => {
+ // We split the paramset into a list of corpora...
+ this.corpora = paramset.source_type || [];
+ // ...and the rest of the keys. This is to make it so the layout is
+ // consistent with other pages (e.g. the search page, the by blame page, etc).
+ delete paramset.source_type;
+ this.paramset = paramset;
+ this._render();
+ sendEndTask(this);
+ })
+ .catch((e) => sendFetchError(this, e, 'paramset'));
+ }
+
+ private showQueryDialog() {
+ $$<QueryDialogSk>('query-dialog-sk')!.open(this.paramset, this.currentQuery);
+ }
+
+ private toggleAllDigests(e: Event) {
+ e.preventDefault();
+ this.showAllDigests = !this.showAllDigests;
+ this.stateChanged();
+ this._render();
+ this.fetch();
+ }
+
+ private toggleIgnoreRules(e: Event) {
+ e.preventDefault();
+ this.disregardIgnoreRules = !this.disregardIgnoreRules;
+ this.stateChanged();
+ this._render();
+ this.fetch();
+ }
+}
+
+define('list-page-sk', ListPageSk);
diff --git a/golden/modules/list-page-sk/list-page-sk_test.js b/golden/modules/list-page-sk/list-page-sk_test.ts
similarity index 65%
rename from golden/modules/list-page-sk/list-page-sk_test.js
rename to golden/modules/list-page-sk/list-page-sk_test.ts
index 1cd503b..207f9a8 100644
--- a/golden/modules/list-page-sk/list-page-sk_test.js
+++ b/golden/modules/list-page-sk/list-page-sk_test.ts
@@ -10,11 +10,19 @@
} from '../../../infra-sk/modules/test_util';
import { sampleByTestList } from './test_data';
import { testOnlySetSettings } from '../settings';
+import { ListPageSk } from './list-page-sk';
+import { expect } from 'chai';
+import { CorpusSelectorSk } from '../corpus-selector-sk/corpus-selector-sk';
+import { QueryDialogSk } from '../query-dialog-sk/query-dialog-sk';
+import { QueryDialogSkPO} from '../query-dialog-sk/query-dialog-sk_po';
+import { CorpusSelectorSkPO } from '../corpus-selector-sk/corpus-selector-sk_po';
describe('list-page-sk', () => {
- const newInstance = setUpElementUnderTest('list-page-sk');
+ const newInstance = setUpElementUnderTest<ListPageSk>('list-page-sk');
- let listPageSk;
+ let listPageSk: ListPageSk;
+ let queryDialogSkPO: QueryDialogSkPO;
+ let corpusSelectorSkPO: CorpusSelectorSkPO;
beforeEach(async () => {
// Clear out any query params we might have to not mess with our current state.
@@ -38,6 +46,12 @@
const event = eventPromise('end-task');
listPageSk = newInstance();
await event;
+
+ queryDialogSkPO =
+ new QueryDialogSkPO(listPageSk.querySelector<QueryDialogSk>('query-dialog-sk')!);
+ corpusSelectorSkPO =
+ new CorpusSelectorSkPO(
+ listPageSk.querySelector<CorpusSelectorSk<string>>('corpus-selector-sk')!);
});
afterEach(() => {
@@ -54,17 +68,24 @@
expect(rows).to.have.length(2);
});
- it('should have 3 corpora loaded in, with the default selected', () => {
- const corpusSelector = $$('corpus-selector-sk', listPageSk);
- expect(corpusSelector.corpora).to.have.length(3);
- expect(corpusSelector.selectedCorpus).to.equal('gm');
+ it('should have 3 corpora loaded in, with the default selected', async () => {
+ expect(await corpusSelectorSkPO.getCorpora()).to.have.length(3);
+ expect(await corpusSelectorSkPO.getSelectedCorpus()).to.equal('gm');
});
it('does not have source_type (corpus) in the params', () => {
- expect(listPageSk._paramset.source_type).to.be.undefined;
+ // Field "paramset" is private, thus the cast to any. Is this test really necessary?
+ expect((listPageSk as any).paramset.source_type).to.be.undefined;
});
- const expectedSearchPageHref = (opts) => {
+ const expectedSearchPageHref =
+ (opts: {
+ positive: boolean,
+ negative: boolean,
+ untriaged: boolean,
+ showAllDigests: boolean,
+ disregardIgnoreRules: boolean
+ }): string => {
return '/search?' + [
'corpus=gm',
`include_ignored=${opts.disregardIgnoreRules}`,
@@ -81,7 +102,8 @@
].join('&');
};
- const expectedClusterPageHref = (opts) => {
+ const expectedClusterPageHref =
+ (opts: {showAllDigests: boolean, disregardIgnoreRules: boolean}): string => {
return '/cluster?' + [
'corpus=gm',
'grouping=this_is_another_test',
@@ -100,8 +122,8 @@
};
it('should have links for searching and the cluster view', () => {
- const secondRow = $$('table tbody tr:nth-child(2)', listPageSk);
- const links = $('a', secondRow);
+ const secondRow = $$<HTMLTableRowElement>('table tbody tr:nth-child(2)', listPageSk)!;
+ const links = $<HTMLAnchorElement>('a', secondRow)!;
expect(links).to.have.length(6);
// First link should be to the search results for all digests.
@@ -154,11 +176,14 @@
expectedClusterPageHref({showAllDigests: false, disregardIgnoreRules: false}));
});
- it('updates the links based on toggle positions', () => {
- listPageSk._showAllDigests = true;
- listPageSk._disregardIgnoreRules = true;
- listPageSk._render();
- const secondRow = $$('table tbody tr:nth-child(2)', listPageSk);
+ it('updates the links based on toggle positions', async () => {
+ fetchMock.get('/json/v1/list?corpus=gm', sampleByTestList);
+ fetchMock.get('/json/v1/list?corpus=gm&include_ignored_traces=true', sampleByTestList);
+
+ await clickDigestsAtHeadOnlyCheckbox(listPageSk)
+ await clickDisregardIgnoreRulesCheckbox(listPageSk)
+
+ const secondRow = $$<HTMLTableRowElement>('table tbody tr:nth-child(2)', listPageSk)!;
const links = $('a', secondRow);
expect(links).to.have.length(6);
@@ -213,79 +238,84 @@
});
it('updates the sort order by clicking on sort-toggle-sk', async () => {
- let firstRow = $$('table tbody tr:nth-child(1)', listPageSk);
- expect($$('td', firstRow).innerText).to.equal('this_is_a_test');
+ let firstRow = $$<HTMLTableRowElement>('table tbody tr:nth-child(1)', listPageSk)!;
+ expect($$<HTMLTableDataCellElement>('td', firstRow)!.innerText).to.equal('this_is_a_test');
// After first click, it will be sorting in descending order by number of negatives.
clickOnNegativeHeader(listPageSk);
- firstRow = $$('table tbody tr:nth-child(1)', listPageSk);
- expect($$('td', firstRow).innerText).to.equal('this_is_another_test');
+ firstRow = $$<HTMLTableRowElement>('table tbody tr:nth-child(1)', listPageSk)!;
+ expect($$<HTMLTableDataCellElement>('td', firstRow)!.innerText)
+ .to.equal('this_is_another_test');
// After second click, it will be sorting in ascending order by number of negatives.
clickOnNegativeHeader(listPageSk);
- firstRow = $$('table tbody tr:nth-child(1)', listPageSk);
- expect($$('td', firstRow).innerText).to.equal('this_is_a_test');
+ firstRow = $$<HTMLTableRowElement>('table tbody tr:nth-child(1)', listPageSk)!;
+ expect($$<HTMLTableDataCellElement>('td', firstRow)!.innerText).to.equal('this_is_a_test');
});
}); // end describe('html layout')
describe('RPC calls', () => {
it('has a checkbox to toggle use of ignore rules', async () => {
- fetchMock.get('/json/v1/list?corpus=gm&at_head_only=true&include_ignored_traces=true', sampleByTestList);
+ fetchMock.get(
+ '/json/v1/list?corpus=gm&at_head_only=true&include_ignored_traces=true',
+ sampleByTestList);
- const checkbox = $$('checkbox-sk.ignore_rules input', listPageSk);
- const event = eventPromise('end-task');
- checkbox.click();
- await event;
+ await clickDisregardIgnoreRulesCheckbox(listPageSk);
expectQueryStringToEqual('?corpus=gm&disregard_ignores=true');
});
it('has a checkbox to toggle measuring at head', async () => {
fetchMock.get('/json/v1/list?corpus=gm', sampleByTestList);
- const checkbox = $$('checkbox-sk.head_only input', listPageSk);
- const event = eventPromise('end-task');
- checkbox.click();
- await event;
+ await clickDigestsAtHeadOnlyCheckbox(listPageSk);
expectQueryStringToEqual('?all_digests=true&corpus=gm');
});
it('changes the corpus based on an event from corpus-selector-sk', async () => {
- fetchMock.get('/json/v1/list?corpus=corpus%20with%20spaces&at_head_only=true', sampleByTestList);
+ fetchMock.get(
+ '/json/v1/list?corpus=corpus%20with%20spaces&at_head_only=true', sampleByTestList);
- const corpusSelector = $$('corpus-selector-sk', listPageSk);
const event = eventPromise('end-task');
- corpusSelector.dispatchEvent(
- new CustomEvent('corpus-selected', {
- detail: 'corpus with spaces',
- bubbles: true,
- }),
- );
+ await corpusSelectorSkPO.clickCorpus('corpus with spaces');
await event;
+
expectQueryStringToEqual('?corpus=corpus%20with%20spaces');
});
it('changes the search params based on an event from query-dialog-sk', async () => {
fetchMock.get(
- '/json/v1/list?corpus=gm&at_head_only=true&trace_values=alpha_type%3DOpaque%26arch%3Darm64',
+ '/json/v1/list?' +
+ 'corpus=gm&at_head_only=true&trace_values=alpha_type%3DOpaque%26arch%3Darm64',
sampleByTestList,
);
- const queryDialog = $$('query-dialog-sk', listPageSk);
const event = eventPromise('end-task');
- queryDialog.dispatchEvent(
- new CustomEvent('edit', {
- detail: 'alpha_type=Opaque&arch=arm64',
- bubbles: true,
- }),
- );
+ $$<HTMLButtonElement>('.show_query_dialog', listPageSk)!.click();
+ await queryDialogSkPO.setSelection({'alpha_type': ['Opaque'], 'arch': ['arm64']});
+ await queryDialogSkPO.clickShowMatchesBtn();
await event;
+
expectQueryStringToEqual('?corpus=gm&query=alpha_type%3DOpaque%26arch%3Darm64');
});
});
});
-function clickOnNegativeHeader(ele) {
- $$('table > thead > tr > th:nth-child(3)', ele).click();
+function clickOnNegativeHeader(ele: ListPageSk) {
+ $$<HTMLTableHeaderCellElement>('table > thead > tr > th:nth-child(3)', ele)!.click();
+}
+
+async function clickDigestsAtHeadOnlyCheckbox(listPageSk: ListPageSk) {
+ const checkbox = $$<HTMLInputElement>('checkbox-sk.head_only input', listPageSk)!;
+ const event = eventPromise('end-task');
+ checkbox.click();
+ await event;
+}
+
+async function clickDisregardIgnoreRulesCheckbox(listPageSk: ListPageSk) {
+ const checkbox = $$<HTMLInputElement>('checkbox-sk.ignore_rules input', listPageSk)!;
+ const event = eventPromise('end-task');
+ checkbox.click();
+ await event;
}
diff --git a/golden/modules/list-page-sk/test_data.js b/golden/modules/list-page-sk/test_data.js
deleted file mode 100644
index 1f02426..0000000
--- a/golden/modules/list-page-sk/test_data.js
+++ /dev/null
@@ -1,11 +0,0 @@
-export const sampleByTestList = [{
- name: 'this_is_a_test',
- positive_digests: 19,
- negative_digests: 24,
- untriaged_digests: 103,
-}, {
- name: 'this_is_another_test',
- positive_digests: 79,
- negative_digests: 48,
- untriaged_digests: 3,
-}];
diff --git a/golden/modules/list-page-sk/test_data.ts b/golden/modules/list-page-sk/test_data.ts
new file mode 100644
index 0000000..f2c02e4
--- /dev/null
+++ b/golden/modules/list-page-sk/test_data.ts
@@ -0,0 +1,17 @@
+import {ListTestsResponse} from '../rpc_types';
+
+export const sampleByTestList: ListTestsResponse = {
+ tests: [{
+ name: 'this_is_a_test',
+ positive_digests: 19,
+ negative_digests: 24,
+ untriaged_digests: 103,
+ total_digests: 146,
+ }, {
+ name: 'this_is_another_test',
+ positive_digests: 79,
+ negative_digests: 48,
+ untriaged_digests: 3,
+ total_digests: 130,
+ }],
+};
diff --git a/golden/modules/rpc_types.ts b/golden/modules/rpc_types.ts
index 2ed23e0..7283b13 100644
--- a/golden/modules/rpc_types.ts
+++ b/golden/modules/rpc_types.ts
@@ -199,6 +199,18 @@
rules: IgnoreRule[] | null;
}
+export interface TestSummary {
+ name: TestName;
+ positive_digests: number;
+ negative_digests: number;
+ untriaged_digests: number;
+ total_digests: number;
+}
+
+export interface ListTestsResponse {
+ tests: TestSummary[] | null;
+}
+
export type ParamSet = { [key: string]: string[] };
export type ParamSetResponse = { [key: string]: string[] | null };