- add third_party src.
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Tools / TestResultServer / static-dashboards / history.js
1 // Copyright (C) 2013 Google Inc. All rights reserved.
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 //         * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 //         * Redistributions in binary form must reproduce the above
10 // copyright notice, this list of conditions and the following disclaimer
11 // in the documentation and/or other materials provided with the
12 // distribution.
13 //         * Neither the name of Google Inc. nor the names of its
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29
30 var history = history || {};
31
32 (function() {
33
34 history.DEFAULT_CROSS_DASHBOARD_STATE_VALUES = {
35     group: null,
36     showAllRuns: false,
37     testType: 'layout-tests',
38     useTestData: false,
39 }
40
41 history.validateParameter = function(state, key, value, validateFn)
42 {
43     if (validateFn()) {
44         state[key] = value;
45         return true;
46     } else {
47         console.log(key + ' value is not valid: ' + value);
48         return false;
49     }
50 }
51
52 history.isTreeMap = function()
53 {
54     return string.endsWith(window.location.pathname, 'treemap.html');
55 }
56
57 // TODO(jparent): Make private once callers move here.
58 history.queryHashAsMap = function()
59 {
60     var hash = window.location.hash;
61     var paramsList = hash ? hash.substring(1).split('&') : [];
62     var paramsMap = {};
63     var invalidKeys = [];
64     for (var i = 0; i < paramsList.length; i++) {
65         var thisParam = paramsList[i].split('=');
66         if (thisParam.length != 2) {
67             console.log('Invalid query parameter: ' + paramsList[i]);
68             continue;
69         }
70
71         paramsMap[thisParam[0]] = decodeURIComponent(thisParam[1]);
72     }
73
74     // FIXME: remove support for mapping from the master parameter to the group
75     // one once the waterfall starts to pass in the builder name instead.
76     if (paramsMap.master) {
77         var errors = new ui.Errors();
78         if (paramsMap.master == 'TryServer')
79             errors.addError('ERROR: You got here from the trybot waterfall. The try bots do not record data in the flakiness dashboard. Showing results for the regular waterfall.');
80         else if (!builders.masters[paramsMap.master])
81             errors.addError('ERROR: Unknown master name: ' + paramsMap.master);
82
83         if (errors.hasErrors()) {
84             errors.show();
85             window.location.hash = window.location.hash.replace('master=' + paramsMap.master, '');
86         } else {
87             var groupIndex = paramsMap.master == 'ChromiumWebkit' ? 1 : 0;
88             paramsMap.group = builders.masters[paramsMap.master].groups[groupIndex];
89             window.location.hash = window.location.hash.replace('master=' + paramsMap.master, 'group=' + encodeURIComponent(paramsMap.group));
90             delete paramsMap.master;
91         }
92     }
93
94     // FIXME: Find a better way to do this. For layout-tests, we want the default group to be
95     // the ToT blink group. For other test types, we want it to be the Deps group.
96     if (!paramsMap.group && (!paramsMap.testType || paramsMap.testType == 'layout-tests'))
97         paramsMap.group = builders.groupNamesForTestType('layout-tests')[1];
98
99     return paramsMap;
100 }
101
102 history._diffStates = function(oldState, newState)
103 {
104     // If there is no old state, everything in the current state is new.
105     if (!oldState)
106         return newState;
107
108     var changedParams = {};
109     for (curKey in newState) {
110         var oldVal = oldState[curKey];
111         var newVal = newState[curKey];
112         // Add new keys or changed values.
113         if (!oldVal || oldVal != newVal)
114             changedParams[curKey] = newVal;
115     }
116     return changedParams;
117 }
118
119 history._fillMissingValues = function(to, from)
120 {
121     for (var state in from) {
122         if (!(state in to))
123             to[state] = from[state];
124     }
125 }
126
127 history.History = function(configuration)
128 {
129     this.crossDashboardState = {};
130     this.dashboardSpecificState = {};
131
132     if (configuration) {
133         this._defaultDashboardSpecificStateValues = configuration.defaultStateValues;
134         this._handleValidHashParameter = configuration.handleValidHashParameter;
135         this._handleQueryParameterChange = configuration.handleQueryParameterChange || function(historyInstance, params) { return true; };
136         this._dashboardSpecificInvalidatingParameters = configuration.invalidatingHashParameters;
137         this._generatePage = configuration.generatePage;
138     }
139 }
140
141 history.reloadRequiringParameters = ['showAllRuns', 'group', 'testType'];
142
143 var CROSS_DB_INVALIDATING_PARAMETERS = {
144     'testType': 'group'
145 };
146
147 history.History.prototype = {
148     initialize: function()
149     {
150         window.onhashchange = this._handleLocationChange.bind(this);
151         this._handleLocationChange();
152     },
153     isLayoutTestResults: function()
154     {
155         return this.crossDashboardState.testType == 'layout-tests';
156     },
157     isGPUTestResults: function()
158     {
159         return this.crossDashboardState.testType == 'gpu_tests';
160     },
161     parseCrossDashboardParameters: function()
162     {
163         this.crossDashboardState = {};
164         var parameters = history.queryHashAsMap();
165         for (parameterName in history.DEFAULT_CROSS_DASHBOARD_STATE_VALUES)
166             this.parseParameter(parameters, parameterName);
167
168         history._fillMissingValues(this.crossDashboardState, history.DEFAULT_CROSS_DASHBOARD_STATE_VALUES);
169     },
170     _parseDashboardSpecificParameters: function()
171     {
172         this.dashboardSpecificState = {};
173         var parameters = history.queryHashAsMap();
174         for (parameterName in this._defaultDashboardSpecificStateValues)
175             this.parseParameter(parameters, parameterName);
176     },
177     // TODO(jparent): Make private once callers move here.
178     parseParameters: function()
179     {
180         var oldCrossDashboardState = this.crossDashboardState;
181         var oldDashboardSpecificState = this.dashboardSpecificState;
182
183         this.parseCrossDashboardParameters();
184
185         // Some parameters require loading different JSON files when the value changes. Do a reload.
186         if (Object.keys(oldCrossDashboardState).length) {
187             for (var key in this.crossDashboardState) {
188                 if (oldCrossDashboardState[key] != this.crossDashboardState[key] && history.reloadRequiringParameters.indexOf(key) != -1) {
189                     window.location.reload();
190                     return false;
191                 }
192             }
193         }
194
195         this._parseDashboardSpecificParameters();
196         var dashboardSpecificDiffState = history._diffStates(oldDashboardSpecificState, this.dashboardSpecificState);
197
198         history._fillMissingValues(this.dashboardSpecificState, this._defaultDashboardSpecificStateValues);
199
200         // FIXME: dashboard_base shouldn't know anything about specific dashboard specific keys.
201         if (dashboardSpecificDiffState.builder)
202             delete this.dashboardSpecificState.tests;
203         if (this.dashboardSpecificState.tests)
204             delete this.dashboardSpecificState.builder;
205
206         var shouldGeneratePage = true;
207         if (Object.keys(dashboardSpecificDiffState).length)
208             shouldGeneratePage = this._handleQueryParameterChange(this, dashboardSpecificDiffState);
209         return shouldGeneratePage;
210     },
211     // TODO(jparent): Make private once callers move here.
212     parseParameter: function(parameters, key)
213     {
214         if (!(key in parameters))
215             return;
216         var value = parameters[key];
217         if (!this._handleValidHashParameterWrapper(key, value))
218             console.log("Invalid query parameter: " + key + '=' + value);
219     },
220     // Takes a key and a value and sets the this.dashboardSpecificState[key] = value iff key is
221     // a valid hash parameter and the value is a valid value for that key. Handles
222     // cross-dashboard parameters then falls back to calling
223     // handleValidHashParameter for dashboard-specific parameters.
224     //
225     // @return {boolean} Whether the key what inserted into the this.dashboardSpecificState.
226     _handleValidHashParameterWrapper: function(key, value)
227     {
228         switch(key) {
229         case 'testType':
230             history.validateParameter(this.crossDashboardState, key, value,
231                 function() { return builders.testTypes.indexOf(value) != -1; });
232             return true;
233
234         case 'group':
235             history.validateParameter(this.crossDashboardState, key, value,
236                 function() {
237                     return builders.getAllGroupNames().indexOf(value) != -1;
238                 });
239             return true;
240
241         case 'useTestData':
242         case 'showAllRuns':
243             this.crossDashboardState[key] = value == 'true';
244             return true;
245
246         default:
247             return this._handleValidHashParameter(this, key, value);
248         }
249     },
250     queryParameterValue: function(parameter)
251     {
252         return this.dashboardSpecificState[parameter] || this.crossDashboardState[parameter];
253     },
254     // Sets the page state. Takes varargs of key, value pairs.
255     setQueryParameter: function(var_args)
256     {
257         var queryParamsAsState = {};
258         for (var i = 0; i < arguments.length; i += 2) {
259             var key = arguments[i];
260             queryParamsAsState[key] = arguments[i + 1];
261         }
262
263         this.invalidateQueryParameters(queryParamsAsState);
264
265         var newState = this._combinedDashboardState();
266         for (var key in queryParamsAsState) {
267             newState[key] = queryParamsAsState[key];
268         }
269
270         // Note: We use window.location.hash rather that window.location.replace
271         // because of bugs in Chrome where extra entries were getting created
272         // when back button was pressed and full page navigation was occuring.
273         // FIXME: file those bugs.
274         window.location.hash = this._permaLinkURLHash(newState);
275     },
276     toggleQueryParameter: function(param)
277     {
278         this.setQueryParameter(param, !this.queryParameterValue(param));
279     },
280     invalidateQueryParameters: function(queryParamsAsState)
281     {
282         for (var key in queryParamsAsState) {
283             if (key in CROSS_DB_INVALIDATING_PARAMETERS)
284                 delete this.crossDashboardState[CROSS_DB_INVALIDATING_PARAMETERS[key]];
285             if (this._dashboardSpecificInvalidatingParameters && key in this._dashboardSpecificInvalidatingParameters)
286                 delete this.dashboardSpecificState[this._dashboardSpecificInvalidatingParameters[key]];
287         }
288     },
289     _joinParameters: function(stateObject)
290     {
291         var state = [];
292         for (var key in stateObject) {
293             var value = stateObject[key];
294             if (value != this._defaultValue(key))
295                 state.push(key + '=' + encodeURIComponent(value));
296         }
297         return state.join('&');
298     },
299     _permaLinkURLHash: function(opt_state)
300     {
301         var state = opt_state || this._combinedDashboardState();
302         return '#' + this._joinParameters(state);
303     },
304     _combinedDashboardState: function()
305     {
306         var combinedState = Object.create(this.dashboardSpecificState);
307         for (var key in this.crossDashboardState)
308             combinedState[key] = this.crossDashboardState[key];
309         return combinedState;
310     },
311     _defaultValue: function(key)
312     {
313         if (key in this._defaultDashboardSpecificStateValues)
314             return this._defaultDashboardSpecificStateValues[key];
315         return history.DEFAULT_CROSS_DASHBOARD_STATE_VALUES[key];
316     },
317     _handleLocationChange: function()
318     {
319         if (this.parseParameters())
320             this._generatePage(this);
321     }
322
323 }
324
325 })();