Home | History | Annotate | Download | only in static-dashboards
      1 // Copyright (C) 2012 Google Inc. All rights reserved.
      2 // Copyright (C) 2012 Zan Dobersek <zandobersek (a] gmail.com>
      3 //
      4 // Redistribution and use in source and binary forms, with or without
      5 // modification, are permitted provided that the following conditions are
      6 // met:
      7 //
      8 //         * Redistributions of source code must retain the above copyright
      9 // notice, this list of conditions and the following disclaimer.
     10 //         * Redistributions in binary form must reproduce the above
     11 // copyright notice, this list of conditions and the following disclaimer
     12 // in the documentation and/or other materials provided with the
     13 // distribution.
     14 //         * Neither the name of Google Inc. nor the names of its
     15 // contributors may be used to endorse or promote products derived from
     16 // this software without specific prior written permission.
     17 //
     18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29 
     30 var loader = loader || {};
     31 
     32 (function() {
     33 
     34 var TEST_RESULTS_SERVER = 'http://test-results.appspot.com/';
     35 
     36 function pathToBuilderResultsFile(builderName) {
     37     return TEST_RESULTS_SERVER + 'testfile?builder=' + builderName +
     38            '&master=' + builders.master(builderName).name +
     39            '&testtype=' + g_history.crossDashboardState.testType + '&name=';
     40 }
     41 
     42 loader.request = function(url, success, error, opt_isBinaryData)
     43 {
     44     var xhr = new XMLHttpRequest();
     45     xhr.open('GET', url, true);
     46     if (opt_isBinaryData)
     47         xhr.overrideMimeType('text/plain; charset=x-user-defined');
     48     xhr.onreadystatechange = function(e) {
     49         if (xhr.readyState == 4) {
     50             if (xhr.status == 200)
     51                 success(xhr);
     52             else
     53                 error(xhr);
     54         }
     55     }
     56     xhr.send();
     57 }
     58 
     59 loader.Loader = function()
     60 {
     61     this._loadingSteps = [
     62         this._loadBuildersList,
     63         this._loadResultsFiles,
     64     ];
     65 
     66     this._buildersThatFailedToLoad = [];
     67     this._staleBuilders = [];
     68     this._errors = new ui.Errors();
     69     // TODO(jparent): Pass in the appropriate history obj per db.
     70     this._history = g_history;
     71 }
     72 
     73 // TODO(aboxhall): figure out whether this is a performance bottleneck and
     74 // change calling code to understand the trie structure instead if necessary.
     75 loader.Loader._flattenTrie = function(trie, prefix)
     76 {
     77     var result = {};
     78     for (var name in trie) {
     79         var fullName = prefix ? prefix + "/" + name : name;
     80         var data = trie[name];
     81         if ("results" in data)
     82             result[fullName] = data;
     83         else {
     84             var partialResult = loader.Loader._flattenTrie(data, fullName);
     85             for (var key in partialResult) {
     86                 result[key] = partialResult[key];
     87             }
     88         }
     89     }
     90     return result;
     91 }
     92 
     93 loader.Loader.prototype = {
     94     load: function()
     95     {
     96         this._loadNext();
     97     },
     98     showErrors: function()
     99     {
    100         this._errors.show();
    101     },
    102     _loadNext: function()
    103     {
    104         var loadingStep = this._loadingSteps.shift();
    105         if (!loadingStep) {
    106             this._addErrors();
    107             this._history.initialize();
    108             return;
    109         }
    110         loadingStep.apply(this);
    111     },
    112     _loadBuildersList: function()
    113     {
    114         builders.loadBuildersList(currentBuilderGroupName(), this._history.crossDashboardState.testType);
    115         this._loadNext();
    116     },
    117     _loadResultsFiles: function()
    118     {
    119         var builderNames = Object.keys(currentBuilders());
    120         if (builderNames.length)
    121             builderNames.forEach(this._loadResultsFileForBuilder.bind(this));
    122         else
    123             this._loadNext();
    124 
    125     },
    126     _loadResultsFileForBuilder: function(builderName)
    127     {
    128         var resultsFilename;
    129         if (history.isTreeMap())
    130             resultsFilename = 'times_ms.json';
    131         else if (this._history.crossDashboardState.showAllRuns)
    132             resultsFilename = 'results.json';
    133         else
    134             resultsFilename = 'results-small.json';
    135 
    136         var resultsFileLocation = pathToBuilderResultsFile(builderName) + resultsFilename;
    137         loader.request(resultsFileLocation,
    138                 partial(function(loader, builderName, xhr) {
    139                     loader._handleResultsFileLoaded(builderName, xhr.responseText);
    140                 }, this, builderName),
    141                 partial(function(loader, builderName, xhr) {
    142                     loader._handleResultsFileLoadError(builderName);
    143                 }, this, builderName));
    144     },
    145     _handleResultsFileLoaded: function(builderName, fileData)
    146     {
    147         if (history.isTreeMap())
    148             this._processTimesJSONData(builderName, fileData);
    149         else
    150             this._processResultsJSONData(builderName, fileData);
    151 
    152         // We need this work-around for webkit.org/b/50589.
    153         if (!g_resultsByBuilder[builderName]) {
    154             this._handleResultsFileLoadError(builderName);
    155             return;
    156         }
    157 
    158         this._handleResourceLoad();
    159     },
    160     _processTimesJSONData: function(builderName, fileData)
    161     {
    162         // FIXME: We should probably include the builderName in the JSON
    163         // rather than relying on only loading one JSON file per page.
    164         g_resultsByBuilder[builderName] = JSON.parse(fileData);
    165     },
    166     _processResultsJSONData: function(builderName, fileData)
    167     {
    168         var builds = JSON.parse(fileData);
    169 
    170         if (builderName == 'version' || builderName == 'failure_map')
    171              return;
    172 
    173         var ONE_DAY_SECONDS = 60 * 60 * 24;
    174         var ONE_WEEK_SECONDS = ONE_DAY_SECONDS * 7;
    175 
    176         // If a test suite stops being run on a given builder, we don't want to show it.
    177         // Assume any builder without a run in two weeks for a given test suite isn't
    178         // running that suite anymore.
    179         // FIXME: Grab which bots run which tests directly from the buildbot JSON instead.
    180         var lastRunSeconds = builds[builderName].secondsSinceEpoch[0];
    181         if ((Date.now() / 1000) - lastRunSeconds > ONE_WEEK_SECONDS)
    182             return;
    183 
    184         if ((Date.now() / 1000) - lastRunSeconds > ONE_DAY_SECONDS)
    185             this._staleBuilders.push(builderName);
    186 
    187         builds[builderName][results.TESTS] = loader.Loader._flattenTrie(builds[builderName][results.TESTS]);
    188         g_resultsByBuilder[builderName] = builds[builderName];
    189     },
    190     _handleResultsFileLoadError: function(builderName)
    191     {
    192         console.error('Failed to load results file for ' + builderName + '.');
    193 
    194         // FIXME: loader shouldn't depend on state defined in dashboard_base.js.
    195         this._buildersThatFailedToLoad.push(builderName);
    196 
    197         // Remove this builder from builders, so we don't try to use the
    198         // data that isn't there.
    199         delete currentBuilders()[builderName];
    200 
    201         // Proceed as if the resource had loaded.
    202         this._handleResourceLoad();
    203     },
    204     _handleResourceLoad: function()
    205     {
    206         if (this._haveResultsFilesLoaded())
    207             this._loadNext();
    208     },
    209     _haveResultsFilesLoaded: function()
    210     {
    211         for (var builderName in currentBuilders()) {
    212             if (!g_resultsByBuilder[builderName] && this._buildersThatFailedToLoad.indexOf(builderName) < 0)
    213                 return false;
    214         }
    215         return true;
    216     },
    217     _addErrors: function()
    218     {
    219         if (this._buildersThatFailedToLoad.length)
    220             this._errors.addError('ERROR: Failed to get data from ' + this._buildersThatFailedToLoad.toString() +'.');
    221 
    222         if (this._staleBuilders.length)
    223             this._errors.addError('ERROR: Data from ' + this._staleBuilders.toString() + ' is more than 1 day stale.');
    224     }
    225 }
    226 
    227 })();
    228