| // Copyright 2021 The LUCI Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import '@material/mwc-button'; |
| import { css, customElement, html } from 'lit-element'; |
| import { observable, reaction } from 'mobx'; |
| |
| import '../components/dot_spinner'; |
| import '../components/hotkey'; |
| import '../components/test_search_filter'; |
| import '../components/test_variants_table'; |
| import '../components/test_variants_table/tvt_config_widget'; |
| import { MiloBaseElement } from '../components/milo_base'; |
| import { TestVariantsTableElement } from '../components/test_variants_table'; |
| import { AppState, consumeAppState } from '../context/app_state'; |
| import { consumeInvocationState, InvocationState } from '../context/invocation_state'; |
| import { consumeConfigsStore, UserConfigsStore } from '../context/user_configs'; |
| import { GA_ACTIONS, GA_CATEGORIES, trackEvent } from '../libs/analytics_utils'; |
| import { consumer } from '../libs/context'; |
| import { errorHandler, forwardWithoutMsg, reportRenderError } from '../libs/error_handler'; |
| import commonStyle from '../styles/common_style.css'; |
| |
| /** |
| * Display test results. |
| */ |
| @customElement('milo-test-results-tab') |
| @errorHandler(forwardWithoutMsg) |
| @consumer |
| export class TestResultsTabElement extends MiloBaseElement { |
| @observable.ref |
| @consumeAppState() |
| appState!: AppState; |
| |
| @observable.ref |
| @consumeConfigsStore() |
| configsStore!: UserConfigsStore; |
| |
| @observable.ref |
| @consumeInvocationState() |
| invocationState!: InvocationState; |
| |
| private allVariantsWereExpanded = false; |
| private toggleAllVariants(expand: boolean) { |
| this.allVariantsWereExpanded = expand; |
| this.shadowRoot!.querySelector<TestVariantsTableElement>('milo-test-variants-table')!.toggleAllVariants(expand); |
| } |
| private readonly toggleAllVariantsByHotkey = () => this.toggleAllVariants(!this.allVariantsWereExpanded); |
| |
| connectedCallback() { |
| super.connectedCallback(); |
| this.appState.selectedTabId = 'test-results'; |
| trackEvent(GA_CATEGORIES.TEST_RESULTS_TAB, GA_ACTIONS.TAB_VISITED, window.location.href); |
| |
| // Update filters to match the querystring without saving them. |
| const searchParams = new URLSearchParams(window.location.search); |
| if (searchParams.has('q')) { |
| this.invocationState.searchText = searchParams.get('q')!; |
| } |
| if (searchParams.has('cols')) { |
| const cols = searchParams.get('cols')!; |
| this.invocationState.columnsParam = cols.split(',').filter((col) => col !== ''); |
| } |
| if (searchParams.has('sortby')) { |
| const sortingKeys = searchParams.get('sortby')!; |
| this.invocationState.sortingKeysParam = sortingKeys.split(',').filter((col) => col !== ''); |
| } |
| if (searchParams.has('groupby')) { |
| const groupingKeys = searchParams.get('groupby')!; |
| this.invocationState.groupingKeysParam = groupingKeys.split(',').filter((key) => key !== ''); |
| } |
| |
| // Update the querystring when filters are updated. |
| this.addDisposer( |
| reaction( |
| () => { |
| const displayedCols = this.invocationState.displayedColumns.join(','); |
| const defaultCols = this.invocationState.defaultColumns.join(','); |
| const sortingKeys = this.invocationState.sortingKeys.join(','); |
| const defaultSortingKeys = this.invocationState.defaultSortingKeys.join(','); |
| const groupingKeys = this.invocationState.groupingKeys.join(','); |
| const defaultGroupingKeys = this.invocationState.defaultGroupingKeys.join(','); |
| |
| const newSearchParams = new URLSearchParams({ |
| ...(!this.invocationState.searchText ? {} : { q: this.invocationState.searchText }), |
| ...(displayedCols === defaultCols ? {} : { cols: displayedCols }), |
| ...(sortingKeys === defaultSortingKeys ? {} : { sortby: sortingKeys }), |
| ...(groupingKeys === defaultGroupingKeys ? {} : { groupby: groupingKeys }), |
| }); |
| const newSearchParamsStr = newSearchParams.toString(); |
| return newSearchParamsStr ? '?' + newSearchParamsStr : ''; |
| }, |
| (newQueryStr) => { |
| const location = window.location; |
| const newUrl = `${location.protocol}//${location.host}${location.pathname}${newQueryStr}`; |
| window.history.replaceState({ path: newUrl }, '', newUrl); |
| }, |
| { fireImmediately: true } |
| ) |
| ); |
| |
| this.addDisposer( |
| reaction( |
| () => this.configsStore.userConfigs.testResults.columnWidths, |
| (columnWidths) => (this.invocationState.customColumnWidths = columnWidths), |
| { fireImmediately: true } |
| ) |
| ); |
| } |
| |
| private renderBody() { |
| const state = this.invocationState; |
| |
| if (state.invocationId === '') { |
| return html` |
| <div id="no-invocation"> |
| No associated invocation.<br /> |
| You need to integrate with ResultDB to see the test results.<br /> |
| See <a href="http://go/resultdb" target="_blank">go/resultdb</a> or ask |
| <a href="mailto: luci-eng@google.com" target="_blank">luci-eng@</a> for help. |
| </div> |
| `; |
| } |
| |
| return html` |
| ${this.invocationState.warning |
| ? html`<div id="test-results-tab-warning">${this.invocationState.warning}</div>` |
| : ''} |
| <milo-test-variants-table |
| style="--columns: ${this.invocationState.columnWidths.map((width) => width + 'px').join(' ')}" |
| ></milo-test-variants-table> |
| `; |
| } |
| |
| protected render = reportRenderError.bind(this)(() => { |
| return html` |
| <div id="header"> |
| <milo-tvt-config-widget class="filters-container"></milo-tvt-config-widget> |
| <div class="filters-container-delimiter"></div> |
| <milo-test-search-filter></milo-test-search-filter> |
| <milo-hotkey key="x" .handler=${this.toggleAllVariantsByHotkey} title="press x to expand/collapse all entries"> |
| <mwc-button dense unelevated @click=${() => this.toggleAllVariants(true)}>Expand All</mwc-button> |
| <mwc-button dense unelevated @click=${() => this.toggleAllVariants(false)}>Collapse All</mwc-button> |
| </milo-hotkey> |
| </div> |
| ${this.renderBody()} |
| `; |
| }); |
| |
| static styles = [ |
| commonStyle, |
| css` |
| :host { |
| /* Required to make 'position: sticky' work in Safari. */ |
| display: block; |
| } |
| |
| #header { |
| display: grid; |
| grid-template-columns: auto auto 1fr auto; |
| grid-gap: 5px; |
| height: 30px; |
| padding: 5px 10px 3px 10px; |
| position: sticky; |
| top: 0px; |
| background: white; |
| border-bottom: 1px solid var(--divider-color); |
| z-index: 2; |
| } |
| milo-test-search-filter { |
| max-width: 1000px; |
| } |
| mwc-button { |
| margin-top: 1px; |
| width: var(--expand-button-width); |
| } |
| |
| .filters-container { |
| display: inline-block; |
| padding: 4px 5px 0; |
| } |
| .filters-container-delimiter { |
| border-left: 1px solid var(--divider-color); |
| width: 0px; |
| height: 100%; |
| } |
| |
| #test-results-tab-warning { |
| background-color: var(--warning-color); |
| border-bottom: 1px solid var(--divider-color); |
| height: 24px; |
| line-height: 24px; |
| text-align: center; |
| } |
| `, |
| ]; |
| } |