| /** |
| * @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 { html } from 'lit/html.js'; |
| import * as human from '../../../infra-sk/modules/human'; |
| |
| import { define } from '../../../elements-sk/modules/define'; |
| import { jsonOrThrow } from '../../../infra-sk/modules/jsonOrThrow'; |
| import { stateReflector } from '../../../infra-sk/modules/stateReflector'; |
| import { ElementSk } from '../../../infra-sk/modules/ElementSk'; |
| |
| import '../../../elements-sk/modules/checkbox-sk'; |
| import '../../../elements-sk/modules/icons/block-icon-sk'; |
| import '../../../elements-sk/modules/icons/cached-icon-sk'; |
| import '../../../elements-sk/modules/icons/done-icon-sk'; |
| |
| import '../pagination-sk'; |
| import { sendBeginTask, sendEndTask, sendFetchError } from '../common'; |
| import { Changelist, ChangelistsResponse } from '../rpc_types'; |
| import { PaginationSkPageChangedEventDetail } from '../pagination-sk/pagination-sk'; |
| |
| export class ChangelistsPageSk extends ElementSk { |
| private static template = (ele: ChangelistsPageSk) => html` |
| <div class="controls"> |
| <pagination-sk |
| page_size=${ele.pageSize} |
| offset=${ele.offset} |
| total=${ele.total} |
| @page-changed=${ele.pageChanged}> |
| </pagination-sk> |
| |
| <checkbox-sk |
| label="show all" |
| ?checked=${ele.showAll} |
| @click=${ele.toggleShowAll}></checkbox-sk> |
| </div> |
| |
| <table> |
| <thead> |
| <tr> |
| <th colspan="2">ChangeList</th> |
| <th>Owner</th> |
| <th>Updated</th> |
| <th>Subject</th> |
| </tr> |
| </thead> |
| <tbody> |
| ${ele.cls.map(ChangelistsPageSk.changelist)} |
| </tbody> |
| </table> |
| `; |
| |
| private static changelist = (cl: Changelist) => html` |
| <tr> |
| <td class="id"> |
| <a |
| title="See codereview in a new window" |
| target="_blank" |
| rel="noopener" |
| href=${cl.url} |
| >${cl.id}</a |
| > |
| ${ChangelistsPageSk.statusIcon(cl)} |
| </td> |
| <td> |
| <a href="/cl/${cl.system}/${cl.id}" target="_blank" rel="noopener" |
| >Triage</a |
| > |
| </td> |
| <td class="owner">${cl.owner}</td> |
| <td title=${cl.updated}>${human.diffDate(cl.updated)} ago</td> |
| <td>${cl.subject}</td> |
| </tr> |
| `; |
| |
| private static statusIcon = (cl: Changelist) => { |
| const st = cl.status.toLowerCase(); |
| if (st === 'open') { |
| return html`<cached-icon-sk title="ChangeList is open"></cached-icon-sk>`; |
| } |
| if (st === 'landed') { |
| return html`<done-icon-sk title="ChangeList was landed"></done-icon-sk>`; |
| } |
| return html`<block-icon-sk |
| title="ChangeList was abandoned"></block-icon-sk>`; |
| }; |
| |
| // Set empty values to allow empty rendering while we wait for |
| // stateReflector (which triggers on DomReady). Additionally, these values |
| // help stateReflector with types. |
| private cls: Changelist[] = []; |
| |
| private offset = 0; |
| |
| private pageSize = 0; |
| |
| private total = 0; |
| |
| private showAll = false; |
| |
| private readonly stateChanged: () => void; |
| |
| // Allows us to abort fetches if a user pages. |
| private fetchController?: AbortController; |
| |
| constructor() { |
| super(ChangelistsPageSk.template); |
| this.stateChanged = stateReflector( |
| /* getState */ () => ({ |
| // provide empty values |
| offset: this.offset, |
| page_size: this.pageSize, |
| show_all: this.showAll, |
| }), |
| /* setState */ (newState) => { |
| if (!this._connected) { |
| return; |
| } |
| |
| // default values if not specified. |
| this.offset = (newState.offset as number) || 0; |
| this.pageSize = |
| (newState.page_size as number) || |
| +this.getAttribute('page_size')! || |
| 50; |
| this.showAll = (newState.show_all as boolean) || false; |
| this.fetch(); |
| this._render(); |
| } |
| ); |
| } |
| |
| connectedCallback(): void { |
| 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. |
| private fetch(): Promise<void> { |
| 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); |
| const base = '/json/v2/changelists'; |
| let u = `${base}?offset=${this.offset}&size=${this.pageSize}`; |
| if (!this.showAll) { |
| u += '&active=true'; |
| } |
| return fetch(u, extra) |
| .then(jsonOrThrow) |
| .then((response: ChangelistsResponse) => { |
| this.cls = response.changelists || []; |
| this.offset = response.offset; |
| this.total = response.total; |
| this._render(); |
| sendEndTask(this); |
| }) |
| .catch((e) => sendFetchError(this, e, 'changelists')); |
| } |
| |
| private pageChanged(e: CustomEvent<PaginationSkPageChangedEventDetail>) { |
| const d = e.detail; |
| this.offset += d.delta * this.pageSize; |
| if (this.offset < 0) { |
| this.offset = 0; |
| } |
| this.stateChanged(); |
| this._render(); |
| this.fetch(); |
| } |
| |
| private toggleShowAll(e: Event) { |
| e.preventDefault(); |
| this.showAll = !this.showAll; |
| this.stateChanged(); |
| this._render(); |
| this.fetch(); |
| } |
| } |
| |
| define('changelists-page-sk', ChangelistsPageSk); |