2 Copyright (C) 2019 Apple Inc. All rights reserved.
4 Redistribution and use in source and binary forms, with or without
5 modification, are permitted provided that the following conditions
7 1. Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9 2. Redistributions in binary form must reproduce the above copyright
10 notice, this list of conditions and the following disclaimer in the
11 documentation and/or other materials provided with the distribution.
13 THIS SOFTWARE IS PROVIDED BY APPLE INC. "AS IS" AND ANY
14 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 {% extends "base.html" %}
28 <link rel="stylesheet" type="text/css" href="assets/css/search.css">
29 <link rel="stylesheet" type="text/css" href="assets/css/timeline.css">
30 <link rel="stylesheet" type="text/css" href="assets/css/tooltip.css">
32 <script type="module">
33 import {CommitBank} from '/assets/js/commit.js';
34 import {deepCompare, ErrorDisplay, queryToParams, paramsToQuery} from '/assets/js/common.js';
35 import {Configuration} from '/assets/js/configuration.js';
36 import {Drawer, BranchSelector, ConfigurationSelectors, LimitSlider} from '/assets/js/drawer.js';
37 import {SearchBar} from '/assets/js/search.js';
38 import {Legend, TimelineFromEndpoint} from '/assets/js/timeline.js';
39 import {ToolTip} from '/assets/js/tooltip.js';
40 import {DOM, REF, EventStream} from '/library/js/Ref.js';
42 const DEFAULT_LIMIT = 100;
43 const SUITES = JSON.parse('{{ suites|safe }}');
47 this.currentParams = queryToParams(document.URL.split('?')[1]);
48 this.currentLimit = this.currentParams.limit ? parseInt(this.currentParams.limit[this.currentParams.limit.length - 1]) : DEFAULT_LIMIT;
49 delete this.currentParams.limit;
51 let state = {children: [], prepending: []};
52 if (this.currentParams.test && (!this.currentParams.suite || this.currentParams.test.length != this.currentParams.suite.length))
53 state = {error: "Query Error", description: 'Must have the same number of suites and tests in query'};
54 else if (this.currentParams.test) {
55 for (let i = 0; i < this.currentParams.test.length; ++i) {
57 suite: this.currentParams.suite[i],
58 test: this.currentParams.test[i],
59 timeline: new TimelineFromEndpoint(`api/results/${this.currentParams.suite[i]}/${this.currentParams.test[i]}`, this.currentParams.suite[i]),
64 delete this.currentParams.suite;
65 delete this.currentParams.test;
68 this.ref = REF.createRef({
70 onStateUpdate: (element, diff, state) => {
72 DOM.inject(element, ErrorDisplay(diff));
76 function renderChild(child) {
77 let exitRef = REF.createRef({
78 onElementMount: element => {
79 element.onclick = () => {
80 for (let i = 0; i < self.ref.state.children.length; ++i) {
81 if (child.test !== self.ref.state.children[i].test && child.suite !== self.ref.state.children[i].suite)
83 self.ref.state.children.splice(i, 1);
87 const splitURL = document.URL.split('?');
88 let params = queryToParams(splitURL[1]);
93 for (let i = 0; i < params.suite.length; ++i) {
94 // This shouldn't be possible, just keep the current params if it happens.
95 if (i >= params.test.length)
97 if (params.suite[i] !== child.suite && params.test[i] !== child.test)
99 params.suite.splice(i, 1);
100 params.test.splice(i, 1);
103 if (params.suite.length === 0)
105 if (params.test.length === 0)
108 const queryString = paramsToQuery(params);
109 window.history.pushState(queryString, '', splitURL[0] + '?' + queryString);
111 const section = element.parentElement.parentElement.parentElement.parentElement;
112 section.parentElement.removeChild(section);
117 return `<div class="section">
119 <div class="title" style="font-size:var(--smallSize);word-break:break-word;">${child.test} (${child.suite})</div>
120 <div class="actions">
122 <a class="item" ref="${exitRef}">X</a>
131 DOM.inject(element, diff.children.map(renderChild).join(''));
132 if (diff.prepending) {
133 diff.prepending.forEach(child => {
134 DOM.after(element.firstElementChild, renderChild(child));
135 this.ref.state.children.unshift(child);
142 let params = queryToParams(document.URL.split('?')[1]);
143 let limit = params.limit ? parseInt(params.limit[params.limit.length - 1]) : DEFAULT_LIMIT;
145 // Try and determine if the tests we're displaying have changed.
146 if (params.test && (!params.suite || params.test.length != params.suite.length))
147 this.ref.setState({error: "Query Error", description: 'Must have the same number of suites and tests in query'});
148 else if (params.test) {
149 let needReRender = params.test.length != this.ref.state.children.length;
150 let newChildren = [];
152 for (let i = 0; i < params.test.length; ++i) {
154 for (let search = 0; search < this.ref.state.children.length; ++search) {
155 if (this.ref.state.children[search].suite === params.suite[i] && this.ref.state.children[search].test === params.test[i]) {
156 newChildren.push(this.ref.state.children[search]);
158 this.ref.state.children.splice(search, 1);
161 this.ref.state.children[search].timeline.teardown();
168 suite: params.suite[i],
169 test: params.test[i],
170 timeline: new TimelineFromEndpoint(`api/results/${params.suite[i]}/${params.test[i]}`),
176 this.ref.setState({children: newChildren});
178 this.ref.state.children = newChildren;
185 if (deepCompare(params, this.currentParams)) {
186 if (limit === this.currentLimit)
188 if (limit < this.currentLimit) {
189 this.currentLimit = limit;
190 this.ref.state.children.forEach(child => {
191 child.timeline.rerender();
195 this.currentLimit = limit;
198 this.ref.state.children.forEach(child => {
199 child.timeline.reload();
201 this.currentParams = params;
204 return `<div ref="${this.ref}"></div>`;
206 notifiyTimelinesRender() {
207 this.ref.state.children.forEach(child => {
208 child.timeline.notifiyRerender();
213 let view = new SearchView();
214 const onLayoutChange = new EventStream();
215 onLayoutChange.action(() => {
216 view.notifiyTimelinesRender();
218 window.onpopstate = event => {view.reload();};
219 window.onpushstate = event => {view.reload();};
223 document.getElementById('app'),
227 view.children.forEach((child) => {
228 child.timeline.update();
231 LimitSlider(() => {view.reload()}),
232 BranchSelector(() => {
236 ConfigurationSelectors(() => {view.reload()}),
237 ], () => onLayoutChange.add())}
239 <div class="main left under-topbar-with-actions">
240 <div class="content">
241 ${SearchBar(function () {
242 const splitURL = document.URL.split('?');
243 let params = queryToParams(splitURL[1]);
249 for (let i = 0; i < arguments.length; i++) {
250 let needToAdd = true;
255 suite: arguments[i].suite,
256 test: arguments[i].test,
257 timeline: new TimelineFromEndpoint(`api/results/${arguments[i].suite}/${arguments[i].test}`),
260 view.ref.setState({prepending: [child]});
261 params.suite.push(child.suite);
262 params.test.push(child.test);
264 const queryString = paramsToQuery(params);
265 window.history.pushState(queryString, '', splitURL[0] + '?' + queryString);