Changeset 91241 in webkit
- Timestamp:
- Jul 19, 2011 12:38:05 AM (13 years ago)
- Location:
- trunk/Tools
- Files:
-
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Tools/ChangeLog
r91240 r91241 1 2011-07-19 Adam Barth <abarth@webkit.org> 2 3 garden-o-matic should have a "Triage Failures" button for iterating over failures 4 https://bugs.webkit.org/show_bug.cgi?id=64769 5 6 Reviewed by Eric Seidel. 7 8 This patch adds some global static state and refactors things a bit so 9 that we can iterate over all the failures in the details pane. 10 11 * Scripts/webkitpy/tool/servers/data/gardeningserver/base.js: 12 - Add a generic callback iterator to iterate through a series of 13 callbacks. We use this to iterate through the failures we want to 14 display the details of. 15 * Scripts/webkitpy/tool/servers/data/gardeningserver/index.html: 16 - Add a toolbar for the results summary and change the buttons to 17 be real buttons. 18 * Scripts/webkitpy/tool/servers/data/gardeningserver/main.css: 19 - Make the detail pane 75% of the window. 20 - Make the buttons pretty. 21 - Allow the failure type badges to apply to the details pane as well. 22 * Scripts/webkitpy/tool/servers/data/gardeningserver/main.js: 23 - Restructure how we bring up the details pane so we can iterate 24 through a bunch of failures. 25 - Remove the transition between results details because it's 26 annoying when you want to click through a bunch of failures. 27 * Scripts/webkitpy/tool/servers/data/gardeningserver/ui.js: 28 - Add some more structure to the title bar for the results details 29 so it's clearer which results we're currently displaying. 30 1 31 2011-07-19 Adam Barth <abarth@webkit.org> 2 32 -
trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/base.js
r91240 r91241 100 100 base.RequestTracker = function(requestsInFlight, callback, args) 101 101 { 102 this. m_requestsInFlight = requestsInFlight;103 this. m_callback = callback;104 this. m_args = args || [];102 this._requestsInFlight = requestsInFlight; 103 this._callback = callback; 104 this._args = args || []; 105 105 }; 106 106 107 107 base.RequestTracker.prototype.requestComplete = function() 108 108 { 109 --this.m_requestsInFlight; 110 if (!this.m_requestsInFlight) 111 this.m_callback.apply(null, this.m_args); 109 --this._requestsInFlight; 110 if (!this._requestsInFlight) 111 this._callback.apply(null, this._args); 112 }; 113 114 base.CallbackIterator = function(callback, listOfArgumentArrays) 115 { 116 this._callback = callback; 117 this._nextIndex = 0; 118 this._listOfArgumentArrays = listOfArgumentArrays; 119 }; 120 121 base.CallbackIterator.prototype.hasNext = function() 122 { 123 return this._nextIndex < this._listOfArgumentArrays.length; 124 }; 125 126 base.CallbackIterator.prototype.hasPrevious = function() 127 { 128 return this._nextIndex - 2 >= 0; 129 }; 130 131 base.CallbackIterator.prototype.callNext = function() 132 { 133 if (!this.hasNext()) 134 return; 135 var args = this._listOfArgumentArrays[this._nextIndex]; 136 this._nextIndex++; 137 this._callback.apply(null, args); 138 }; 139 140 base.CallbackIterator.prototype.callPrevious = function() 141 { 142 if (!this.hasPrevious()) 143 return; 144 var args = this._listOfArgumentArrays[this._nextIndex - 2]; 145 this._nextIndex--; 146 this._callback.apply(null, args); 112 147 }; 113 148 -
trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/base_unittests.js
r91240 r91241 73 73 }); 74 74 75 test("CallbackIterator", 22, function() { 76 var expected = 0; 77 var iterator = new base.CallbackIterator(function(a, b) { 78 equals(a, 'ArgA' + expected); 79 equals(b, 'ArgB' + expected); 80 ++expected; 81 }, [ 82 ['ArgA0', 'ArgB0'], 83 ['ArgA1', 'ArgB1'], 84 ['ArgA2', 'ArgB2'], 85 ]); 86 ok(iterator.hasNext()) 87 ok(!iterator.hasPrevious()) 88 iterator.callNext(); 89 ok(iterator.hasNext()) 90 ok(!iterator.hasPrevious()) 91 iterator.callNext(); 92 ok(iterator.hasNext()) 93 ok(iterator.hasPrevious()) 94 iterator.callNext(); 95 ok(!iterator.hasNext()) 96 ok(iterator.hasPrevious()) 97 expected = 1; 98 iterator.callPrevious(); 99 ok(iterator.hasNext()) 100 ok(iterator.hasPrevious()) 101 expected = 0; 102 iterator.callPrevious(); 103 ok(iterator.hasNext()) 104 ok(!iterator.hasPrevious()) 105 }); 106 75 107 test("filterTree", 2, function() { 76 108 var tree = { -
trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/index.html
r91196 r91241 13 13 <div class="butterbar"><span class="status">Loading...</span></div> 14 14 <div class="alert"><span class="status"></span></div> 15 <div class="results"></div> 15 <div class="results"> 16 <div class="content"></div> 17 <div class="toolbar"> 18 <div class="actions"> 19 <button class="triage">Triage Failures</button> 20 </div> 21 </div> 22 </div> 16 23 <div class="results-detail"> 17 24 <div class="toolbar"> 18 25 <div class="actions"> 19 <a class="rebaseline" href="#">Rebaseline</a> 20 <a class="dismiss" href="#">Close</a> 26 <button class="rebaseline default">Rebaseline</button><button class="previous">◀</button><button class="next">▶</button><button class="dismiss">Close</button> 21 27 </div> 22 28 <div class="status"></div> 29 <div class="clear"></div> 23 30 </div> 24 31 <div class="content"></div> -
trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/main.css
r91240 r91241 8 8 a { 9 9 color: Black; 10 font-weight: bold;11 10 } 12 11 … … 20 19 overflow: hidden; 21 20 vertical-align: top; 22 border-right: 1px solid # CCC;21 border-right: 1px solid #c6c6c6; 23 22 } 24 23 … … 28 27 29 28 th { 30 border-bottom: 1px solid #CCC; 29 border-bottom: 1px solid #c6c6c6; 30 } 31 32 button { 33 border-radius: 2px; 34 background-image: -webkit-linear-gradient(top,#f5f5f5,#f1f1f1); 35 border: 1px solid rgba(0, 0, 0, 0.1); 36 border-radius: 2px; 37 color: #666; 38 cursor: pointer; 39 font-size: 11px; 40 font-weight: bold; 41 height: 29px; 42 line-height: 27px; 43 margin: 11px 6px; 44 min-width: 54px; 45 padding: 0 8px; 46 text-align: center 47 } 48 49 button.next { 50 margin-left: 0px; 51 } 52 53 button.previous { 54 margin-right: 0px; 55 } 56 57 button[disabled] { 58 visibility: hidden; 59 } 60 61 button:hover { 62 background-image: -webkit-linear-gradient(top,#f8f8f8,#f1f1f1); 63 -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1); 64 background-color: #f8f8f8; 65 background-image: linear-gradient(top,#f8f8f8,#f1f1f1); 66 border: 1px solid #c6c6c6; 67 box-shadow: 0 1px 1px rgba(0,0,0,0.1); 68 color: #333 69 } 70 71 button:focus { 72 border: 1px solid #4d90fe; 73 outline: none 74 } 75 76 button.default { 77 border: 1px solid #3079ED; 78 color: white; 79 background-image: -webkit-linear-gradient(top,#4d90fe,#4787ed); 80 } 81 82 button.default:hover { 83 border: 1px solid #2f5bb7; 84 color: white; 85 background-color: #357ae8; 86 background-image: -webkit-gradient(linear,left top,left bottom,from(#4d90fe),to(#357ae8)); 87 } 88 89 .clear { 90 clear: both; 31 91 } 32 92 … … 34 94 35 95 .butterbar { 36 background-color: #f 3f3f3;96 background-color: #f5f5f5; 37 97 padding: 5px; 38 98 position: fixed; … … 77 137 /*** results-summary ***/ 78 138 139 .results .toolbar { 140 text-align: center; 141 display: none; 142 } 143 79 144 table.results-summary { 80 145 width: auto; 81 146 table-layout: auto; 82 147 margin: 5px auto; 83 border: 1px solid # CCC;148 border: 1px solid #c6c6c6; 84 149 } 85 150 … … 114 179 } 115 180 116 . results-summary .what a[draggable].IMAGE\+TEXT::after {181 .test-name.IMAGE\+TEXT::after { 117 182 content: 'IMAGE+TEXT'; 183 font-weight: normal; 118 184 color: white; 119 185 background-color: Indigo; … … 124 190 } 125 191 126 . results-summary .what a[draggable].IMAGE::after {192 .test-name.IMAGE::after { 127 193 content: 'IMAGE'; 194 font-weight: normal; 128 195 color: white; 129 196 background-color: MidnightBlue; … … 134 201 } 135 202 136 . results-summary .what a[draggable].TIMEOUT::after {203 .test-name.TIMEOUT::after { 137 204 content: 'TIMEOUT'; 205 font-weight: normal; 138 206 color: white; 139 207 background-color: DarkGoldenRod; … … 144 212 } 145 213 146 . results-summary .what a[draggable].TEXT::after {214 .test-name.TEXT::after { 147 215 content: 'TEXT'; 216 font-weight: normal; 148 217 color: white; 149 218 background-color: DodgerBlue; … … 154 223 } 155 224 156 . results-summary .what a[draggable].CRASH::after {225 .test-name.CRASH::after { 157 226 content: 'CRASH'; 227 font-weight: normal; 158 228 color: white; 159 229 background-color: Tomato; … … 175 245 display: none; 176 246 position: fixed; 177 background-color: #f 3f3f3;178 top: 50%;247 background-color: #f5f5f5; 248 top: 25%; 179 249 left: 0px; 180 250 right: 0px; … … 183 253 184 254 .results-detail .toolbar { 185 border-top: 1px solid # CCC;186 border-bottom: 1px solid # CCC;255 border-top: 1px solid #c6c6c6; 256 border-bottom: 1px solid #c6c6c6; 187 257 } 188 258 189 259 .results-detail .toolbar .status { 190 display: inline-block; 191 padding: 5px; 260 font-size: 11px; 261 font-weight: bold; 262 height: 29px; 263 line-height: 27px; 264 margin: 11px 6px; 265 padding: 0 8px; 266 } 267 268 .results-detail .toolbar .status .builder-name { 269 font-weight: normal; 270 padding-left: 20px; 271 } 272 273 .results-detail .toolbar .status .selected { 274 font-style: italic; 192 275 } 193 276 194 277 .results-detail .toolbar .actions { 195 278 float: right; 196 }197 198 .results-detail .toolbar .actions a {199 display: inline-block;200 padding: 5px 5px 5px 0px;201 }202 203 .results-detail .toolbar {204 position: relative;205 279 } 206 280 -
trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/main.js
r91240 r91241 2 2 3 3 var g_updateTimerId = 0; 4 var g_resultsDetailsIterator = null; 4 5 5 6 var kBuildFailedAlertType = 'build-failed'; … … 13 14 { 14 15 $('.butterbar .status').text(message); 15 $('.butterbar').fadeIn( );16 $('.butterbar').fadeIn('fast'); 16 17 } 17 18 … … 48 49 } 49 50 51 function toggleButton(button, isEnabled) 52 { 53 if (isEnabled) 54 button.removeAttr('disabled'); 55 else 56 button.attr('disabled', true) 57 } 58 50 59 function togglePartyTime(hasFailures) 51 60 { 52 61 if (!hasFailures) { 53 $('.results ').text('No failures. Party time!');62 $('.results .content').text('No failures. Party time!'); 54 63 var partyTime = $('<div class="partytime"><img src="partytime.gif"></div>'); 55 $('.results ').append(partyTime);64 $('.results .content').append(partyTime); 56 65 partyTime.fadeIn(1200).delay(7000).fadeOut(); 57 66 return; 58 67 } 59 $('.results ').empty();68 $('.results .content').empty(); 60 69 } 61 70 … … 66 75 return container; 67 76 container = ui.regressionsContainer(); 68 $('.results ').append(container);77 $('.results .content').append(container); 69 78 return container; 70 79 } … … 118 127 var requestTracker = new base.RequestTracker(base.keys(unexpectedFailures).length, function() { 119 128 newTestSummaries.fadeIn(); 129 $('.results .toolbar').fadeIn(); 120 130 callback() 121 131 }); … … 137 147 } 138 148 139 function showResultsDetail() 140 { 141 var testSummary = $(this).parents('.test'); 142 var builderName = $(this).attr(config.kBuilderNameAttr); 149 function showResultsDetail(testName, selectedBuilderName, failureTypeListByBuilder) 150 { 151 var builderNameList = base.keys(failureTypeListByBuilder); 152 var failureTypeList = failureTypeListByBuilder[selectedBuilderName]; 153 var failureTypes = failureTypeList.join(' '); 154 155 var content = $('.results-detail .content'); 156 157 if ($('.failure-details', content).attr(config.kBuilderNameAttr) == selectedBuilderName && 158 $('.failure-details', content).attr(config.kTestNameAttr) == testName && 159 $('.failure-details', content).attr(config.kFailureTypesAttr) == failureTypes) 160 return; 161 162 displayOnButterbar('Loading...'); 163 164 results.fetchResultsURLs(selectedBuilderName, testName, failureTypeList, function(resultsURLs) { 165 var status = $('.results-detail .toolbar .status'); 166 167 status.empty(); 168 content.empty(); 169 170 status.append(ui.failureDetailsStatus(testName, selectedBuilderName, failureTypes, builderNameList)); 171 content.append(ui.failureDetails(resultsURLs)); 172 173 toggleButton($('.results-detail .actions .next'), g_resultsDetailsIterator.hasNext()); 174 toggleButton($('.results-detail .actions .previous'), g_resultsDetailsIterator.hasPrevious()); 175 toggleButton($('.results-detail .actions .rebaseline'), results.canRebaseline(failureTypeList)); 176 177 $('.failure-details', content).attr(config.kBuilderNameAttr, selectedBuilderName); 178 $('.failure-details', content).attr(config.kTestNameAttr, testName); 179 $('.failure-details', content).attr(config.kFailureTypesAttr, failureTypes); 180 181 if (!$('.results-detail').is(":visible")) 182 $('.results-detail').fadeIn('fast', dismissButterbar); 183 else 184 dismissButterbar(); 185 }); 186 } 187 188 function hideResultsDetail() 189 { 190 $('.results-detail').fadeOut('fast', function() { 191 $('.results-detail .status').empty(); 192 $('.results-detail .content').empty(); 193 // Strictly speaking, we don't need to clear g_resultsDetailsIterator, 194 // but doing so helps the garbage collector free memory. 195 g_resultsDetailsIterator = null; 196 }); 197 } 198 199 function nextResultsDetail() 200 { 201 g_resultsDetailsIterator.callNext(); 202 } 203 204 function previousResultsDetail() 205 { 206 g_resultsDetailsIterator.callPrevious(); 207 } 208 209 function resultsDetailArgumentsForBuilderElement(builderBlock) 210 { 211 var selectedBuilderName = builderBlock.attr(config.kBuilderNameAttr); 212 213 var testSummary = builderBlock.parents('.test'); 143 214 var testName = testSummary.attr(config.kTestNameAttr); 144 215 … … 147 218 var failureTypeList = failureTypes.split(' '); 148 219 149 var content = $('.results-detail .content'); 150 if ($('.failure-details', content).attr(config.kBuilderNameAttr) == builderName && 151 $('.failure-details', content).attr(config.kTestNameAttr) == testName && 152 $('.failure-details', content).attr(config.kFailureTypesAttr) == failureTypes) 153 return; 154 155 displayOnButterbar('Loading...'); 156 157 results.fetchResultsURLs(builderName, testName, failureTypeList, function(resultsURLs) { 158 var status = $('.results-detail .toolbar .status'); 159 160 function updateResults() 161 { 162 status.text(testName + ' [' + builderName + ']'); 163 content.empty(); 164 content.append(ui.failureDetails(resultsURLs)); 165 $('.results-detail .actions .rebaseline').toggle(results.canRebaseline(failureTypeList)); 166 $('.failure-details', content).attr(config.kBuilderNameAttr, builderName); 167 $('.failure-details', content).attr(config.kTestNameAttr, testName); 168 $('.failure-details', content).attr(config.kFailureTypesAttr, failureTypes); 169 } 170 171 var children = content.children(); 172 if (children.length && $('.results-detail').is(":visible")) { 173 // The results-detail pane is already open. Let's do a quick cross-fade. 174 status.fadeOut('fast'); 175 children.fadeOut('fast', function() { 176 updateResults(); 177 status.fadeIn('fast'); 178 content.children().hide().fadeIn('fast', dismissButterbar); 179 }); 180 } else { 181 updateResults(); 182 $('.results-detail').fadeIn('fast', dismissButterbar); 183 } 184 }); 185 } 186 187 function hideResultsDetail() 188 { 189 $('.results-detail').fadeOut('fast', function() { 190 $('.results-detail .content').empty(); 191 }); 220 var failureTypeListByBuilder = {} 221 $('.where .builder-name', testSummary).each(function() { 222 var builderName = $(this).attr(config.kBuilderNameAttr); 223 // FIXME: We should understand that tests can fail in different ways 224 // on different builders. 225 failureTypeListByBuilder[builderName] = failureTypeList; 226 }); 227 228 return [testName, selectedBuilderName, failureTypeListByBuilder]; 229 } 230 231 function prepareResultsDetailsIterator(query) 232 { 233 var listOfResultsDetailArguments = []; 234 235 query.each(function() { 236 var resultsDetailArguments = resultsDetailArgumentsForBuilderElement($(this)); 237 listOfResultsDetailArguments.push(resultsDetailArguments); 238 }); 239 240 return new base.CallbackIterator(showResultsDetail, listOfResultsDetailArguments); 241 } 242 243 function triageFailures() 244 { 245 g_resultsDetailsIterator = prepareResultsDetailsIterator($('.results .test .builder-name')); 246 g_resultsDetailsIterator.callNext(); 192 247 } 193 248 … … 223 278 } 224 279 225 $('.results-summary .where a').live('click', showResultsDetail); 280 $('.results .toolbar .triage').live('click', triageFailures); 281 $('.results-detail .actions .next').live('click', nextResultsDetail); 282 $('.results-detail .actions .previous').live('click', previousResultsDetail); 283 $('.results-detail .actions .rebaseline').live('click', rebaselineResults); 226 284 $('.results-detail .actions .dismiss').live('click', hideResultsDetail); 227 $('.results-detail .actions .rebaseline').live('click', rebaselineResults);228 285 229 286 $(document).ready(function() { -
trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/ui.js
r91240 r91241 49 49 var block = $( 50 50 '<tr class="test">' + 51 '<td class="what"><a draggable></a></td>' +51 '<td class="what"><a class="test-name"></a></td>' + 52 52 '<td class="where"><ul></ul></td>' + 53 53 '<td class="when"></td>' + 54 54 '<td class="how-many"></td>' + 55 55 '</tr>'); 56 $('. what a', block).text(testName).attr('href', ui.urlForTest(testName)).attr('class',unexpectedResults.join(' '));56 $('.test-name', block).text(testName).attr('href', ui.urlForTest(testName)).addClass(unexpectedResults.join(' ')); 57 57 block.attr(config.kTestNameAttr, testName); 58 58 block.attr(config.kFailureTypesAttr, unexpectedResults); … … 60 60 var where = $('.where', block); 61 61 $.each(resultNodesByBuilder, function(builderName, resultNode) { 62 var listElement = $('<li><a href="#"></a></li>'); 62 var listElement = $('<li class="builder-name"></li>'); 63 listElement.attr(config.kBuilderNameAttr, builderName).text(displayNameForBuilder(builderName)); 63 64 where.append(listElement); 64 $('a', listElement).attr(config.kBuilderNameAttr, builderName).text(displayNameForBuilder(builderName));65 65 }); 66 66 … … 80 80 displayNameForRevision(impliedFirstFailingRevision) + '-' + displayNameForRevision(oldestFailingRevision); 81 81 82 var block = $('<div class="regression-range"><a ></a></div>');82 var block = $('<div class="regression-range"><a target="_blank"></a></div>'); 83 83 $('a', block).attr('href', href).text(text) 84 84 return block; … … 108 108 }; 109 109 110 ui.failureDetailsStatus = function(testName, selectedBuilderName, failureTypes, builderNameList) 111 { 112 var block = $('<span><span class="test-name"></span><span class="builder-list"></span></span>'); 113 $('.test-name', block).addClass(failureTypes).text(testName); 114 115 var builderList = $('.builder-list', block); 116 $.each(builderNameList, function(index, builderName) { 117 var builder = $('<span class="builder-name"></span>') 118 builder.text(builderName); 119 if (builderName == selectedBuilderName) 120 builder.addClass('selected'); 121 builderList.append(builder); 122 }); 123 124 return block; 125 }; 126 110 127 ui.failureDetails = function(resultsURLs) 111 128 { -
trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/ui_unittests.js
r91197 r91241 38 38 var summaryWithMultipleRevisions = ui.summarizeRegressionRange(90424, 90426); 39 39 summaryWithMultipleRevisions.wrap('<wrapper></wrapper>'); 40 equal(summaryWithMultipleRevisions.parent().html(), '<div class="regression-range"><a href="http://trac.webkit.org/log/trunk/?rev=90424&stop_rev=90427&limit=100&verbose=on">r90427-r90424</a></div>');40 equal(summaryWithMultipleRevisions.parent().html(), '<div class="regression-range"><a target="_blank" href="http://trac.webkit.org/log/trunk/?rev=90424&stop_rev=90427&limit=100&verbose=on">r90427-r90424</a></div>'); 41 41 42 42 var summaryWithOneRevision = ui.summarizeRegressionRange(90425, 90426); 43 43 summaryWithOneRevision.wrap('<wrapper></wrapper>'); 44 equal(summaryWithOneRevision.parent().html(), '<div class="regression-range"><a href="http://trac.webkit.org/log/trunk/?rev=90425&stop_rev=90427&limit=100&verbose=on">r90427-r90425</a></div>');44 equal(summaryWithOneRevision.parent().html(), '<div class="regression-range"><a target="_blank" href="http://trac.webkit.org/log/trunk/?rev=90425&stop_rev=90427&limit=100&verbose=on">r90427-r90425</a></div>'); 45 45 }); 46 46 … … 60 60 '<li><a target="_blank" href="http://build.chromium.org/p/chromium.webkit/waterfall?builder=Another+Builder">Another Builder</a></li>' + 61 61 '</ul></div>'); 62 }); 63 64 test("failureDetailsStatus", 1, function() { 65 var status = ui.failureDetailsStatus('userscripts/another-test.html', 'Mock Builder', 'TEXT', ['Mock Builder', 'Another Builder']); 66 status.wrap('<wrapper></wrapper>'); 67 equal(status.parent().html(), 68 '<span>' + 69 '<span class="test-name TEXT">userscripts/another-test.html</span>' + 70 '<span class="builder-list">' + 71 '<span class="builder-name selected">Mock Builder</span>' + 72 '<span class="builder-name">Another Builder</span>' + 73 '</span>' + 74 '</span>'); 62 75 }); 63 76
Note: See TracChangeset
for help on using the changeset viewer.