Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Loader: | 2 * Loader: |
| 3 * Reads GM result reports written out by results.py, and imports | 3 * Reads GM result reports written out by results.py, and imports |
| 4 * them into $scope.categories and $scope.testData . | 4 * them into $scope.categories and $scope.testData . |
| 5 */ | 5 */ |
| 6 var Loader = angular.module( | 6 var Loader = angular.module( |
| 7 'Loader', | 7 'Loader', |
| 8 [] | 8 [] |
| 9 ); | 9 ); |
| 10 | 10 |
| 11 | |
| 11 // TODO(epoger): Combine ALL of our filtering operations (including | 12 // TODO(epoger): Combine ALL of our filtering operations (including |
| 12 // truncation) into this one filter, so that runs most efficiently? | 13 // truncation) into this one filter, so that runs most efficiently? |
| 13 // (We would have to make sure truncation still took place after | 14 // (We would have to make sure truncation still took place after |
| 14 // sorting, though.) | 15 // sorting, though.) |
| 15 Loader.filter( | 16 Loader.filter( |
| 16 'removeHiddenItems', | 17 'removeHiddenItems', |
| 17 function() { | 18 function() { |
| 18 return function(unfilteredItems, hiddenResultTypes, hiddenConfigs, | 19 return function(unfilteredItems, hiddenResultTypes, hiddenConfigs, |
| 19 viewingTab) { | 20 viewingTab) { |
| 20 var filteredItems = []; | 21 var filteredItems = []; |
| 21 for (var i = 0; i < unfilteredItems.length; i++) { | 22 for (var i = 0; i < unfilteredItems.length; i++) { |
| 22 var item = unfilteredItems[i]; | 23 var item = unfilteredItems[i]; |
| 24 // For performance, we examine the "set" objects directly rather | |
| 25 // than calling $scope.isValueInSet(). | |
| 23 if (!(true == hiddenResultTypes[item.resultType]) && | 26 if (!(true == hiddenResultTypes[item.resultType]) && |
| 24 !(true == hiddenConfigs[item.config]) && | 27 !(true == hiddenConfigs[item.config]) && |
| 25 (viewingTab == item.tab)) { | 28 (viewingTab == item.tab)) { |
| 26 filteredItems.push(item); | 29 filteredItems.push(item); |
| 27 } | 30 } |
| 28 } | 31 } |
| 29 return filteredItems; | 32 return filteredItems; |
| 30 }; | 33 }; |
| 31 } | 34 } |
| 32 ); | 35 ); |
| 33 | 36 |
| 37 | |
| 34 Loader.controller( | 38 Loader.controller( |
| 35 'Loader.Controller', | 39 'Loader.Controller', |
| 36 function($scope, $http, $filter, $location) { | 40 function($scope, $http, $filter, $location) { |
| 37 $scope.windowTitle = "Loading GM Results..."; | 41 $scope.windowTitle = "Loading GM Results..."; |
| 38 var resultsToLoad = $location.search().resultsToLoad; | 42 var resultsToLoad = $location.search().resultsToLoad; |
| 39 $scope.loadingMessage = "Loading results of type '" + resultsToLoad + | 43 $scope.loadingMessage = "Loading results of type '" + resultsToLoad + |
| 40 "', please wait..."; | 44 "', please wait..."; |
| 41 | 45 |
| 46 /** | |
| 47 * On initial page load, load a full dictionary of results. | |
| 48 * Once the dictionary is loaded, unhide the page elements so they can | |
| 49 * render the data. | |
| 50 */ | |
| 42 $http.get("/results/" + resultsToLoad).success( | 51 $http.get("/results/" + resultsToLoad).success( |
| 43 function(data, status, header, config) { | 52 function(data, status, header, config) { |
| 44 $scope.loadingMessage = "Processing data, please wait..."; | 53 $scope.loadingMessage = "Processing data, please wait..."; |
| 45 | 54 |
| 46 $scope.header = data.header; | 55 $scope.header = data.header; |
| 47 $scope.categories = data.categories; | 56 $scope.categories = data.categories; |
| 48 $scope.testData = data.testData; | 57 $scope.testData = data.testData; |
| 49 $scope.sortColumn = 'test'; | 58 $scope.sortColumn = 'test'; |
| 50 $scope.showTodos = false; | 59 $scope.showTodos = false; |
| 51 | 60 |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 67 $scope.numResultsPerTab[$scope.tabs[i]] = 0; | 76 $scope.numResultsPerTab[$scope.tabs[i]] = 0; |
| 68 } | 77 } |
| 69 $scope.numResultsPerTab[$scope.defaultTab] = $scope.testData.length; | 78 $scope.numResultsPerTab[$scope.defaultTab] = $scope.testData.length; |
| 70 | 79 |
| 71 // Add index and tab fields to all records. | 80 // Add index and tab fields to all records. |
| 72 for (var i = 0; i < $scope.testData.length; i++) { | 81 for (var i = 0; i < $scope.testData.length; i++) { |
| 73 $scope.testData[i].index = i; | 82 $scope.testData[i].index = i; |
| 74 $scope.testData[i].tab = $scope.defaultTab; | 83 $scope.testData[i].tab = $scope.defaultTab; |
| 75 } | 84 } |
| 76 | 85 |
| 86 // Arrays within which the user can toggle individual elements. | |
| 87 $scope.selectedItems = []; | |
| 88 | |
| 89 // Sets within which the user can toggle individual elements. | |
| 77 $scope.hiddenResultTypes = { | 90 $scope.hiddenResultTypes = { |
| 78 'failure-ignored': true, | 91 'failure-ignored': true, |
| 79 'no-comparison': true, | 92 'no-comparison': true, |
| 80 'succeeded': true, | 93 'succeeded': true, |
| 81 }; | 94 }; |
| 82 $scope.hiddenConfigs = {}; | 95 $scope.hiddenConfigs = {}; |
| 83 $scope.selectedItems = []; | |
| 84 | 96 |
| 85 $scope.updateResults(); | 97 $scope.updateResults(); |
| 86 $scope.loadingMessage = ""; | 98 $scope.loadingMessage = ""; |
| 87 $scope.windowTitle = "Current GM Results"; | 99 $scope.windowTitle = "Current GM Results"; |
| 88 } | 100 } |
| 89 ).error( | 101 ).error( |
| 90 function(data, status, header, config) { | 102 function(data, status, header, config) { |
| 91 $scope.loadingMessage = "Failed to load results of type '" | 103 $scope.loadingMessage = "Failed to load results of type '" |
| 92 + resultsToLoad + "'"; | 104 + resultsToLoad + "'"; |
| 93 $scope.windowTitle = "Failed to Load GM Results"; | 105 $scope.windowTitle = "Failed to Load GM Results"; |
| 94 } | 106 } |
| 95 ); | 107 ); |
| 96 | 108 |
| 97 $scope.isItemSelected = function(index) { | |
| 98 return (-1 != $scope.selectedItems.indexOf(index)); | |
| 99 } | |
| 100 $scope.toggleItemSelected = function(index) { | |
| 101 var i = $scope.selectedItems.indexOf(index); | |
| 102 if (-1 == i) { | |
| 103 $scope.selectedItems.push(index); | |
| 104 } else { | |
| 105 $scope.selectedItems.splice(i, 1); | |
| 106 } | |
| 107 // unlike other toggle methods below, does not set | |
| 108 // $scope.areUpdatesPending = true; | |
| 109 } | |
| 110 | 109 |
| 111 $scope.isHiddenResultType = function(thisResultType) { | 110 // |
| 112 return (true == $scope.hiddenResultTypes[thisResultType]); | 111 // Tab operations. |
| 113 } | 112 // |
| 114 $scope.toggleHiddenResultType = function(thisResultType) { | |
| 115 if (true == $scope.hiddenResultTypes[thisResultType]) { | |
| 116 delete $scope.hiddenResultTypes[thisResultType]; | |
| 117 } else { | |
| 118 $scope.hiddenResultTypes[thisResultType] = true; | |
| 119 } | |
| 120 $scope.areUpdatesPending = true; | |
| 121 } | |
| 122 | 113 |
| 123 // TODO(epoger): Rather than maintaining these as hard-coded | 114 /** |
|
epoger
2013/10/23 20:10:31
Combined these functions into generic versions and
| |
| 124 // variants of isHiddenResultType and toggleHiddenResultType, we | 115 * Change the selected tab. |
| 125 // should create general-purpose functions that can work with ANY | 116 * |
| 126 // category. | 117 * @param tab (string): name of the tab to select |
| 127 // But for now, I wanted to see this working. :-) | 118 */ |
| 128 $scope.isHiddenConfig = function(thisConfig) { | |
| 129 return (true == $scope.hiddenConfigs[thisConfig]); | |
| 130 } | |
| 131 $scope.toggleHiddenConfig = function(thisConfig) { | |
| 132 if (true == $scope.hiddenConfigs[thisConfig]) { | |
| 133 delete $scope.hiddenConfigs[thisConfig]; | |
| 134 } else { | |
| 135 $scope.hiddenConfigs[thisConfig] = true; | |
| 136 } | |
| 137 $scope.areUpdatesPending = true; | |
| 138 } | |
| 139 | |
| 140 $scope.setViewingTab = function(tab) { | 119 $scope.setViewingTab = function(tab) { |
| 141 $scope.viewingTab = tab; | 120 $scope.viewingTab = tab; |
| 142 $scope.updateResults(); | 121 $scope.updateResults(); |
| 143 } | 122 } |
| 144 | 123 |
| 145 $scope.localTimeString = function(secondsPastEpoch) { | |
| 146 var d = new Date(secondsPastEpoch * 1000); | |
| 147 return d.toString(); | |
| 148 } | |
| 149 | |
| 150 /** | 124 /** |
| 151 * Move the items in $scope.selectedItems to a different tab, | 125 * Move the items in $scope.selectedItems to a different tab, |
| 152 * and then clear $scope.selectedItems. | 126 * and then clear $scope.selectedItems. |
| 153 * | 127 * |
| 154 * @param newTab (string): name of the tab to move the tests to | 128 * @param newTab (string): name of the tab to move the tests to |
| 155 */ | 129 */ |
| 156 $scope.moveSelectedItemsToTab = function(newTab) { | 130 $scope.moveSelectedItemsToTab = function(newTab) { |
| 157 $scope.moveItemsToTab($scope.selectedItems, newTab); | 131 $scope.moveItemsToTab($scope.selectedItems, newTab); |
| 158 $scope.selectedItems = []; | 132 $scope.selectedItems = []; |
| 159 $scope.updateResults(); | 133 $scope.updateResults(); |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 170 var itemIndex; | 144 var itemIndex; |
| 171 var numItems = itemIndices.length; | 145 var numItems = itemIndices.length; |
| 172 for (var i = 0; i < numItems; i++) { | 146 for (var i = 0; i < numItems; i++) { |
| 173 itemIndex = itemIndices[i]; | 147 itemIndex = itemIndices[i]; |
| 174 $scope.numResultsPerTab[$scope.testData[itemIndex].tab]--; | 148 $scope.numResultsPerTab[$scope.testData[itemIndex].tab]--; |
| 175 $scope.testData[itemIndex].tab = newTab; | 149 $scope.testData[itemIndex].tab = newTab; |
| 176 } | 150 } |
| 177 $scope.numResultsPerTab[newTab] += numItems; | 151 $scope.numResultsPerTab[newTab] += numItems; |
| 178 } | 152 } |
| 179 | 153 |
| 154 | |
| 155 // | |
| 156 // updateResults() and friends. | |
| 157 // | |
| 158 | |
| 159 /** | |
| 160 * Set $scope.areUpdatesPending (to enable/disable the Update Results | |
| 161 * button). | |
| 162 * | |
| 163 * @param val boolean value to set $scope.areUpdatesPending to | |
| 164 */ | |
| 165 $scope.setUpdatesPending = function(val) { | |
| 166 $scope.areUpdatesPending = val; | |
| 167 } | |
|
borenet
2013/10/23 20:31:46
Why is this helper function necessary?
epoger
2013/10/24 15:37:17
Great question. I didn't know why, but by asking
borenet
2013/10/28 12:57:09
Thanks for explaining. I don't think this is a TOD
| |
| 168 | |
| 169 /** | |
| 170 * Update the displayed results, based on filters/settings. | |
| 171 */ | |
| 180 $scope.updateResults = function() { | 172 $scope.updateResults = function() { |
| 181 $scope.displayLimit = $scope.displayLimitPending; | 173 $scope.displayLimit = $scope.displayLimitPending; |
| 182 // TODO(epoger): Every time we apply a filter, AngularJS creates | 174 // TODO(epoger): Every time we apply a filter, AngularJS creates |
| 183 // another copy of the array. Is there a way we can filter out | 175 // another copy of the array. Is there a way we can filter out |
| 184 // the items as they are displayed, rather than storing multiple | 176 // the items as they are displayed, rather than storing multiple |
| 185 // array copies? (For better performance.) | 177 // array copies? (For better performance.) |
| 186 | 178 |
| 187 if ($scope.viewingTab == $scope.defaultTab) { | 179 if ($scope.viewingTab == $scope.defaultTab) { |
| 188 $scope.filteredTestData = | 180 $scope.filteredTestData = |
| 189 $filter("orderBy")( | 181 $filter("orderBy")( |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 202 $filter("filter")( | 194 $filter("filter")( |
| 203 $scope.testData, | 195 $scope.testData, |
| 204 {tab: $scope.viewingTab}, | 196 {tab: $scope.viewingTab}, |
| 205 true | 197 true |
| 206 ), | 198 ), |
| 207 $scope.sortColumn); | 199 $scope.sortColumn); |
| 208 $scope.limitedTestData = $filter("limitTo")( | 200 $scope.limitedTestData = $filter("limitTo")( |
| 209 $scope.filteredTestData, $scope.displayLimit); | 201 $scope.filteredTestData, $scope.displayLimit); |
| 210 } | 202 } |
| 211 $scope.imageSize = $scope.imageSizePending; | 203 $scope.imageSize = $scope.imageSizePending; |
| 212 $scope.areUpdatesPending = false; | 204 $scope.setUpdatesPending(false); |
| 213 } | 205 } |
| 214 | 206 |
| 207 /** | |
| 208 * Re-sort the displayed results. | |
| 209 * | |
| 210 * @param sortColumn (string): name of the column to sort on | |
| 211 */ | |
| 215 $scope.sortResultsBy = function(sortColumn) { | 212 $scope.sortResultsBy = function(sortColumn) { |
| 216 $scope.sortColumn = sortColumn; | 213 $scope.sortColumn = sortColumn; |
| 217 $scope.updateResults(); | 214 $scope.updateResults(); |
| 218 } | 215 } |
| 219 | 216 |
| 217 | |
| 218 // | |
| 219 // Operations for sending info back to the server. | |
| 220 // | |
| 221 | |
| 220 /** | 222 /** |
| 221 * Tell the server that the actual results of these particular tests | 223 * Tell the server that the actual results of these particular tests |
| 222 * are acceptable. | 224 * are acceptable. |
| 223 * | 225 * |
| 224 * @param testDataSubset an array of test results, most likely a subset of | 226 * @param testDataSubset an array of test results, most likely a subset of |
| 225 * $scope.testData (perhaps with some modifications) | 227 * $scope.testData (perhaps with some modifications) |
| 226 */ | 228 */ |
| 227 $scope.submitApprovals = function(testDataSubset) { | 229 $scope.submitApprovals = function(testDataSubset) { |
| 228 $scope.submitPending = true; | 230 $scope.submitPending = true; |
| 229 var newResults = []; | 231 var newResults = []; |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 259 "the server side to the Skia repo.\n\n" + | 261 "the server side to the Skia repo.\n\n" + |
| 260 "Also: in order to see the complete updated data, or to submit " + | 262 "Also: in order to see the complete updated data, or to submit " + |
| 261 "more baselines, you will need to reload your client."); | 263 "more baselines, you will need to reload your client."); |
| 262 $scope.submitPending = false; | 264 $scope.submitPending = false; |
| 263 }).error(function(data, status, headers, config) { | 265 }).error(function(data, status, headers, config) { |
| 264 alert("There was an error submitting your baselines.\n\n" + | 266 alert("There was an error submitting your baselines.\n\n" + |
| 265 "Please see server-side log for details."); | 267 "Please see server-side log for details."); |
| 266 $scope.submitPending = false; | 268 $scope.submitPending = false; |
| 267 }); | 269 }); |
| 268 } | 270 } |
| 271 | |
| 272 | |
| 273 // | |
| 274 // Operations we use to mimic Set semantics, in such a way that | |
| 275 // checking for presence within the Set is as fast as possible. | |
| 276 // But getting a list of all values within the Set is not necessarily | |
| 277 // possible. | |
| 278 // TODO(epoger): move into a separate .js file? | |
| 279 // | |
| 280 | |
| 281 /** | |
| 282 * Returns true if value "value" is present within set "set". | |
| 283 * | |
| 284 * @param value a value of any type | |
| 285 * @param set an Object which we use to mimic set semantics | |
| 286 * (this should make isValueInSet faster than if we used an Array) | |
| 287 */ | |
| 288 $scope.isValueInSet = function(value, set) { | |
| 289 return (true == set[value]); | |
| 290 } | |
| 291 | |
| 292 /** | |
| 293 * If value "value" is already in set "set", remove it; otherwise, add it. | |
| 294 * | |
| 295 * @param value a value of any type | |
| 296 * @param set an Object which we use to mimic set semantics | |
| 297 */ | |
| 298 $scope.toggleValueInSet = function(value, set) { | |
| 299 if (true == set[value]) { | |
| 300 delete set[value]; | |
| 301 } else { | |
| 302 set[value] = true; | |
| 303 } | |
| 304 } | |
| 305 | |
| 306 | |
| 307 // | |
| 308 // Array operations; similar to our Set operations, but operate on a | |
| 309 // Javascript Array so we *can* easily get a list of all values in the Set. | |
| 310 // TODO(epoger): move into a separate .js file? | |
| 311 // | |
| 312 | |
| 313 /** | |
| 314 * Returns true if value "value" is present within array "array". | |
| 315 * | |
| 316 * @param value a value of any type | |
| 317 * @param array a Javascript Array | |
| 318 */ | |
| 319 $scope.isValueInArray = function(value, array) { | |
| 320 return (-1 != array.indexOf(value)); | |
| 321 } | |
| 322 | |
| 323 /** | |
| 324 * If value "value" is already in array "array", remove it; otherwise, | |
| 325 * add it. | |
| 326 * | |
| 327 * @param value a value of any type | |
| 328 * @param array a Javascript Array | |
| 329 */ | |
| 330 $scope.toggleValueInArray = function(value, array) { | |
| 331 var i = array.indexOf(value); | |
| 332 if (-1 == i) { | |
| 333 array.push(value); | |
| 334 } else { | |
| 335 array.splice(i, 1); | |
| 336 } | |
| 337 } | |
| 338 | |
| 339 | |
| 340 // | |
| 341 // Miscellaneous utility functions. | |
| 342 // TODO(epoger): move into a separate .js file? | |
| 343 // | |
| 344 | |
| 345 /** | |
| 346 * Returns a human-readable (in local time zone) time string for a | |
| 347 * particular moment in time. | |
| 348 * | |
| 349 * @param secondsPastEpoch (numeric): seconds past epoch in UTC | |
| 350 */ | |
| 351 $scope.localTimeString = function(secondsPastEpoch) { | |
| 352 var d = new Date(secondsPastEpoch * 1000); | |
| 353 return d.toString(); | |
| 354 } | |
| 355 | |
| 269 } | 356 } |
| 270 ); | 357 ); |
| OLD | NEW |