blob: de3ba706ffb254103393cdabd8736c01c48dc136 [file] [log] [blame]
/**
* @module modules/changelists-page-sk
* @description <h2><code>changelists-page-sk</code></h2>
*
* Allows the user to page through the ChangeLists for which Gold has seen
* data uploaded via TryJobs.
*
*/
import * as human from 'common-sk/modules/human'
import { define } from 'elements-sk/define'
import { ElementSk } from '../../../infra-sk/modules/ElementSk'
import { html } from 'lit-html'
import { jsonOrThrow } from 'common-sk/modules/jsonOrThrow'
import { stateReflector } from 'common-sk/modules/stateReflector'
import '../pagination-sk'
const _changelist = (cl) => html`
<tr>
<td>
<a title="See codereview in a new window" target=_blank rel=noopener href=${cl.url}>
${cl.id}
</a>
</td>
<td>
<a href="/search?issue=${cl.id}&new_clstore=true"
target="_blank" rel="noopener">Triage</a>
</td>
<td>${cl.owner}</td>
<td title=${cl.updated}>${human.diffDate(cl.updated)} ago</td>
<td>${cl.subject}</td>
</tr>`;
const template = (ele) => html`
<div>
<pagination-sk page_size=${ele._page_size} offset=${ele._offset}
total=${ele._total} @page-changed=${ele._pageChanged}>
</pagination-sk>
</div>
<table>
<thead>
<tr>
<th>ChangeList</th>
<th></th>
<th>Owner</th>
<th>Updated</th>
<th>Subject</th>
</tr>
</thead>
<tbody>
${ele._cls.map(_changelist)}
</tbody>`;
define('changelists-page-sk', class extends ElementSk {
constructor() {
super(template);
// Set empty values to allow empty rendering while we wait for
// stateReflector (which triggers on DomReady). Additionally, these values
// help stateReflector with types.
this._cls = [];
this._offset = 0;
this._page_size = 0;
this._total = 0;
this._urlParamsLoaded = false;
this._stateChanged = stateReflector(
/*getState*/() => {
return {
// provide empty values
'offset': this._offset,
'page_size': this._page_size,
}
}, /*setState*/(newState) => {
// default values if not specified.
this._offset = newState.offset || 0;
this._page_size = newState.page_size || +this.getAttribute('page_size') || 50;
if (!this._urlParamsLoaded) {
// initial page load/fetch
this._urlParamsLoaded = true;
this._fetch();
}
this._render();
});
// Allows us to abort fetches if a user pages.
this._fetchController = null;
}
connectedCallback() {
super.connectedCallback();
this._render();
}
// Returns a promise that resolves when all outstanding requests resolve
// or null if none were made. This promise makes unit tests a little more concise.
_fetch() {
if (!this._urlParamsLoaded) {
return null;
}
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,
};
this._sendBusy();
return fetch(`/json/changelists?offset=${this._offset}&size=${this._page_size}`, extra)
.then(jsonOrThrow)
.then((json) => {
this._cls = json.data || [];
this._offset = json.pagination.offset;
this._total = json.pagination.total;
this._stateChanged();
this._render();
this._sendDone();
})
.catch((e) => this._sendFetchError(e, 'changelists'));
}
_pageChanged(e) {
const d = e.detail;
this._offset += d.delta * this._page_size;
if (this._offset < 0) {
this._offset = 0;
}
this._stateChanged();
this._render();
this._fetch();
}
_sendBusy() {
this.dispatchEvent(new CustomEvent('begin-task', {bubbles: true}));
}
_sendDone() {
this.dispatchEvent(new CustomEvent('end-task', {bubbles: true}));
}
_sendFetchError(e, what) {
this.dispatchEvent(new CustomEvent('fetch-error', { detail: {
error: e,
loading: what,
}, bubbles: true}));
}
});