[gold] Port ignores-page-sk to TypeScript.
Bug: skia:10246
Change-Id: I206104a202db702fa7c1f460b941a98d1e476286
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/401224
Commit-Queue: Leandro Lovisolo <lovisolo@google.com>
Reviewed-by: 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 f6c8778..c95fc4c 100644
--- a/golden/go/web/frontend/generate_typescript_rpc_types/main.go
+++ b/golden/go/web/frontend/generate_typescript_rpc_types/main.go
@@ -64,6 +64,12 @@
// Response for the /json/v1/changelists RPC endpoint.
generator.Add(frontend.ChangelistsResponse{})
+ // Payload for the /json/v1/ignores/add and /json/v1/ignores/save RPC endpoints.
+ generator.Add(frontend.IgnoreRuleBody{})
+
+ // Response for the /json/v1/ignores RPC endpoint.
+ generator.Add(frontend.IgnoresResponse{})
+
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 8d6c907..9ff1e18 100644
--- a/golden/go/web/frontend/types.go
+++ b/golden/go/web/frontend/types.go
@@ -168,6 +168,11 @@
Digests []types.Digest `json:"digests"`
}
+// IgnoresResponse is the response for /json/v1/ignores.
+type IgnoresResponse struct {
+ Rules []IgnoreRule `json:"rules"`
+}
+
// IgnoreRule represents an ignore.Rule as well as how many times the rule
// was applied. This allows for the decoupling of the rule as stored in the
// DB from how we present it to the UI.
diff --git a/golden/go/web/web.go b/golden/go/web/web.go
index 0c8aef6..bf71344 100644
--- a/golden/go/web/web.go
+++ b/golden/go/web/web.go
@@ -728,25 +728,29 @@
return
}
- sendJSONResponse(w, ignores)
+ response := frontend.IgnoresResponse{
+ Rules: ignores,
+ }
+
+ sendJSONResponse(w, response)
}
// getIgnores fetches the ignores from the store and optionally counts how many
// times they are applied.
-func (wh *Handlers) getIgnores(ctx context.Context, withCounts bool) ([]*frontend.IgnoreRule, error) {
+func (wh *Handlers) getIgnores(ctx context.Context, withCounts bool) ([]frontend.IgnoreRule, error) {
rules, err := wh.IgnoreStore.List(ctx)
if err != nil {
return nil, skerr.Wrapf(err, "fetching ignores from store")
}
// We want to make a slice of pointers because addIgnoreCounts will add the counts in-place.
- ret := make([]*frontend.IgnoreRule, 0, len(rules))
+ ret := make([]frontend.IgnoreRule, 0, len(rules))
for _, r := range rules {
fr, err := frontend.ConvertIgnoreRule(r)
if err != nil {
return nil, skerr.Wrap(err)
}
- ret = append(ret, &fr)
+ ret = append(ret, fr)
}
if withCounts {
@@ -762,7 +766,7 @@
// addIgnoreCounts goes through the whole tile and counts how many traces each of the rules
// applies to. This uses the most recent index, so there may be some discrepancies in the counts
// if a new rule has been added since the last index was computed.
-func (wh *Handlers) addIgnoreCounts(ctx context.Context, rules []*frontend.IgnoreRule) error {
+func (wh *Handlers) addIgnoreCounts(ctx context.Context, rules []frontend.IgnoreRule) error {
defer metrics2.FuncTimer().Stop()
sklog.Debugf("adding counts to %d rules", len(rules))
@@ -830,11 +834,11 @@
}
mutex.Lock()
defer mutex.Unlock()
- for i, r := range rules {
- r.Count += ruleCounts[i].Count
- r.UntriagedCount += ruleCounts[i].UntriagedCount
- r.ExclusiveCount += ruleCounts[i].ExclusiveCount
- r.ExclusiveUntriagedCount += ruleCounts[i].ExclusiveUntriagedCount
+ for i := range rules {
+ (&rules[i]).Count += ruleCounts[i].Count
+ (&rules[i]).UntriagedCount += ruleCounts[i].UntriagedCount
+ (&rules[i]).ExclusiveCount += ruleCounts[i].ExclusiveCount
+ (&rules[i]).ExclusiveUntriagedCount += ruleCounts[i].ExclusiveUntriagedCount
}
return nil
})
diff --git a/golden/go/web/web_test.go b/golden/go/web/web_test.go
index f46cec2..93d4c06 100644
--- a/golden/go/web/web_test.go
+++ b/golden/go/web/web_test.go
@@ -989,9 +989,9 @@
}, dlr)
}
-// TestGetIgnores_NoCounts_SunnyDay_Success tests the case where we simply return the list of the
-// current ignore rules, without counting any of the traces to which they apply.
-func TestGetIgnores_NoCounts_SunnyDay_Success(t *testing.T) {
+// TestListIgnoreRules_NoCounts_SunnyDay_Success tests the case where we simply return the list of
+// the current ignore rules, without counting any of the traces to which they apply.
+func TestListIgnoreRules_NoCounts_SunnyDay_Success(t *testing.T) {
unittest.SmallTest(t)
mis := &mock_ignore.Store{}
@@ -1000,45 +1000,52 @@
mis.On("List", testutils.AnyContext).Return(makeIgnoreRules(), nil)
wh := Handlers{
+ anonymousCheapQuota: rate.NewLimiter(rate.Inf, 1),
HandlersConfig: HandlersConfig{
IgnoreStore: mis,
},
}
- xir, err := wh.getIgnores(context.Background(), false)
+ expectedResponse := frontend.IgnoresResponse{
+ Rules: []frontend.IgnoreRule{
+ {
+ ID: "1234",
+ CreatedBy: "user@example.com",
+ UpdatedBy: "user2@example.com",
+ Expires: firstRuleExpire,
+ Query: "device=delta",
+ Note: "Flaky driver",
+ },
+ {
+ ID: "5678",
+ CreatedBy: "user2@example.com",
+ UpdatedBy: "user@example.com",
+ Expires: secondRuleExpire,
+ Query: "name=test_two&source_type=gm",
+ Note: "Not ready yet",
+ },
+ {
+ ID: "-1",
+ CreatedBy: "user3@example.com",
+ UpdatedBy: "user3@example.com",
+ Expires: thirdRuleExpire,
+ Query: "matches=nothing",
+ Note: "Oops, this matches nothing",
+ },
+ },
+ }
+
+ w := httptest.NewRecorder()
+ r := httptest.NewRequest(http.MethodGet, "/json/v1/ignores", nil)
+ wh.ListIgnoreRules(w, r)
+ b, err := json.Marshal(expectedResponse)
require.NoError(t, err)
- clearParsedQueries(xir)
- assert.Equal(t, []*frontend.IgnoreRule{
- {
- ID: "1234",
- CreatedBy: "user@example.com",
- UpdatedBy: "user2@example.com",
- Expires: firstRuleExpire,
- Query: "device=delta",
- Note: "Flaky driver",
- },
- {
- ID: "5678",
- CreatedBy: "user2@example.com",
- UpdatedBy: "user@example.com",
- Expires: secondRuleExpire,
- Query: "name=test_two&source_type=gm",
- Note: "Not ready yet",
- },
- {
- ID: "-1",
- CreatedBy: "user3@example.com",
- UpdatedBy: "user3@example.com",
- Expires: thirdRuleExpire,
- Query: "matches=nothing",
- Note: "Oops, this matches nothing",
- },
- }, xir)
+ assertJSONResponseWas(t, http.StatusOK, string(b), w)
}
-// TestGetIgnores_WithCounts_SunnyDay_Success tests the case where we get the list of current ignore
-// rules and count the traces to which those rules apply.
-func TestGetIgnores_WithCounts_SunnyDay_Success(t *testing.T) {
+// TestListIgnoreRules_WithCounts_SunnyDay_Success tests the case where we get the list of current
+// ignore rules and count the traces to which those rules apply.
+func TestListIgnoreRules_WithCounts_SunnyDay_Success(t *testing.T) {
unittest.SmallTest(t)
mes := &mock_expectations.Store{}
@@ -1061,6 +1068,7 @@
mis.On("List", testutils.AnyContext).Return(makeIgnoreRules(), nil)
wh := Handlers{
+ anonymousExpensiveQuota: rate.NewLimiter(rate.Inf, 1),
HandlersConfig: HandlersConfig{
ExpectationsStore: mes,
IgnoreStore: mis,
@@ -1068,52 +1076,58 @@
},
}
- xir, err := wh.getIgnores(context.Background(), true /* = withCounts*/)
+ expectedResponse := frontend.IgnoresResponse{
+ Rules: []frontend.IgnoreRule{
+ {
+ ID: "1234",
+ CreatedBy: "user@example.com",
+ UpdatedBy: "user2@example.com",
+ Expires: firstRuleExpire,
+ Query: "device=delta",
+ Note: "Flaky driver",
+ Count: 2,
+ ExclusiveCount: 1,
+ UntriagedCount: 1,
+ ExclusiveUntriagedCount: 0,
+ },
+ {
+ ID: "5678",
+ CreatedBy: "user2@example.com",
+ UpdatedBy: "user@example.com",
+ Expires: secondRuleExpire,
+ Query: "name=test_two&source_type=gm",
+ Note: "Not ready yet",
+ Count: 4,
+ ExclusiveCount: 3,
+ UntriagedCount: 2,
+ ExclusiveUntriagedCount: 1,
+ },
+ {
+ ID: "-1",
+ CreatedBy: "user3@example.com",
+ UpdatedBy: "user3@example.com",
+ Expires: thirdRuleExpire,
+ Query: "matches=nothing",
+ Note: "Oops, this matches nothing",
+ Count: 0,
+ ExclusiveCount: 0,
+ UntriagedCount: 0,
+ ExclusiveUntriagedCount: 0,
+ },
+ },
+ }
+
+ w := httptest.NewRecorder()
+ r := httptest.NewRequest(http.MethodGet, "/json/v1/ignores?counts=1", nil)
+ wh.ListIgnoreRules(w, r)
+ b, err := json.Marshal(expectedResponse)
require.NoError(t, err)
- clearParsedQueries(xir)
- assert.Equal(t, []*frontend.IgnoreRule{
- {
- ID: "1234",
- CreatedBy: "user@example.com",
- UpdatedBy: "user2@example.com",
- Expires: firstRuleExpire,
- Query: "device=delta",
- Note: "Flaky driver",
- Count: 2,
- ExclusiveCount: 1,
- UntriagedCount: 1,
- ExclusiveUntriagedCount: 0,
- },
- {
- ID: "5678",
- CreatedBy: "user2@example.com",
- UpdatedBy: "user@example.com",
- Expires: secondRuleExpire,
- Query: "name=test_two&source_type=gm",
- Note: "Not ready yet",
- Count: 4,
- ExclusiveCount: 3,
- UntriagedCount: 2,
- ExclusiveUntriagedCount: 1,
- },
- {
- ID: "-1",
- CreatedBy: "user3@example.com",
- UpdatedBy: "user3@example.com",
- Expires: thirdRuleExpire,
- Query: "matches=nothing",
- Note: "Oops, this matches nothing",
- Count: 0,
- ExclusiveCount: 0,
- UntriagedCount: 0,
- ExclusiveUntriagedCount: 0,
- },
- }, xir)
+ assertJSONResponseWas(t, http.StatusOK, string(b), w)
}
-// TestGetIgnores_WithCountsOnBigTile_SunnyDay_NoRaceConditions uses an artificially bigger tile to
-// process to make sure the counting code has no races in it when sharded.
-func TestGetIgnores_WithCountsOnBigTile_SunnyDay_NoRaceConditions(t *testing.T) {
+// TestListIgnoreRules_WithCountsOnBigTile_SunnyDay_NoRaceConditions uses an artificially bigger
+// tile to process to make sure the counting code has no races in it when sharded.
+func TestListIgnoreRules_WithCountsOnBigTile_SunnyDay_NoRaceConditions(t *testing.T) {
unittest.SmallTest(t)
mes := &mock_expectations.Store{}
@@ -1134,6 +1148,7 @@
mis.On("List", testutils.AnyContext).Return(makeIgnoreRules(), nil)
wh := Handlers{
+ anonymousExpensiveQuota: rate.NewLimiter(rate.Inf, 1),
HandlersConfig: HandlersConfig{
ExpectationsStore: mes,
IgnoreStore: mis,
@@ -1141,10 +1156,15 @@
},
}
- xir, err := wh.getIgnores(context.Background(), true /* = withCounts*/)
- require.NoError(t, err)
+ w := httptest.NewRecorder()
+ r := httptest.NewRequest(http.MethodGet, "/json/v1/ignores?counts=1", nil)
+ wh.ListIgnoreRules(w, r)
+ responseBytes := assertJSONResponseAndReturnBody(t, http.StatusOK, w)
+ response := frontend.IgnoresResponse{}
+ require.NoError(t, json.Unmarshal(responseBytes, &response))
+
// Just check the length, other unit tests will validate the correctness.
- assert.Len(t, xir, 3)
+ assert.Len(t, response.Rules, 3)
}
// TestHandlersThatRequireLogin_NotLoggedIn_UnauthorizedError tests a list of handlers to make sure
@@ -3175,27 +3195,26 @@
}
}
-// clearParsedQueries removes the implementation detail parts of the IgnoreRule that don't make
-// sense to assert against.
-func clearParsedQueries(xir []*frontend.IgnoreRule) {
- for _, ir := range xir {
- ir.ParsedQuery = nil
- }
-}
-
-// assertJSONResponseWasOK asserts that the given ResponseRecorder was given the appropriate JSON
-// headers and saw a status OK (200) response.
-func assertJSONResponseWas(t *testing.T, status int, body string, w *httptest.ResponseRecorder) {
+// assertJSONResponseAndReturnBody asserts that the given ResponseRecorder was given the
+// appropriate JSON and the expected status code, and returns the response body.
+func assertJSONResponseAndReturnBody(t *testing.T, expectedStatusCode int, w *httptest.ResponseRecorder) []byte {
resp := w.Result()
- assert.Equal(t, status, resp.StatusCode)
+ assert.Equal(t, expectedStatusCode, resp.StatusCode)
assert.Equal(t, jsonContentType, resp.Header.Get(contentTypeHeader))
assert.Equal(t, allowAllOrigins, resp.Header.Get(accessControlHeader))
assert.Equal(t, noSniffContent, resp.Header.Get(contentTypeOptionsHeader))
respBody, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
+ return respBody
+}
+
+// assertJSONResponseWas asserts that the given ResponseRecorder was given the appropriate JSON
+// headers and the expected status code and response body.
+func assertJSONResponseWas(t *testing.T, expectedStatusCode int, expectedBody string, w *httptest.ResponseRecorder) {
+ actualBody := assertJSONResponseAndReturnBody(t, expectedStatusCode, w)
// The JSON encoder includes a newline "\n" at the end of the body, which is awkward to include
// in the literals passed in above, so we add that here
- assert.Equal(t, body+"\n", string(respBody))
+ assert.Equal(t, expectedBody+"\n", string(actualBody))
}
func assertImageResponseWas(t *testing.T, expected []byte, w *httptest.ResponseRecorder) {
diff --git a/golden/modules/ignores-page-sk/ignores-page-sk-demo.js b/golden/modules/ignores-page-sk/ignores-page-sk-demo.ts
similarity index 88%
rename from golden/modules/ignores-page-sk/ignores-page-sk-demo.js
rename to golden/modules/ignores-page-sk/ignores-page-sk-demo.ts
index 650cc35..24f78ff 100644
--- a/golden/modules/ignores-page-sk/ignores-page-sk-demo.js
+++ b/golden/modules/ignores-page-sk/ignores-page-sk-demo.ts
@@ -1,7 +1,6 @@
import './index';
import '../gold-scaffold-sk';
-import { $$ } from 'common-sk/modules/dom';
import { delay } from '../demo_util';
import { ignoreRules_10, fakeNow } from './test_data';
import { manyParams } from '../shared_demo_data';
@@ -25,8 +24,8 @@
// 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.
+// Make it the first element in body.
+document.body.insertBefore(newScaf, document.body.childNodes[0]);
const page = document.createElement('ignores-page-sk');
page.setAttribute('page_size', '10');
newScaf.appendChild(page);
diff --git a/golden/modules/ignores-page-sk/ignores-page-sk.js b/golden/modules/ignores-page-sk/ignores-page-sk.js
deleted file mode 100644
index 0433949..0000000
--- a/golden/modules/ignores-page-sk/ignores-page-sk.js
+++ /dev/null
@@ -1,247 +0,0 @@
-/**
- * @module module/ignores-page-sk
- * @description <h2><code>ignores-page-sk</code></h2>
- *
- * Page to view/edit/delete ignore rules.
- */
-
-import * as human from 'common-sk/modules/human';
-import dialogPolyfill from 'dialog-polyfill';
-
-import { $$ } from 'common-sk/modules/dom';
-import { classMap } from 'lit-html/directives/class-map';
-import { define } from 'elements-sk/define';
-import { html } from 'lit-html';
-import { stateReflector } from 'common-sk/modules/stateReflector';
-import { jsonOrThrow } from 'common-sk/modules/jsonOrThrow';
-import { ElementSk } from '../../../infra-sk/modules/ElementSk';
-import { escapeAndLinkify } from '../../../infra-sk/modules/linkify';
-import {
- humanReadableQuery, sendBeginTask, sendEndTask, sendFetchError,
-} from '../common';
-
-import '../../../infra-sk/modules/confirm-dialog-sk';
-import '../edit-ignore-rule-sk';
-import 'elements-sk/checkbox-sk';
-import 'elements-sk/icon/delete-icon-sk';
-import 'elements-sk/icon/info-outline-icon-sk';
-import 'elements-sk/icon/mode-edit-icon-sk';
-import 'elements-sk/styles/buttons';
-
-const template = (ele) => html`
-<div class=controls>
- <checkbox-sk label="Only count traces with untriaged digests"
- ?checked=${!ele._countAllTraces} @click=${ele._toggleCountAll}></checkbox-sk>
-
- <button @click=${ele._newIgnoreRule} class=create>Create new ignore rule</button>
-</div>
-
-<confirm-dialog-sk></confirm-dialog-sk>
-
-<dialog id=edit-ignore-rule-dialog>
- <h2>${ele._ruleID ? 'Edit Ignore Rule' : 'Create Ignore Rule'}</h2>
- <edit-ignore-rule-sk .paramset=${ele._paramset}></edit-ignore-rule-sk>
- <button @click=${() => ele._editIgnoreRuleDialog.close()}>Cancel</button>
- <button id=ok class=action @click=${ele._saveIgnoreRule}>
- ${ele._ruleID ? 'Update' : 'Create'}
- </button>
-</dialog>
-
-<table>
- <thead>
- <tr>
- <th colspan=2>Filter</th>
- <th>Note</th>
- <th> Traces matched <br> exclusive/all
- <info-outline-icon-sk class=small-icon
- title="'all' is the number of traces that a given ignore rule applies to. 'exclusive' \
-is the number of traces which are matched by the given ignore rule and no other ignore rule of the \
-rules in this list. If the checkbox is checked to only count traces with untriaged digests, it \
-means 'untriaged digests at head', which is typically an indication of a flaky test/config.">
- </info-outline-icon-sk>
- </th>
- <th>Expires in</th>
- <th>Created by</th>
- <th>Updated by</th>
- </tr>
- </thead>
- <tbody>
- ${ele._rules.map((r) => ruleTemplate(ele, r))}
- </tbody>
-</table>`;
-
-const ruleTemplate = (ele, r) => {
- const isExpired = Date.parse(r.expires) < Date.now();
- return html`
-<tr class=${classMap({ expired: isExpired })}>
- <td class=mutate-icons>
- <mode-edit-icon-sk title="Edit this rule."
- @click=${() => ele._editIgnoreRule(r)}></mode-edit-icon-sk>
- <delete-icon-sk title="Delete this rule."
- @click=${() => ele._deleteIgnoreRule(r)}></delete-icon-sk>
- </td>
- <td class=query><a href=${`/list?include=true&query=${encodeURIComponent(r.query)}`}
- >${humanReadableQuery(r.query)}</a></td>
- <td>${escapeAndLinkify(r.note) || '--'}</td>
- <td class=matches title="These counts are recomputed every few minutes.">
- ${ele._countAllTraces ? r.exclusiveCountAll : r.exclusiveCount} /
- ${ele._countAllTraces ? r.countAll : r.count}
- </td>
- <td class=${classMap({ expired: isExpired })}>
- ${isExpired ? 'Expired' : human.diffDate(r.expires)}
- </td>
- <td title=${`Originally created by ${r.name}`}>${trimEmail(r.name)}</td>
- <td title=${`Last updated by ${r.updatedBy}`}>
- ${r.name === r.updatedBy ? '' : trimEmail(r.updatedBy)}
- </td>
-</tr>`;
-};
-
-function trimEmail(s) {
- return `${s.split('@')[0]}@`;
-}
-
-define('ignores-page-sk', class extends ElementSk {
- constructor() {
- super(template);
-
- this._rules = [];
- this._paramset = {};
- this._countAllTraces = false;
-
- this._stateChanged = stateReflector(
- /* getState */() => ({
- // provide empty values
- count_all: this._countAllTraces,
- }), /* setState */(newState) => {
- if (!this._connected) {
- return;
- }
-
- // default values if not specified.
- this._countAllTraces = newState.count_all || false;
- this._fetch();
- this._render();
- },
- );
- // Allows us to abort fetches if we fetch again.
- this._fetchController = null;
- // This is the dialog element for creating or editing rules.
- this._editIgnoreRuleDialog = null;
- this._ruleID = '';
- }
-
- connectedCallback() {
- super.connectedCallback();
- this._render();
- this._editIgnoreRuleDialog = $$('#edit-ignore-rule-dialog', this);
- dialogPolyfill.registerDialog(this._editIgnoreRuleDialog);
- }
-
- _deleteIgnoreRule(rule) {
- const dialog = $$('confirm-dialog-sk', this);
- dialog.open('Are you sure you want to delete '
- + 'this ignore rule?').then(() => {
- sendBeginTask(this);
- fetch(`/json/v1/ignores/del/${rule.id}`, {
- method: 'POST',
- }).then(jsonOrThrow).then(() => {
- this._fetch();
- sendEndTask(this);
- }).catch((e) => sendFetchError(this, e, 'deleting ignore'));
- });
- }
-
- _editIgnoreRule(rule) {
- const editor = $$('edit-ignore-rule-sk', this);
- editor.reset();
- editor.query = rule.query;
- editor.note = rule.note;
- editor.expires = rule.expires;
- this._ruleID = rule.id;
- this._render();
- this._editIgnoreRuleDialog.showModal();
- }
-
- _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);
-
- // We always want the counts of the ignore rules, thus the parameter counts=1.
- fetch('/json/v1/ignores?counts=1', extra)
- .then(jsonOrThrow)
- .then((arr) => {
- this._rules = arr || [];
- this._render();
- sendEndTask(this);
- })
- .catch((e) => sendFetchError(this, e, 'ignores'));
-
- fetch('/json/v1/paramset', extra)
- .then(jsonOrThrow)
- .then((paramset) => {
- this._paramset = paramset;
- this._render();
- sendEndTask(this);
- })
- .catch((e) => sendFetchError(this, e, 'paramset'));
- }
-
- _newIgnoreRule() {
- const editor = $$('edit-ignore-rule-sk', this);
- editor.reset();
- this._ruleID = '';
- this._render();
- this._editIgnoreRuleDialog.showModal();
- }
-
- _saveIgnoreRule() {
- const editor = $$('edit-ignore-rule-sk', this);
- if (editor.verifyFields()) {
- const body = {
- duration: editor.expires,
- filter: editor.query,
- note: editor.note,
- };
- // TODO(kjlubick) remove the / from the json endpoint
- let url = '/json/v1/ignores/add/';
- if (this._ruleID) {
- url = `/json/v1/ignores/save/${this._ruleID}`;
- }
-
- sendBeginTask(this);
- fetch(url, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(body),
- }).then(jsonOrThrow).then(() => {
- this._fetch();
- sendEndTask(this);
- }).catch((e) => sendFetchError(this, e, 'saving ignore'));
-
- editor.reset();
- this._editIgnoreRuleDialog.close();
- }
- }
-
- _toggleCountAll(e) {
- e.preventDefault();
- this._countAllTraces = !this._countAllTraces;
- this._stateChanged();
- this._render();
- }
-});
diff --git a/golden/modules/ignores-page-sk/ignores-page-sk.ts b/golden/modules/ignores-page-sk/ignores-page-sk.ts
new file mode 100644
index 0000000..3c21e05
--- /dev/null
+++ b/golden/modules/ignores-page-sk/ignores-page-sk.ts
@@ -0,0 +1,255 @@
+/**
+ * @module module/ignores-page-sk
+ * @description <h2><code>ignores-page-sk</code></h2>
+ *
+ * Page to view/edit/delete ignore rules.
+ */
+
+import * as human from 'common-sk/modules/human';
+import dialogPolyfill from 'dialog-polyfill';
+
+import { classMap } from 'lit-html/directives/class-map';
+import { define } from 'elements-sk/define';
+import { html } from 'lit-html';
+import { stateReflector } from 'common-sk/modules/stateReflector';
+import { jsonOrThrow } from 'common-sk/modules/jsonOrThrow';
+import { ElementSk } from '../../../infra-sk/modules/ElementSk';
+import { escapeAndLinkify } from '../../../infra-sk/modules/linkify';
+import {
+ humanReadableQuery, sendBeginTask, sendEndTask, sendFetchError,
+} from '../common';
+import { IgnoreRule, IgnoreRuleBody, IgnoresResponse, ParamSet } from '../rpc_types';
+import { EditIgnoreRuleSk } from '../edit-ignore-rule-sk/edit-ignore-rule-sk';
+import { ConfirmDialogSk } from '../../../infra-sk/modules/confirm-dialog-sk/confirm-dialog-sk';
+
+import '../../../infra-sk/modules/confirm-dialog-sk';
+import '../edit-ignore-rule-sk';
+import 'elements-sk/checkbox-sk';
+import 'elements-sk/icon/delete-icon-sk';
+import 'elements-sk/icon/info-outline-icon-sk';
+import 'elements-sk/icon/mode-edit-icon-sk';
+import 'elements-sk/styles/buttons';
+
+function trimEmail(s: string) {
+ return `${s.split('@')[0]}@`;
+}
+
+export class IgnoresPageSk extends ElementSk {
+ private static template = (ele: IgnoresPageSk) => html`
+ <div class=controls>
+ <checkbox-sk label="Only count traces with untriaged digests"
+ ?checked=${!ele.countAllTraces} @click=${ele.toggleCountAll}></checkbox-sk>
+
+ <button @click=${ele.newIgnoreRule} class=create>Create new ignore rule</button>
+ </div>
+
+ <confirm-dialog-sk></confirm-dialog-sk>
+
+ <dialog id=edit-ignore-rule-dialog>
+ <h2>${ele.ruleID ? 'Edit Ignore Rule' : 'Create Ignore Rule'}</h2>
+ <edit-ignore-rule-sk .paramset=${ele.paramset}></edit-ignore-rule-sk>
+ <button @click=${() => ele.editIgnoreRuleDialog?.close()}>Cancel</button>
+ <button id=ok class=action @click=${ele.saveIgnoreRule}>
+ ${ele.ruleID ? 'Update' : 'Create'}
+ </button>
+ </dialog>
+
+ <table>
+ <thead>
+ <tr>
+ <th colspan=2>Filter</th>
+ <th>Note</th>
+ <th> Traces matched <br> exclusive/all
+ <info-outline-icon-sk class=small-icon
+ title="'all' is the number of traces that a given ignore rule applies to. \
+ 'exclusive' is the number of traces which are matched by the given ignore rule and no other \
+ ignore rule of the rules in this list. If the checkbox is checked to only count traces with \
+ untriaged digests, it means 'untriaged digests at head', which is typically an indication of \
+ a flaky test/config.">
+ </info-outline-icon-sk>
+ </th>
+ <th>Expires in</th>
+ <th>Created by</th>
+ <th>Updated by</th>
+ </tr>
+ </thead>
+ <tbody>
+ ${ele.rules.map((r) => IgnoresPageSk.ruleTemplate(ele, r))}
+ </tbody>
+ </table>
+ `;
+
+ private static ruleTemplate = (ele: IgnoresPageSk, r: IgnoreRule) => {
+ const isExpired = Date.parse(r.expires) < Date.now();
+ return html`
+ <tr class=${classMap({ expired: isExpired })}>
+ <td class=mutate-icons>
+ <mode-edit-icon-sk title="Edit this rule."
+ @click=${() => ele.editIgnoreRule(r)}></mode-edit-icon-sk>
+ <delete-icon-sk title="Delete this rule."
+ @click=${() => ele.deleteIgnoreRule(r)}></delete-icon-sk>
+ </td>
+ <td class=query><a href=${`/list?include=true&query=${encodeURIComponent(r.query)}`}
+ >${humanReadableQuery(r.query)}</a></td>
+ <td>${escapeAndLinkify(r.note) || '--'}</td>
+ <td class=matches title="These counts are recomputed every few minutes.">
+ ${ele.countAllTraces ? r.exclusiveCountAll : r.exclusiveCount} /
+ ${ele.countAllTraces ? r.countAll : r.count}
+ </td>
+ <td class=${classMap({ expired: isExpired })}>
+ ${isExpired ? 'Expired' : human.diffDate(r.expires)}
+ </td>
+ <td title=${`Originally created by ${r.name}`}>${trimEmail(r.name)}</td>
+ <td title=${`Last updated by ${r.updatedBy}`}>
+ ${r.name === r.updatedBy ? '' : trimEmail(r.updatedBy)}
+ </td>
+ </tr>
+ `;
+ };
+
+ private rules: IgnoreRule[] = [];
+ private paramset: ParamSet = {};
+ private countAllTraces = false;
+ private ruleID = '';
+
+ private editIgnoreRuleDialog?: HTMLDialogElement; // Dialog for creating or editing rules.
+ private editIgnoreRuleSk?: EditIgnoreRuleSk;
+ private confirmDialogSk?: ConfirmDialogSk;
+
+ private readonly stateChanged: () => void;
+ private fetchController?: AbortController; // Allows us to abort fetches if we fetch again.
+
+ constructor() {
+ super(IgnoresPageSk.template);
+
+ this.stateChanged = stateReflector(
+ /* getState */() => ({
+ // provide empty values
+ count_all: this.countAllTraces,
+ }), /* setState */(newState) => {
+ if (!this._connected) {
+ return;
+ }
+
+ // default values if not specified.
+ this.countAllTraces = newState.count_all as boolean || false;
+ this.fetch();
+ this._render();
+ },
+ );
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this._render();
+ this.editIgnoreRuleDialog = this.querySelector<HTMLDialogElement>('#edit-ignore-rule-dialog')!;
+ dialogPolyfill.registerDialog(this.editIgnoreRuleDialog);
+ this.editIgnoreRuleSk = this.querySelector<EditIgnoreRuleSk>('edit-ignore-rule-sk')!;
+ this.confirmDialogSk = this.querySelector<ConfirmDialogSk>('confirm-dialog-sk')!;
+ }
+
+ private deleteIgnoreRule(rule: IgnoreRule) {
+ this.confirmDialogSk!.open('Are you sure you want to delete '
+ + 'this ignore rule?').then(() => {
+ sendBeginTask(this);
+ fetch(`/json/v1/ignores/del/${rule.id}`, {
+ method: 'POST',
+ }).then(jsonOrThrow).then(() => {
+ this.fetch();
+ sendEndTask(this);
+ }).catch((e) => sendFetchError(this, e, 'deleting ignore'));
+ });
+ }
+
+ private editIgnoreRule(rule: IgnoreRule) {
+ this.editIgnoreRuleSk!.reset();
+ this.editIgnoreRuleSk!.query = rule.query;
+ this.editIgnoreRuleSk!.note = rule.note;
+ this.editIgnoreRuleSk!.expires = rule.expires;
+ this.ruleID = rule.id;
+ this._render();
+ this.editIgnoreRuleDialog!.showModal();
+ }
+
+ 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);
+
+ // We always want the counts of the ignore rules, thus the parameter counts=1.
+ fetch('/json/v1/ignores?counts=1', extra)
+ .then(jsonOrThrow)
+ .then((response: IgnoresResponse) => {
+ this.rules = response.rules || [];
+ this._render();
+ sendEndTask(this);
+ })
+ .catch((e) => sendFetchError(this, e, 'ignores'));
+
+ fetch('/json/v1/paramset', extra)
+ .then(jsonOrThrow)
+ .then((paramset: ParamSet) => {
+ this.paramset = paramset;
+ this._render();
+ sendEndTask(this);
+ })
+ .catch((e) => sendFetchError(this, e, 'paramset'));
+ }
+
+ private newIgnoreRule() {
+ this.editIgnoreRuleSk!.reset();
+ this.ruleID = '';
+ this._render();
+ this.editIgnoreRuleDialog!.showModal();
+ }
+
+ private saveIgnoreRule() {
+ if (this.editIgnoreRuleSk!.verifyFields()) {
+ const body: IgnoreRuleBody = {
+ duration: this.editIgnoreRuleSk!.expires,
+ filter: this.editIgnoreRuleSk!.query,
+ note: this.editIgnoreRuleSk!.note,
+ };
+ // TODO(kjlubick) remove the / from the json endpoint
+ let url = '/json/v1/ignores/add/';
+ if (this.ruleID) {
+ url = `/json/v1/ignores/save/${this.ruleID}`;
+ }
+
+ sendBeginTask(this);
+ fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ }).then(jsonOrThrow).then(() => {
+ this.fetch();
+ sendEndTask(this);
+ }).catch((e) => sendFetchError(this, e, 'saving ignore'));
+
+ this.editIgnoreRuleSk!.reset();
+ this.editIgnoreRuleDialog!.close();
+ }
+ }
+
+ private toggleCountAll(e: Event) {
+ e.preventDefault();
+ this.countAllTraces = !this.countAllTraces;
+ this.stateChanged();
+ this._render();
+ }
+}
+
+define('ignores-page-sk', IgnoresPageSk);
diff --git a/golden/modules/ignores-page-sk/ignores-page-sk_test.js b/golden/modules/ignores-page-sk/ignores-page-sk_test.ts
similarity index 73%
rename from golden/modules/ignores-page-sk/ignores-page-sk_test.js
rename to golden/modules/ignores-page-sk/ignores-page-sk_test.ts
index df25f94..03d197e 100644
--- a/golden/modules/ignores-page-sk/ignores-page-sk_test.js
+++ b/golden/modules/ignores-page-sk/ignores-page-sk_test.ts
@@ -9,13 +9,18 @@
setQueryString,
setUpElementUnderTest,
} from '../../../infra-sk/modules/test_util';
+import { IgnoresPageSk } from './ignores-page-sk';
+import { ParamSet } from '../rpc_types';
import { fakeNow, ignoreRules_10 } from './test_data';
+import { CheckOrRadio } from 'elements-sk/checkbox-sk/checkbox-sk';
+import { EditIgnoreRuleSk } from '../edit-ignore-rule-sk/edit-ignore-rule-sk';
+import { expect } from 'chai';
-describe('ignores-page-sk', () => {
- const newInstance = setUpElementUnderTest('ignores-page-sk');
+describe.only('ignores-page-sk', () => {
+ const newInstance = setUpElementUnderTest<IgnoresPageSk>('ignores-page-sk');
const regularNow = Date.now;
- let ignoresPageSk;
+ let ignoresPageSk: IgnoresPageSk;
beforeEach(async () => {
// Clear out any query params we might have to not mess with our current state.
@@ -24,7 +29,7 @@
fetchMock.get('/json/v1/ignores?counts=1', ignoreRules_10);
// We only need a few params to make sure the edit-ignore-rule-dialog works properly and it
// does not matter really what they are, so we use a small subset of actual params.
- const someParams = {
+ const someParams: ParamSet = {
alpha_type: ['Opaque', 'Premul'],
arch: ['arm', 'arm64', 'x86', 'x86_64'],
};
@@ -55,7 +60,7 @@
it('creates links to test the filter', () => {
const rows = $('table tbody tr', ignoresPageSk);
- const queryLink = $$('.query a', rows[9]);
+ const queryLink = $$<HTMLAnchorElement>('.query a', rows[9])!;
expect(queryLink.href).to.contain(
'include=true&query=config%3Dglmsaa4%26cpu_or_gpu_value%3DTegraX1%26name%3Drg1024_green_grapes.svg',
);
@@ -68,12 +73,12 @@
const rows = $('table tbody tr', ignoresPageSk);
const firstRow = rows[0];
expect(firstRow.className).to.contain('expired');
- let timeBox = $$('.expired', firstRow);
+ let timeBox = $$<HTMLElement>('.expired', firstRow)!;
expect(timeBox.innerText).to.contain('Expired');
const fourthRow = rows[4];
expect(fourthRow.className).to.not.contain('expired');
- timeBox = $$('.expired', fourthRow);
+ timeBox = $$<HTMLElement>('.expired', fourthRow)!;
expect(timeBox).to.be.null;
});
}); // end describe('html layout')
@@ -82,21 +87,21 @@
it('toggles between counting traces with untriaged digests and all traces', () => {
let checkbox = findUntriagedDigestsCheckbox(ignoresPageSk);
expect(checkbox.checked).to.be.true;
- expect(findMatchesTextForRow(2, ignoresPageSk)).to.contain('0 / 4');
+ expect(findMatchesTextForRow(ignoresPageSk, 2)).to.contain('0 / 4');
expectQueryStringToEqual('');
clickUntriagedDigestsCheckbox(ignoresPageSk);
checkbox = findUntriagedDigestsCheckbox(ignoresPageSk);
expect(checkbox.checked).to.be.false;
- expect(findMatchesTextForRow(2, ignoresPageSk)).to.contain('6 / 10');
+ expect(findMatchesTextForRow(ignoresPageSk, 2)).to.contain('6 / 10');
expectQueryStringToEqual('?count_all=true');
clickUntriagedDigestsCheckbox(ignoresPageSk);
checkbox = findUntriagedDigestsCheckbox(ignoresPageSk);
expect(checkbox.checked).to.be.true;
- expect(findMatchesTextForRow(2, ignoresPageSk)).to.contain('0 / 4');
+ expect(findMatchesTextForRow(ignoresPageSk, 2)).to.contain('0 / 4');
expectQueryStringToEqual('');
});
@@ -120,17 +125,17 @@
const dialog = findConfirmDeleteDialog(ignoresPageSk);
expect(dialog.hasAttribute('open')).to.be.false;
- const del = findDeleteForRow(2);
+ const del = findDeleteForRow(ignoresPageSk, 2);
del.click();
expect(dialog.hasAttribute('open')).to.be.true;
- const msg = $$('.message', dialog);
+ const msg = $$<HTMLElement>('.message', dialog)!;
expect(msg.innerText).to.contain('Are you sure you want to delete');
});
it('deletes an existing ignore rule', async () => {
const idOfThirdRule = '7589748925671328782';
- const del = findDeleteForRow(2);
+ const del = findDeleteForRow(ignoresPageSk, 2);
del.click();
fetchMock.post(`/json/v1/ignores/del/${idOfThirdRule}`, '{"deleted": "true"}');
@@ -167,7 +172,7 @@
it('updates an existing ignore rule', async () => {
const idOfThirdRule = '7589748925671328782';
- const edit = findUpdateForRow(2);
+ const edit = findUpdateForRow(ignoresPageSk, 2);
edit.click();
const dialog = findCreateEditIgnoreRuleDialog(ignoresPageSk);
@@ -201,59 +206,58 @@
});
});
-function findUntriagedDigestsCheckbox(ele) {
- return $$('.controls checkbox-sk', ele);
+function findUntriagedDigestsCheckbox(ele: IgnoresPageSk): CheckOrRadio {
+ return $$<CheckOrRadio>('.controls checkbox-sk', ele)!;
}
-function findMatchesTextForRow(n, ele) {
- const row = $('table tbody tr', ele)[n];
- const cell = $$('td.matches', row);
+function findMatchesTextForRow(ele: IgnoresPageSk, n: number): string {
+ const row = $<HTMLTableRowElement>('table tbody tr', ele)[n];
+ const cell = $$<HTMLTableDataCellElement>('td.matches', row)!;
// condense all whitespace and then trim to avoid the formatting of
// the html from impacting the tests too much (e.g. extraneous \n)
return cell.innerText;
}
-function findDeleteForRow(n, ele) {
+function findDeleteForRow(ele: IgnoresPageSk, n: number): HTMLElement {
const row = $('table tbody tr', ele)[n];
- return $$('.mutate-icons delete-icon-sk', row);
+ return $$<HTMLElement>('.mutate-icons delete-icon-sk', row)!;
}
-function findUpdateForRow(n, ele) {
+function findUpdateForRow(ele: IgnoresPageSk, n: number): HTMLElement {
const row = $('table tbody tr', ele)[n];
- return $$('.mutate-icons mode-edit-icon-sk', row);
+ return $$<HTMLElement>('.mutate-icons mode-edit-icon-sk', row)!;
}
-function findConfirmDeleteDialog(ele) {
- return $$('confirm-dialog-sk dialog', ele);
+function findConfirmDeleteDialog(ele: IgnoresPageSk): HTMLDialogElement {
+ return $$<HTMLDialogElement>('confirm-dialog-sk dialog', ele)!;
}
-function findCreateEditIgnoreRuleDialog(ele) {
- return $$('dialog#edit-ignore-rule-dialog', ele);
+function findCreateEditIgnoreRuleDialog(ele: IgnoresPageSk): HTMLDialogElement {
+ return $$<HTMLDialogElement>('dialog#edit-ignore-rule-dialog', ele)!;
}
-function findConfirmSaveIgnoreRuleButton(ele) {
- return $$('#edit-ignore-rule-dialog button#ok', ele);
+function findConfirmSaveIgnoreRuleButton(ele: IgnoresPageSk): HTMLButtonElement {
+ return $$<HTMLButtonElement>('#edit-ignore-rule-dialog button#ok', ele)!;
}
-function clickUntriagedDigestsCheckbox(ele) {
+function clickUntriagedDigestsCheckbox(ele: IgnoresPageSk) {
// We need to click on the input element to accurately mimic a user event. This is
// because the checkbox-sk element listens for the click event created by the
// internal input event.
- const input = $$('input[type="checkbox"]', findUntriagedDigestsCheckbox(ele));
+ const input = $$<HTMLInputElement>('input[type="checkbox"]', findUntriagedDigestsCheckbox(ele))!;
input.click();
}
-function clickConfirmDeleteButton(ele) {
- const ok = $$('button.confirm', findConfirmDeleteDialog(ele));
- ok.click();
+function clickConfirmDeleteButton(ele: IgnoresPageSk) {
+ $$<HTMLButtonElement>('button.confirm', findConfirmDeleteDialog(ele))!.click();
}
-function clickCreateIgnoreRuleButton(ele) {
- $$('.controls button.create', ele).click();
+function clickCreateIgnoreRuleButton(ele: IgnoresPageSk) {
+ $$<HTMLButtonElement>('.controls button.create', ele)!.click();
}
-function setIgnoreRuleProperties(ele, query, expires, note) {
- const editor = $$('edit-ignore-rule-sk', findCreateEditIgnoreRuleDialog(ele));
+function setIgnoreRuleProperties(ele: IgnoresPageSk, query: string, expires: string, note: string) {
+ const editor = $$<EditIgnoreRuleSk>('edit-ignore-rule-sk', findCreateEditIgnoreRuleDialog(ele))!;
editor.query = query;
editor.expires = expires;
editor.note = note;
diff --git a/golden/modules/ignores-page-sk/index.js b/golden/modules/ignores-page-sk/index.ts
similarity index 100%
rename from golden/modules/ignores-page-sk/index.js
rename to golden/modules/ignores-page-sk/index.ts
diff --git a/golden/modules/ignores-page-sk/test_data.js b/golden/modules/ignores-page-sk/test_data.js
deleted file mode 100644
index 996f416..0000000
--- a/golden/modules/ignores-page-sk/test_data.js
+++ /dev/null
@@ -1,124 +0,0 @@
-export const fakeNow = Date.parse('2019-12-30T00:00:00Z');
-
-export const ignoreRules_10 = [
- {
- id: '7589748926651362910',
- name: 'alpha@example.com',
- updatedBy: 'alpha@example.com',
- expires: '2018-10-18T23:18:39Z',
- query: 'config=gles\u0026model=iPhone7\u0026name=glyph_pos_h_s_this_is_a_super_long_test_name_or_key_value',
- note: 'skia:7204',
- countAll: 2,
- exclusiveCountAll: 0,
- count: 2,
- exclusiveCount: 0,
- },
- {
- id: '7589748926390545630',
- name: 'alpha@example.com',
- updatedBy: 'alpha@example.com',
- expires: '2018-11-15T00:51:14Z',
- query: 'config=angle_d3d9_es2\u0026cpu_or_gpu_value=RadeonR9M470X\u0026name=alpha_image\u0026name=colorfilterimagefilter_layer\u0026name=gradients_no_texture\u0026name=lighting\u0026name=multipicturedraw_sierpinski_simple\u0026name=radial_gradient\u0026name=shadermaskfilter_gradient\u0026name=shadermaskfilter_image\u0026name=srgb_colorfilter\u0026name=xfermodes3',
- note: 'skia:7245',
- countAll: 18,
- exclusiveCountAll: 18,
- count: 15,
- exclusiveCount: 15,
- },
- {
- id: '7589748925671328782',
- name: 'alpha@example.com',
- updatedBy: 'alpha@example.com',
- expires: '2019-04-03T20:35:12Z',
- query: 'config=glmsaa8\u0026cpu_or_gpu_value=GTX660\u0026cpu_or_gpu_value=GTX960\u0026cpu_or_gpu_value=QuadroP400\u0026name=car.svg',
- note: 'skia:6545',
- countAll: 10,
- exclusiveCountAll: 6,
- count: 4,
- exclusiveCount: 0,
- },
- {
- id: '7589748925255618990',
- name: 'alpha@example.com',
- updatedBy: 'beta@example.com',
- expires: '2019-12-29T17:54:13Z',
- query: 'config=enarrow\u0026config=esrgb\u0026config=f16\u0026config=glenarrow\u0026config=glessrgb\u0026config=glsrgb\u0026config=narrow',
- note: '',
- countAll: 135459,
- exclusiveCountAll: 133449,
- count: 62701,
- exclusiveCount: 61895,
- },
- {
- id: '7589748924822791934',
- name: 'alpha@example.com',
- updatedBy: 'alpha@example.com',
- expires: '2019-12-30T08:56:24Z',
- query: 'config=angle_gl_es2_msaa8\u0026config=angle_gl_es3_msaa8\u0026config=glmsaa8\u0026cpu_or_gpu_value=GTX660\u0026cpu_or_gpu_value=GTX960\u0026name=car.svg\u0026name=draw_image_set\u0026name=filltypespersp\u0026name=gallardo.svg\u0026name=rg1024_green_grapes.svg\u0026os=Win10',
- note: 'skia:6813',
- countAll: 60,
- exclusiveCountAll: 40,
- count: 36,
- exclusiveCount: 28,
- },
- {
- id: '7589748924681439406',
- name: 'alpha@example.com',
- updatedBy: 'alpha@example.com',
- expires: '2020-01-01T02:15:23Z',
- query: 'config=gles\u0026config=glesdft\u0026cpu_or_gpu=GPU\u0026cpu_or_gpu_value=Tegra3\u0026name=bleed_alpha_bmp\u0026name=bleed_alpha_bmp_shader\u0026name=bleed_alpha_image\u0026name=bleed_alpha_image_shader',
- note: 'skia:5013',
- countAll: 16,
- exclusiveCountAll: 16,
- count: 16,
- exclusiveCount: 16,
- },
- {
- id: '7589748924269153262',
- name: 'alpha@example.com',
- updatedBy: 'alpha@example.com',
- expires: '2020-01-04T03:10:51Z',
- query: 'config=glmsaa8\u0026cpu_or_gpu_value=GTX660\u0026cpu_or_gpu_value=GTX960\u0026name=dstreadshuffle\u0026name=rg1024_green_grapes.svg\u0026name=tiger-8.svg',
- note: 'skia:6545',
- countAll: 12,
- exclusiveCountAll: 8,
- count: 10,
- exclusiveCount: 6,
- },
- {
- id: '7589748924016733918',
- name: 'alpha@example.com',
- updatedBy: 'alpha@example.com',
- expires: '2020-01-12T17:07:57Z',
- query: 'config=angle_d3d11_es3\u0026model=LenovoYogaC630\u0026name=car.svg\u0026name=gallardo.svg',
- note: 'skia:8976',
- countAll: 2,
- exclusiveCountAll: 2,
- count: 2,
- exclusiveCount: 2,
- },
- {
- id: '7589748923887311614',
- name: 'alpha@example.com',
- updatedBy: 'gamma@example.com',
- expires: '2020-01-17T16:54:11Z',
- query: 'config=angle_d3d11_es2_msaa8\u0026config=angle_d3d11_es3_msaa8\u0026cpu_or_gpu_value=QuadroP400\u0026name=Chalkboard.svg\u0026name=bezier_quad_effects\u0026name=bleed\u0026name=bleed_alpha_bmp\u0026name=bleed_alpha_bmp_shader\u0026name=bleed_alpha_image\u0026name=bleed_alpha_image_shader\u0026name=bleed_image\u0026name=car.svg\u0026name=circular_arcs_fill\u0026name=circular_arcs_stroke_and_fill_butt\u0026name=circular_arcs_stroke_and_fill_round\u0026name=circular_arcs_stroke_and_fill_square\u0026name=circular_arcs_stroke_butt\u0026name=circular_arcs_stroke_round\u0026name=circular_arcs_stroke_square\u0026name=filltypespersp\u0026name=gallardo.svg\u0026name=glyph_pos_n_b\u0026name=glyph_pos_n_s\u0026name=hittestpath\u0026name=image_scale_aligned\u0026name=pictureimagegenerator\u0026name=repeated_bitmap\u0026name=rg1024_green_grapes.svg\u0026name=shadow_utils_gray\u0026name=shadow_utils_occl\u0026name=verylarge_picture_image\u0026name=verylargebitmap\u0026os=Win10',
- note: 'skia:6813',
- countAll: 116,
- exclusiveCountAll: 92,
- count: 100,
- exclusiveCount: 76,
- },
- {
- id: '7589748923749696270',
- name: 'alpha@example.com',
- updatedBy: 'alpha@example.com',
- expires: '2020-01-25T17:15:25Z',
- query: 'config=glmsaa4\u0026cpu_or_gpu_value=TegraX1\u0026name=rg1024_green_grapes.svg',
- note: 'skia:6545',
- countAll: 4,
- exclusiveCountAll: 4,
- count: 4,
- exclusiveCount: 4,
- },
-];
diff --git a/golden/modules/ignores-page-sk/test_data.ts b/golden/modules/ignores-page-sk/test_data.ts
new file mode 100644
index 0000000..5be5f8b
--- /dev/null
+++ b/golden/modules/ignores-page-sk/test_data.ts
@@ -0,0 +1,128 @@
+import { IgnoresResponse } from '../rpc_types';
+
+export const fakeNow = Date.parse('2019-12-30T00:00:00Z');
+
+export const ignoreRules_10: IgnoresResponse = {
+ rules: [
+ {
+ id: '7589748926651362910',
+ name: 'alpha@example.com',
+ updatedBy: 'alpha@example.com',
+ expires: '2018-10-18T23:18:39Z',
+ query: 'config=gles\u0026model=iPhone7\u0026name=glyph_pos_h_s_this_is_a_super_long_test_name_or_key_value',
+ note: 'skia:7204',
+ countAll: 2,
+ exclusiveCountAll: 0,
+ count: 2,
+ exclusiveCount: 0,
+ },
+ {
+ id: '7589748926390545630',
+ name: 'alpha@example.com',
+ updatedBy: 'alpha@example.com',
+ expires: '2018-11-15T00:51:14Z',
+ query: 'config=angle_d3d9_es2\u0026cpu_or_gpu_value=RadeonR9M470X\u0026name=alpha_image\u0026name=colorfilterimagefilter_layer\u0026name=gradients_no_texture\u0026name=lighting\u0026name=multipicturedraw_sierpinski_simple\u0026name=radial_gradient\u0026name=shadermaskfilter_gradient\u0026name=shadermaskfilter_image\u0026name=srgb_colorfilter\u0026name=xfermodes3',
+ note: 'skia:7245',
+ countAll: 18,
+ exclusiveCountAll: 18,
+ count: 15,
+ exclusiveCount: 15,
+ },
+ {
+ id: '7589748925671328782',
+ name: 'alpha@example.com',
+ updatedBy: 'alpha@example.com',
+ expires: '2019-04-03T20:35:12Z',
+ query: 'config=glmsaa8\u0026cpu_or_gpu_value=GTX660\u0026cpu_or_gpu_value=GTX960\u0026cpu_or_gpu_value=QuadroP400\u0026name=car.svg',
+ note: 'skia:6545',
+ countAll: 10,
+ exclusiveCountAll: 6,
+ count: 4,
+ exclusiveCount: 0,
+ },
+ {
+ id: '7589748925255618990',
+ name: 'alpha@example.com',
+ updatedBy: 'beta@example.com',
+ expires: '2019-12-29T17:54:13Z',
+ query: 'config=enarrow\u0026config=esrgb\u0026config=f16\u0026config=glenarrow\u0026config=glessrgb\u0026config=glsrgb\u0026config=narrow',
+ note: '',
+ countAll: 135459,
+ exclusiveCountAll: 133449,
+ count: 62701,
+ exclusiveCount: 61895,
+ },
+ {
+ id: '7589748924822791934',
+ name: 'alpha@example.com',
+ updatedBy: 'alpha@example.com',
+ expires: '2019-12-30T08:56:24Z',
+ query: 'config=angle_gl_es2_msaa8\u0026config=angle_gl_es3_msaa8\u0026config=glmsaa8\u0026cpu_or_gpu_value=GTX660\u0026cpu_or_gpu_value=GTX960\u0026name=car.svg\u0026name=draw_image_set\u0026name=filltypespersp\u0026name=gallardo.svg\u0026name=rg1024_green_grapes.svg\u0026os=Win10',
+ note: 'skia:6813',
+ countAll: 60,
+ exclusiveCountAll: 40,
+ count: 36,
+ exclusiveCount: 28,
+ },
+ {
+ id: '7589748924681439406',
+ name: 'alpha@example.com',
+ updatedBy: 'alpha@example.com',
+ expires: '2020-01-01T02:15:23Z',
+ query: 'config=gles\u0026config=glesdft\u0026cpu_or_gpu=GPU\u0026cpu_or_gpu_value=Tegra3\u0026name=bleed_alpha_bmp\u0026name=bleed_alpha_bmp_shader\u0026name=bleed_alpha_image\u0026name=bleed_alpha_image_shader',
+ note: 'skia:5013',
+ countAll: 16,
+ exclusiveCountAll: 16,
+ count: 16,
+ exclusiveCount: 16,
+ },
+ {
+ id: '7589748924269153262',
+ name: 'alpha@example.com',
+ updatedBy: 'alpha@example.com',
+ expires: '2020-01-04T03:10:51Z',
+ query: 'config=glmsaa8\u0026cpu_or_gpu_value=GTX660\u0026cpu_or_gpu_value=GTX960\u0026name=dstreadshuffle\u0026name=rg1024_green_grapes.svg\u0026name=tiger-8.svg',
+ note: 'skia:6545',
+ countAll: 12,
+ exclusiveCountAll: 8,
+ count: 10,
+ exclusiveCount: 6,
+ },
+ {
+ id: '7589748924016733918',
+ name: 'alpha@example.com',
+ updatedBy: 'alpha@example.com',
+ expires: '2020-01-12T17:07:57Z',
+ query: 'config=angle_d3d11_es3\u0026model=LenovoYogaC630\u0026name=car.svg\u0026name=gallardo.svg',
+ note: 'skia:8976',
+ countAll: 2,
+ exclusiveCountAll: 2,
+ count: 2,
+ exclusiveCount: 2,
+ },
+ {
+ id: '7589748923887311614',
+ name: 'alpha@example.com',
+ updatedBy: 'gamma@example.com',
+ expires: '2020-01-17T16:54:11Z',
+ query: 'config=angle_d3d11_es2_msaa8\u0026config=angle_d3d11_es3_msaa8\u0026cpu_or_gpu_value=QuadroP400\u0026name=Chalkboard.svg\u0026name=bezier_quad_effects\u0026name=bleed\u0026name=bleed_alpha_bmp\u0026name=bleed_alpha_bmp_shader\u0026name=bleed_alpha_image\u0026name=bleed_alpha_image_shader\u0026name=bleed_image\u0026name=car.svg\u0026name=circular_arcs_fill\u0026name=circular_arcs_stroke_and_fill_butt\u0026name=circular_arcs_stroke_and_fill_round\u0026name=circular_arcs_stroke_and_fill_square\u0026name=circular_arcs_stroke_butt\u0026name=circular_arcs_stroke_round\u0026name=circular_arcs_stroke_square\u0026name=filltypespersp\u0026name=gallardo.svg\u0026name=glyph_pos_n_b\u0026name=glyph_pos_n_s\u0026name=hittestpath\u0026name=image_scale_aligned\u0026name=pictureimagegenerator\u0026name=repeated_bitmap\u0026name=rg1024_green_grapes.svg\u0026name=shadow_utils_gray\u0026name=shadow_utils_occl\u0026name=verylarge_picture_image\u0026name=verylargebitmap\u0026os=Win10',
+ note: 'skia:6813',
+ countAll: 116,
+ exclusiveCountAll: 92,
+ count: 100,
+ exclusiveCount: 76,
+ },
+ {
+ id: '7589748923749696270',
+ name: 'alpha@example.com',
+ updatedBy: 'alpha@example.com',
+ expires: '2020-01-25T17:15:25Z',
+ query: 'config=glmsaa4\u0026cpu_or_gpu_value=TegraX1\u0026name=rg1024_green_grapes.svg',
+ note: 'skia:6545',
+ countAll: 4,
+ exclusiveCountAll: 4,
+ count: 4,
+ exclusiveCount: 4,
+ },
+ ],
+};
diff --git a/golden/modules/rpc_types.ts b/golden/modules/rpc_types.ts
index a29da04..2ed23e0 100644
--- a/golden/modules/rpc_types.ts
+++ b/golden/modules/rpc_types.ts
@@ -176,6 +176,29 @@
total: number;
}
+export interface IgnoreRuleBody {
+ duration: string;
+ filter: string;
+ note: string;
+}
+
+export interface IgnoreRule {
+ id: string;
+ name: string;
+ updatedBy: string;
+ expires: string;
+ query: string;
+ note: string;
+ countAll: number;
+ exclusiveCountAll: number;
+ count: number;
+ exclusiveCount: number;
+}
+
+export interface IgnoresResponse {
+ rules: IgnoreRule[] | null;
+}
+
export type ParamSet = { [key: string]: string[] };
export type ParamSetResponse = { [key: string]: string[] | null };