Upstream version 10.38.222.0
[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: '@ToT Blink',
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: Make master a first-class parameter instead of replacing it with the group.
75     if (paramsMap.master) {
76         var errors = new ui.Errors();
77         if (paramsMap.master == 'TryServer')
78             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.');
79         else if (!builders.masters[paramsMap.master] && !builders.urlNameToMasterName[paramsMap.master])
80             errors.addError('ERROR: Unknown master name: ' + paramsMap.master);
81
82         if (errors.hasErrors()) {
83             errors.show();
84             window.location.hash = window.location.hash.replace('master=' + paramsMap.master, '');
85         } else {
86             var master = builders.urlNameToMasterName[paramsMap.master] || paramsMap.master;
87             var groupIndex = master == 'ChromiumWebkit' ? 1 : 0;
88             paramsMap.group = builders.masters[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     isBlinkGroup: function()
154     {
155         var group = this.crossDashboardState.group;
156         return group && group.indexOf('@ToT Blink') >= 0;
157     },
158     isLayoutTestResults: function()
159     {
160         return this.crossDashboardState.testType == 'layout-tests';
161     },
162     isGPUTestResults: function()
163     {
164         return this.crossDashboardState.testType == 'gpu_tests';
165     },
166     parseCrossDashboardParameters: function()
167     {
168         this.crossDashboardState = {};
169         var parameters = history.queryHashAsMap();
170         for (parameterName in history.DEFAULT_CROSS_DASHBOARD_STATE_VALUES)
171             this.parseParameter(parameters, parameterName);
172
173         history._fillMissingValues(this.crossDashboardState, history.DEFAULT_CROSS_DASHBOARD_STATE_VALUES);
174     },
175     _parseDashboardSpecificParameters: function()
176     {
177         this.dashboardSpecificState = {};
178         var parameters = history.queryHashAsMap();
179         for (parameterName in this._defaultDashboardSpecificStateValues)
180             this.parseParameter(parameters, parameterName);
181     },
182     // TODO(jparent): Make private once callers move here.
183     parseParameters: function()
184     {
185         var oldCrossDashboardState = this.crossDashboardState;
186         var oldDashboardSpecificState = this.dashboardSpecificState;
187
188         this.parseCrossDashboardParameters();
189
190         // Some parameters require loading different JSON files when the value changes. Do a reload.
191         if (Object.keys(oldCrossDashboardState).length) {
192             for (var key in this.crossDashboardState) {
193                 if (oldCrossDashboardState[key] != this.crossDashboardState[key] && history.reloadRequiringParameters.indexOf(key) != -1) {
194                     window.location.reload();
195                     return false;
196                 }
197             }
198         }
199
200         this._parseDashboardSpecificParameters();
201         var dashboardSpecificDiffState = history._diffStates(oldDashboardSpecificState, this.dashboardSpecificState);
202
203         history._fillMissingValues(this.dashboardSpecificState, this._defaultDashboardSpecificStateValues);
204
205         // FIXME: dashboard_base shouldn't know anything about specific dashboard specific keys.
206         if (dashboardSpecificDiffState.builder)
207             delete this.dashboardSpecificState.tests;
208         if (this.dashboardSpecificState.tests)
209             delete this.dashboardSpecificState.builder;
210
211         var shouldGeneratePage = true;
212         if (Object.keys(dashboardSpecificDiffState).length)
213             shouldGeneratePage = this._handleQueryParameterChange(this, dashboardSpecificDiffState);
214         return shouldGeneratePage;
215     },
216     // TODO(jparent): Make private once callers move here.
217     parseParameter: function(parameters, key)
218     {
219         if (!(key in parameters))
220             return;
221         var value = parameters[key];
222         if (!this._handleValidHashParameterWrapper(key, value))
223             console.log("Invalid query parameter: " + key + '=' + value);
224     },
225     // Takes a key and a value and sets the this.dashboardSpecificState[key] = value iff key is
226     // a valid hash parameter and the value is a valid value for that key. Handles
227     // cross-dashboard parameters then falls back to calling
228     // handleValidHashParameter for dashboard-specific parameters.
229     //
230     // @return {boolean} Whether the key what inserted into the this.dashboardSpecificState.
231     _handleValidHashParameterWrapper: function(key, value)
232     {
233         switch(key) {
234         case 'testType':
235             history.validateParameter(this.crossDashboardState, key, value,
236                 function() { return builders.testTypes.indexOf(value) != -1; });
237             return true;
238
239         case 'group':
240             history.validateParameter(this.crossDashboardState, key, value,
241                 function() {
242                     return builders.getAllGroupNames().indexOf(value) != -1;
243                 });
244             return true;
245
246         case 'useTestData':
247         case 'showAllRuns':
248             this.crossDashboardState[key] = value == 'true';
249             return true;
250
251         default:
252             return this._handleValidHashParameter(this, key, value);
253         }
254     },
255     queryParameterValue: function(parameter)
256     {
257         return this.dashboardSpecificState[parameter] || this.crossDashboardState[parameter];
258     },
259     // Sets the page state. Takes varargs of key, value pairs.
260     setQueryParameter: function(var_args)
261     {
262         var queryParamsAsState = {};
263         for (var i = 0; i < arguments.length; i += 2) {
264             var key = arguments[i];
265             queryParamsAsState[key] = arguments[i + 1];
266         }
267
268         this.invalidateQueryParameters(queryParamsAsState);
269
270         var newState = this._combinedDashboardState();
271         for (var key in queryParamsAsState) {
272             newState[key] = queryParamsAsState[key];
273         }
274
275         // Note: We use window.location.hash rather that window.location.replace
276         // because of bugs in Chrome where extra entries were getting created
277         // when back button was pressed and full page navigation was occuring.
278         // FIXME: file those bugs.
279         window.location.hash = this._permaLinkURLHash(newState);
280     },
281     toggleQueryParameter: function(param)
282     {
283         this.setQueryParameter(param, !this.queryParameterValue(param));
284     },
285     invalidateQueryParameters: function(queryParamsAsState)
286     {
287         for (var key in queryParamsAsState) {
288             if (key in CROSS_DB_INVALIDATING_PARAMETERS)
289                 delete this.crossDashboardState[CROSS_DB_INVALIDATING_PARAMETERS[key]];
290             if (this._dashboardSpecificInvalidatingParameters && key in this._dashboardSpecificInvalidatingParameters)
291                 delete this.dashboardSpecificState[this._dashboardSpecificInvalidatingParameters[key]];
292         }
293     },
294     _joinParameters: function(stateObject)
295     {
296         var state = [];
297         for (var key in stateObject) {
298             var value = stateObject[key];
299             if (value != this._defaultValue(key))
300                 state.push(key + '=' + encodeURIComponent(value));
301         }
302         return state.join('&');
303     },
304     _permaLinkURLHash: function(opt_state)
305     {
306         var state = opt_state || this._combinedDashboardState();
307         return '#' + this._joinParameters(state);
308     },
309     _combinedDashboardState: function()
310     {
311         var combinedState = Object.create(this.dashboardSpecificState);
312         for (var key in this.crossDashboardState)
313             combinedState[key] = this.crossDashboardState[key];
314         return combinedState;
315     },
316     _defaultValue: function(key)
317     {
318         if (key in this._defaultDashboardSpecificStateValues)
319             return this._defaultDashboardSpecificStateValues[key];
320         return history.DEFAULT_CROSS_DASHBOARD_STATE_VALUES[key];
321     },
322     _handleLocationChange: function()
323     {
324         if (this.parseParameters())
325             this._generatePage(this);
326     }
327
328 }
329
330 })();