Implement pagination for the multi graph view.
- Use pagination-sk from golden to create pagination support for multi graph view. In a future CL, I think we should move the pagination-sk out to infra-sk/modules for better sharing. Not doing it in this CL to avoid cluttering.
Demo: https://screencast.googleplex.com/cast/NTA0MTg2Mzk1NzQxMzg4OHw2OGNiMWNhMy0wZQ
Bug: b/323256991
Change-Id: I8ffe27fd6aef4749e07a565f91a29f625a7959e6
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/816210
Commit-Queue: Ashwin Verleker <ashwinpv@google.com>
Reviewed-by: Eduardo Yap <eduardoyap@google.com>
diff --git a/golden/modules/pagination-sk/pagination-sk.ts b/golden/modules/pagination-sk/pagination-sk.ts
index 20e6988..bb33d4c 100644
--- a/golden/modules/pagination-sk/pagination-sk.ts
+++ b/golden/modules/pagination-sk/pagination-sk.ts
@@ -109,7 +109,7 @@
}
private _canGoNext(next: number) {
- return this.total === MANY ? true : next <= this.total;
+ return this.total === MANY ? true : next < this.total;
}
private _page(n: number) {
diff --git a/perf/modules/explore-multi-sk/BUILD.bazel b/perf/modules/explore-multi-sk/BUILD.bazel
index cdc8a1f..8981c87 100644
--- a/perf/modules/explore-multi-sk/BUILD.bazel
+++ b/perf/modules/explore-multi-sk/BUILD.bazel
@@ -5,13 +5,13 @@
sass_srcs = ["explore-multi-sk.scss"],
sk_element_deps = [
"//perf/modules/explore-simple-sk",
+ "//golden/modules/pagination-sk",
],
ts_deps = [
"//elements-sk/modules:define_ts_lib",
"//perf/modules/errorMessage:index_ts_lib",
"//infra-sk/modules:hintable_ts_lib",
"//infra-sk/modules:statereflector_ts_lib",
- "//infra-sk/modules:query_ts_lib",
"//infra-sk/modules/ElementSk:index_ts_lib",
"//perf/modules/paramtools:index_ts_lib",
"//:node_modules/lit-html",
diff --git a/perf/modules/explore-multi-sk/explore-multi-sk.scss b/perf/modules/explore-multi-sk/explore-multi-sk.scss
index 5b66060..5add39d 100644
--- a/perf/modules/explore-multi-sk/explore-multi-sk.scss
+++ b/perf/modules/explore-multi-sk/explore-multi-sk.scss
@@ -7,4 +7,15 @@
#menu {
margin: 1em;
}
+
+ label > span.prefix {
+ display: inline-block;
+ width: 100px;
+ }
+
+ label > input {
+ color: var(--on-surface);
+ background: var(--surface-1dp);
+ width: 64px;
+ }
}
diff --git a/perf/modules/explore-multi-sk/explore-multi-sk.ts b/perf/modules/explore-multi-sk/explore-multi-sk.ts
index eb34e96..5b0db86 100644
--- a/perf/modules/explore-multi-sk/explore-multi-sk.ts
+++ b/perf/modules/explore-multi-sk/explore-multi-sk.ts
@@ -13,7 +13,6 @@
*
*/
import { html } from 'lit-html';
-import * as query from '../../../infra-sk/modules/query';
import { define } from '../../../elements-sk/modules/define';
import {
DEFAULT_RANGE_S,
@@ -29,9 +28,10 @@
import { ElementSk } from '../../../infra-sk/modules/ElementSk';
import '../explore-simple-sk';
-import { jsonOrThrow } from '../../../infra-sk/modules/jsonOrThrow';
+import '../../../golden/modules/pagination-sk/pagination-sk';
-const GRAPH_LIMIT = 50;
+import { jsonOrThrow } from '../../../infra-sk/modules/jsonOrThrow';
+import { PaginationSkPageChangedEventDetail } from '../../../golden/modules/pagination-sk/pagination-sk';
class State {
begin: number = Math.floor(Date.now() / 1000 - DEFAULT_RANGE_S);
@@ -47,6 +47,12 @@
numCommits: number = 250;
summary: boolean = false;
+
+ pageSize: number = 10;
+
+ pageOffset: number = 0;
+
+ totalGraphs: number = 0;
}
class GraphConfig {
@@ -62,6 +68,10 @@
private exploreElements: ExploreSimpleSk[] = [];
+ private currentPageExploreElements: ExploreSimpleSk[] = [];
+
+ private currentPageGraphConfigs: GraphConfig[] = [];
+
private stateHasChanged: (() => void) | null = null;
private _state: State = new State();
@@ -70,6 +80,8 @@
private mergeGraphsButton: HTMLButtonElement | null = null;
+ private graphDiv: Element | null = null;
+
constructor() {
super(ExploreMultiSk.template);
}
@@ -78,6 +90,7 @@
super.connectedCallback();
this._render();
+ this.graphDiv = this.querySelector('#graphContainer');
this.splitGraphButton = this.querySelector('#split-graph-button');
this.mergeGraphsButton = this.querySelector('#merge-graphs-button');
@@ -96,43 +109,26 @@
}
}
+ // This loop helps get rid of extra graphs that aren't part of the
+ // current config. A scenario where this occurs is if we have 1 graph,
+ // add another graph and then go back in the browser.
+ while (this.exploreElements.length > graphConfigs.length) {
+ this.exploreElements.pop();
+ this.graphConfigs.pop();
+ this.graphDiv!.removeChild(this.graphDiv!.lastChild!);
+ }
+
for (let i = 0; i < graphConfigs.length; i++) {
if (i >= numElements) {
this.addEmptyGraph();
}
this.graphConfigs[i] = graphConfigs[i];
}
- while (this.exploreElements.length > graphConfigs.length) {
- this.popGraph();
- }
this.state = state;
+ this.addGraphsToCurrentPage();
+
this.updateButtons();
-
- this.exploreElements.forEach((elem, i) => {
- const graphConfig = this.graphConfigs[i];
-
- const newState: ExploreState = {
- formulas: graphConfig.formulas,
- queries: graphConfig.queries,
- keys: graphConfig.keys,
- begin: state.begin,
- end: state.end,
- showZero: state.showZero,
- dots: state.dots,
- numCommits: state.numCommits,
- summary: state.summary,
- xbaroffset: elem.state.xbaroffset,
- autoRefresh: elem.state.autoRefresh,
- requestType: elem.state.requestType,
- pivotRequest: elem.state.pivotRequest,
- sort: elem.state.sort,
- selected: elem.state.selected,
- _incremental: false,
- labelMode: LabelMode.Date,
- };
- elem.state = newState;
- });
}
);
}
@@ -144,6 +140,7 @@
@click=${() => {
const explore = ele.addEmptyGraph();
if (explore) {
+ ele.updatePageForNewExplore();
explore.openQuery();
}
}}
@@ -168,31 +165,96 @@
</button>
</div>
<hr />
+
+ <pagination-sk
+ offset=${ele.state.pageOffset}
+ page_size=${ele.state.pageSize}
+ total=${ele.state.totalGraphs}
+ @page-changed=${ele.pageChanged}>
+ </pagination-sk>
+ <label>
+ <span class="prefix">Charts per page</span>
+ <input
+ @change=${ele.pageSizeChanged}
+ type="number"
+ .value="${ele.state.pageSize.toString()}"
+ min="1"
+ max="50"
+ title="The number of charts per page." />
+ </label>
<div id="graphContainer"></div>
+ <pagination-sk
+ offset=${ele.state.pageOffset}
+ page_size=${ele.state.pageSize}
+ total=${ele.state.totalGraphs}
+ @page-changed=${ele.pageChanged}>
+ </pagination-sk>
`;
- private popGraph() {
- const graphDiv: Element | null = this.querySelector('#graphContainer');
-
- this.exploreElements.pop();
- this.graphConfigs.pop();
+ private clearGraphs() {
+ this.exploreElements = [];
+ this.graphConfigs = [];
this.updateButtons();
- graphDiv!.removeChild(graphDiv!.lastChild!);
}
- private clearGraphs() {
- while (this.exploreElements.length > 0) {
- this.popGraph();
+ private emptyCurrentPage(): void {
+ while (this.graphDiv!.hasChildNodes()) {
+ this.graphDiv!.removeChild(this.graphDiv!.lastChild!);
}
+ this.currentPageExploreElements = [];
+ this.currentPageGraphConfigs = [];
+ }
+
+ private addGraphsToCurrentPage(): void {
+ this.state.totalGraphs = this.exploreElements.length;
+ this.emptyCurrentPage();
+ const startIndex = this.state.pageOffset;
+ let endIndex = startIndex + this.state.pageSize - 1;
+ if (this.exploreElements.length <= endIndex) {
+ endIndex = this.exploreElements.length - 1;
+ }
+
+ for (let i = startIndex; i <= endIndex; i++) {
+ this.graphDiv!.appendChild(this.exploreElements[i]);
+ this.currentPageExploreElements.push(this.exploreElements[i]);
+ this.currentPageGraphConfigs.push(this.graphConfigs[i]);
+ }
+
+ this.currentPageExploreElements.forEach((elem, i) => {
+ const graphConfig = this.currentPageGraphConfigs[i];
+ this.addStateToExplore(elem, graphConfig);
+ });
+
+ this._render();
+ }
+
+ private addStateToExplore(
+ explore: ExploreSimpleSk,
+ graphConfig: GraphConfig
+ ) {
+ const newState: ExploreState = {
+ formulas: graphConfig.formulas || [],
+ queries: graphConfig.queries || [],
+ keys: graphConfig.keys || '',
+ begin: this.state.begin,
+ end: this.state.end,
+ showZero: this.state.showZero,
+ dots: this.state.dots,
+ numCommits: this.state.numCommits,
+ summary: this.state.summary,
+ xbaroffset: explore.state.xbaroffset,
+ autoRefresh: explore.state.autoRefresh,
+ requestType: explore.state.requestType,
+ pivotRequest: explore.state.pivotRequest,
+ sort: explore.state.sort,
+ selected: explore.state.selected,
+ _incremental: false,
+ labelMode: LabelMode.Date,
+ };
+ explore.state = newState;
}
private addEmptyGraph(): ExploreSimpleSk | null {
- if (this.exploreElements.length >= GRAPH_LIMIT) {
- errorMessage(`Cannot exceed display limit of ${GRAPH_LIMIT} graphs.`);
- return null;
- }
-
- const graphDiv: Element | null = this.querySelector('#graphContainer');
const explore: ExploreSimpleSk = new ExploreSimpleSk(true);
explore.openQueryByDefault = false;
@@ -217,7 +279,6 @@
this.updateShortcut();
});
- graphDiv!.appendChild(explore);
return explore;
}
@@ -352,26 +413,21 @@
}
this.clearGraphs();
traceset.forEach((key, i) => {
- const newExplore = this.addEmptyGraph();
- if (newExplore) {
- if (key[0] === ',') {
- const queries = this.queryFromKey(key);
- newExplore.state = {
- ...newExplore.state,
- queries: [queries],
- };
- this.graphConfigs[i].queries = [queries];
- } else {
- const formulas = key;
- newExplore.state = {
- ...newExplore.state,
- formulas: [formulas],
- };
- this.graphConfigs[i].formulas = [formulas];
- }
+ this.addEmptyGraph();
+ if (key[0] === ',') {
+ const queries = this.queryFromKey(key);
+ this.graphConfigs[i].queries = [queries];
+ } else {
+ const formulas = key;
+ this.graphConfigs[i].formulas = [formulas];
}
});
this.updateShortcut();
+
+ // Upon the split action, we would want to move to the first page
+ // of the split graph set.
+ this.state.pageOffset = 0;
+ this.addGraphsToCurrentPage();
}
/**
@@ -381,39 +437,23 @@
* Opposite of splitGraph function.
*/
private async mergeGraphs() {
- const tracesets = this.getTracesets();
-
- const traces: string[] = [];
- // Flatten tracesets
- tracesets.forEach((traceset) => {
- traceset.forEach((trace) => {
- if (!traces.includes(trace)) {
- traces.push(trace);
- }
+ const mergedGraphConfig = new GraphConfig();
+ this.graphConfigs.forEach((config) => {
+ config.formulas.forEach((formula) => {
+ mergedGraphConfig.formulas.push(formula);
+ });
+ config.queries.forEach((query) => {
+ mergedGraphConfig.queries.push(query);
});
});
-
this.clearGraphs();
- const newExplore = this.addEmptyGraph();
+ this.addEmptyGraph();
- const queries: string[] = [];
- const formulas: string[] = [];
-
- traces.forEach((trace) => {
- if (trace[0] === ',') {
- queries.push(this.queryFromKey(trace));
- } else {
- formulas.push(trace);
- }
- });
- newExplore!.state = {
- ...newExplore!.state,
- formulas: formulas,
- queries: queries,
- };
- this.graphConfigs[0].formulas = formulas;
- this.graphConfigs[0].queries = queries;
+ this.graphConfigs[0] = mergedGraphConfig;
this.updateShortcut!();
+ // Upon the merge action, we would want to move to the first page.
+ this.state.pageOffset = 0;
+ this.addGraphsToCurrentPage();
}
/**
@@ -471,6 +511,40 @@
})
.catch(errorMessage);
}
+
+ private pageChanged(e: CustomEvent<PaginationSkPageChangedEventDetail>) {
+ this.state.pageOffset = Math.max(
+ 0,
+ this.state.pageOffset + e.detail.delta * this.state.pageSize
+ );
+ this.stateHasChanged!();
+ this.addGraphsToCurrentPage();
+ }
+
+ private pageSizeChanged(e: MouseEvent) {
+ this.state.pageSize = +(e.target! as HTMLInputElement).value;
+ this.stateHasChanged!();
+ this.addGraphsToCurrentPage();
+ }
+
+ private updatePageForNewExplore() {
+ // Check if there is space left on the current page
+ if (this.graphDiv!.childElementCount === this.state.pageSize) {
+ // We will have to add another page since the current one is full.
+ // Go to the next page.
+ this.pageChanged(
+ new CustomEvent<PaginationSkPageChangedEventDetail>('page-changed', {
+ detail: {
+ delta: 1,
+ },
+ bubbles: true,
+ })
+ );
+ } else {
+ // Re-render the page
+ this.addGraphsToCurrentPage();
+ }
+ }
}
define('explore-multi-sk', ExploreMultiSk);