go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/frontend/static/common/js/build.js (about) 1 // Copyright 2018 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 16 // Code used for managing how nested steps are rendered in build view. In 17 // particular, it implement collapsing/nesting functionality and various modes 18 // for the Show option. 19 20 $(document).ready(function() { 21 'use strict'; 22 23 $('li.substeps').each(function() { 24 const substep = $(this); 25 $(this).find('>div.result').click(function() { 26 substep.toggleClass('collapsed'); 27 }); 28 }); 29 30 function setCookie(name, value) { 31 const farFuture = new Date(2100, 0).toUTCString(); 32 document.cookie = `${name}=${value}; expires=${farFuture}; path=/`; 33 } 34 35 function displayPrefChanged(selectedOption) { 36 if (!['expanded', 'default', 'non-green'].includes(selectedOption)) { 37 selectedOption = 'default'; 38 } 39 40 $('li.substeps').removeClass('collapsed'); 41 $('#steps').removeClass('non-green'); 42 43 switch (selectedOption) { 44 case 'expanded': 45 break; 46 case 'default': 47 $('li.substeps.green').addClass('collapsed'); 48 break; 49 case 'non-green': 50 $('#steps').addClass('non-green'); 51 break; 52 } 53 54 setCookie('stepDisplayPref', selectedOption); 55 } 56 57 const newBuildPageLink = $('#new-build-page-link'); 58 newBuildPageLink.on('click', (e) => { 59 if (e.metaKey || e.shiftKey || e.ctrlKey || e.altKey) { 60 return true; 61 } 62 63 setCookie('showNewBuildPage', true); 64 65 if ('serviceWorker' in navigator) { 66 navigator.serviceWorker.register('/root_sw.js') 67 .then((registration) => { 68 console.log('Root SW registered: ', registration); 69 window.open(newBuildPageLink.attr('href'), newBuildPageLink.attr('target') || '_self'); 70 }); 71 return false; 72 } 73 return true; 74 }); 75 76 displayPrefChanged($('input[name="hider"]:checked').val()); 77 $('input[name="hider"]').change(function() {displayPrefChanged($(this).val())}); 78 79 $('#showDebugLogs').change(function(e) { 80 setCookie('showDebugLogsPref', this.checked) 81 $('#steps').toggleClass('hide-debug-logs', !this.checked); 82 }); 83 84 function createTimeline() { 85 function groupTemplater(group, element, data) { 86 return ` 87 <div class="group-title ${group.data.statusClassName}"> 88 <span class="title">${group.data.label}</span> 89 <span class="duration">( ${group.data.duration} )</span> 90 </div>`; 91 } 92 93 const options = { 94 clickToUse: false, 95 groupTemplate: groupTemplater, 96 multiselect: false, 97 onInitialDrawComplete: () => { 98 $('#timeline-rendering').remove(); 99 }, 100 orientation: { 101 axis: 'both', 102 item: 'top', 103 }, 104 template: item => item.data.label, 105 zoomable: false, 106 }; 107 108 const timeline = new vis.Timeline( 109 document.getElementById('timeline'), 110 new vis.DataSet(timelineData.items), 111 new vis.DataSet(timelineData.groups), 112 options); 113 timeline.on('select', function(props) { 114 const item = timeline.itemsData.get(props.items[0]); 115 if (!item) { 116 return; 117 } 118 if (item.data && item.data.logUrl) { 119 window.open(item.data.logUrl, '_blank'); 120 } 121 }); 122 123 return timeline; 124 } 125 126 let timeline = null; 127 let relatedBuilds = null; 128 129 // By hiding the tab div until the tabs are constructed a flicker 130 // of the tab contents not within the tabs is avoided. 131 $('#tabs').tabs({ 132 activate: function(event, ui) { 133 switch (ui.newPanel.attr('id')) { 134 // By lazily creating the timeline only when its tab is first activated an 135 // ugly multi-step rendering (and console warning of infinite redraw loop) 136 // of the timeline is avoided. This could also be avoided by making the 137 // timeline tab the initially visible tab and creating the timeline just 138 // after the tabs are initialized (that is, wouldn't have to use this 139 // activate event hack). 140 case 'timeline-tab': 141 if (timeline === null) { 142 timeline = createTimeline(); 143 } 144 break; 145 146 case 'blamelist-tab': 147 if (!blamelistLoaded) { 148 const params = (new URL(window.location)).searchParams; 149 params.set('blamelist', '1'); 150 document.location = `?${params.toString()}#blamelist-tab`; 151 } 152 break; 153 154 // Load related builds asynchronously so it doesn't block rendering the rest of the page. 155 case 'related-tab': 156 const tableContainerRef = $('#related-builds-table-container') 157 if (!relatedBuilds && tableContainerRef) { 158 relatedBuilds = fetch(`/internal_widgets/related_builds/${buildbucketId}`) 159 .then((res) => res.text()) 160 .then((tablehtml) => tableContainerRef.html(tablehtml)) 161 .catch((_) => tableContainerRef.html('<p style="color: red;">Something went wrong</p>')) 162 } 163 break; 164 } 165 }, 166 }).show(); 167 168 const closeModal = () => { 169 $('#modal-overlay') 170 .removeClass('show') 171 .removeClass('show-retry-build-modal') 172 .removeClass('show-cancel-build-modal'); 173 }; 174 175 $('#retry-build-button').click(e => { 176 closeModal(); 177 $('#modal-overlay') 178 .addClass('show') 179 .addClass('show-retry-build-modal'); 180 }); 181 $('#dismiss-retry-build-button').click(closeModal); 182 183 $('#cancel-build-button').click(e => { 184 closeModal(); 185 $('#modal-overlay') 186 .addClass('show') 187 .addClass('show-cancel-build-modal'); 188 }); 189 $('#dismiss-cancel-build-button').click(closeModal); 190 191 $('#modal-overlay').click(e => { 192 if ($(e.target).is('#modal-overlay')) { 193 closeModal(); 194 } 195 }); 196 197 if (!document.cookie.includes('askForFeedback=false')) { 198 $('#feedback-bar').toggleClass('show', true); 199 } 200 201 $('#dismiss-feedback-bar').click(e => { 202 $('#feedback-bar').toggleClass('show', false); 203 204 const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toUTCString(); 205 document.cookie = `askForFeedback=false; expires=${expires}; path=/`; 206 e.preventDefault(); 207 }); 208 209 $('#feedback-link').click(e => { 210 const feedbackComment = encodeURIComponent( 211 `From Link: ${document.location.href} 212 Please enter a description of the problem, with repro steps if applicable. 213 `); 214 const url = `https://issuetracker.google.com/issues/new?component=1456503&type=BUG&priority=P2&severity=S2&inProd=true&description=${feedbackComment}`; 215 window.open(url); 216 e.preventDefault(); 217 }); 218 219 // Keep the new build page version up-to-date even when the user is using the 220 // legacy build page. 221 // Otherwise the new build page version will not take effect until the user 222 // loads the new build page for the 2nd time due to service worker activation 223 // rules. 224 navigator.serviceWorker?.register('/ui/ui_sw.js') 225 .then((registration) => registration.update()) 226 .then((registration) => { 227 console.log('New build page SW registered: ', registration); 228 }); 229 });