mirror of
				https://github.com/ohwgiles/laminar.git
				synced 2025-06-13 12:54:29 +00:00 
			
		
		
		
	frontend: better dynamic update of graphs
react to jobCompletion messages by updating graphs in more cases so manual refresh is not necessary to see most up to date representation.
This commit is contained in:
		
							parent
							
								
									60f7ee5402
								
							
						
					
					
						commit
						a50514a135
					
				| @ -470,6 +470,12 @@ std::string Laminar::getStatus(MonitorScope scope) { | ||||
|             j.EndObject(); | ||||
|         }); | ||||
|         j.EndArray(); | ||||
|         j.startObject("completedCounts"); | ||||
|         db->stmt("SELECT name, COUNT(*) FROM builds WHERE result IS NOT NULL GROUP BY name") | ||||
|                 .fetch<str, uint>([&](str job, uint count){ | ||||
|             j.set(job.c_str(), count); | ||||
|         }); | ||||
|         j.EndObject(); | ||||
|     } | ||||
|     j.EndObject(); | ||||
|     return j.str(); | ||||
|  | ||||
| @ -203,15 +203,19 @@ const Charts = (() => { | ||||
|           if (c.data.labels[j] == name) { | ||||
|             c.data.datasets[0].data[j]++; | ||||
|             c.update(); | ||||
|             break; | ||||
|             return; | ||||
|           } | ||||
|         } | ||||
|         // if we get here, it's a new/unknown job
 | ||||
|         c.data.labels.push(name); | ||||
|         c.data.datasets[0].data.push(1); | ||||
|         c.update(); | ||||
|       } | ||||
|       return c; | ||||
|     }, | ||||
|     createTimePerJobChart: (id, data) => { | ||||
|     createTimePerJobChart: (id, data, completedCounts) => { | ||||
|       const scale = timeScale(Math.max(...Object.values(data))); | ||||
|       return new Chart(document.getElementById(id), { | ||||
|       const c = new Chart(document.getElementById(id), { | ||||
|         type: 'horizontalBar', | ||||
|         data: { | ||||
|           labels: Object.keys(data), | ||||
| @ -232,23 +236,38 @@ const Charts = (() => { | ||||
|             } | ||||
|           }]}, | ||||
|           tooltips:{callbacks:{ | ||||
|             label: (tip, data) => data.datasets[tip.datasetIndex].label + ': ' + tip.xLabel + ' ' + scale.label.toLowerCase() | ||||
|             label: (tip, data) => data.datasets[tip.datasetIndex].label + ': ' + tip.xLabel.toFixed(2) + ' ' + scale.label.toLowerCase() | ||||
|           }} | ||||
|         } | ||||
|       }); | ||||
|       c.jobCompleted = (name, time) => { | ||||
|         for (var j = 0; j < c.data.datasets[0].data.length; ++j) { | ||||
|           if (c.data.labels[j] == name) { | ||||
|             c.data.datasets[0].data[j] = ((completedCounts[name]-1) * c.data.datasets[0].data[j] + time * scale.factor) / completedCounts[name]; | ||||
|             c.update(); | ||||
|             return; | ||||
|           } | ||||
|         } | ||||
|         // if we get here, it's a new/unknown job
 | ||||
|         c.data.labels.push(name); | ||||
|         c.data.datasets[0].data.push(time * scale.factor); | ||||
|         c.update(); | ||||
|       }; | ||||
|       return c; | ||||
|     }, | ||||
|     createRunTimeChangesChart: (id, data) => { | ||||
|       const scale = timeScale(Math.max(...data.map(e => Math.max(...e.durations)))); | ||||
|       return new Chart(document.getElementById(id), { | ||||
|       const dataValue = (name, durations) => ({ | ||||
|         label: name, | ||||
|         data: durations.map(x => x * scale.factor), | ||||
|         borderColor: 'hsl('+(name.hashCode() % 360)+', 27%, 57%)', | ||||
|         backgroundColor: 'transparent' | ||||
|       }); | ||||
|       const c = new Chart(document.getElementById(id), { | ||||
|         type: 'line', | ||||
|         data: { | ||||
|           labels: [...Array(10).keys()], | ||||
|           datasets: data.map(e => ({ | ||||
|             label: e.name, | ||||
|             data: e.durations.map(x => x * scale.factor), | ||||
|             borderColor: 'hsl('+(e.name.hashCode() % 360)+', 27%, 57%)', | ||||
|             backgroundColor: 'transparent' | ||||
|           })) | ||||
|           datasets: data.map(e => dataValue(e.name, e.durations)) | ||||
|         }, | ||||
|         options:{ | ||||
|           title: { display: true, text: 'Run time changes' }, | ||||
| @ -268,10 +287,25 @@ const Charts = (() => { | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|       c.jobCompleted = (name, time) => { | ||||
|         for (var j = 0; j < c.data.datasets.length; ++j) { | ||||
|           if (c.data.datasets[j].label == name) { | ||||
|             if(c.data.datasets[j].data.length == 10) | ||||
|               c.data.datasets[j].data.shift(); | ||||
|             c.data.datasets[j].data.push(time * scale.factor); | ||||
|             c.update(); | ||||
|             return; | ||||
|           } | ||||
|         } | ||||
|         // if we get here, it's a new/unknown job
 | ||||
|         c.data.datasets.push(dataValue(name, [time])); | ||||
|         c.update(); | ||||
|       }; | ||||
|       return c; | ||||
|     }, | ||||
|     createRunTimeChart: (id, jobs, avg) => { | ||||
|       const scale = timeScale(Math.max(...jobs.map(v=>v.completed-v.started))); | ||||
|       return new Chart(document.getElementById(id), { | ||||
|       const c = new Chart(document.getElementById(id), { | ||||
|         type: 'bar', | ||||
|         data: { | ||||
|           labels: jobs.map(e => '#' + e.number).reverse(), | ||||
| @ -319,6 +353,22 @@ const Charts = (() => { | ||||
|           }} | ||||
|         } | ||||
|       }); | ||||
|       c.jobCompleted = (num, result, time) => { | ||||
|         let avg = c.data.datasets[0].data[0].y / scale.factor; | ||||
|         avg = ((avg * (num - 1)) + time) / num; | ||||
|         c.data.datasets[0].data[0].y = avg * scale.factor; | ||||
|         c.data.datasets[0].data[1].y = avg * scale.factor; | ||||
|         if(c.data.datasets[1].data.length == 20) { | ||||
|           c.data.labels.shift(); | ||||
|           c.data.datasets[1].data.shift(); | ||||
|           c.data.datasets[1].backgroundColor.shift(); | ||||
|         } | ||||
|         c.data.labels.push('#' + num); | ||||
|         c.data.datasets[1].data.push(time * scale.factor); | ||||
|         c.data.datasets[1].backgroundColor.push(result == 'success' ? '#74af77': '#883d3d'); | ||||
|         c.update(); | ||||
|       }; | ||||
|       return c; | ||||
|     } | ||||
|   }; | ||||
| })(); | ||||
| @ -341,6 +391,7 @@ const Home = templateId => { | ||||
|     lowPassRates: [], | ||||
|   }; | ||||
|   let chtUtilization, chtBuildsPerDay, chtBuildsPerJob, chtTimePerJob; | ||||
|   let completedCounts; | ||||
|   return { | ||||
|     template: templateId, | ||||
|     data: () => state, | ||||
| @ -351,6 +402,7 @@ const Home = templateId => { | ||||
|         state.jobsRecent = msg.recent; | ||||
|         state.resultChanged = msg.resultChanged; | ||||
|         state.lowPassRates = msg.lowPassRates; | ||||
|         completedCounts = msg.completedCounts; | ||||
|         this.$forceUpdate(); | ||||
| 
 | ||||
|         // defer charts to nextTick because they get DOM elements which aren't rendered yet
 | ||||
| @ -358,7 +410,7 @@ const Home = templateId => { | ||||
|           chtUtilization = Charts.createExecutorUtilizationChart("chartUtil", msg.executorsBusy, msg.executorsTotal); | ||||
|           chtBuildsPerDay = Charts.createRunsPerDayChart("chartBpd", msg.buildsPerDay); | ||||
|           chtBuildsPerJob = Charts.createRunsPerJobChart("chartBpj", msg.buildsPerJob); | ||||
|           chtTimePerJob = Charts.createTimePerJobChart("chartTpj", msg.timePerJob); | ||||
|           chtTimePerJob = Charts.createTimePerJobChart("chartTpj", msg.timePerJob, completedCounts); | ||||
|           chtBuildTimeChanges = Charts.createRunTimeChangesChart("chartBuildTimeChanges", msg.buildTimeChanges); | ||||
|         }); | ||||
|       }, | ||||
| @ -373,8 +425,10 @@ const Home = templateId => { | ||||
|         chtUtilization.executorBusyChanged(true); | ||||
|       }, | ||||
|       job_completed: function(data) { | ||||
|         for (var i = 0; i < state.jobsRunning.length; ++i) { | ||||
|           var job = state.jobsRunning[i]; | ||||
|         if(!(job.name in completedCounts)) | ||||
|           completedCounts[job.name] = 0; | ||||
|         for(let i = 0; i < state.jobsRunning.length; ++i) { | ||||
|           const job = state.jobsRunning[i]; | ||||
|           if (job.name == data.name && job.number == data.number) { | ||||
|             state.jobsRunning.splice(i, 1); | ||||
|             state.jobsRecent.splice(0, 0, data); | ||||
| @ -382,9 +436,28 @@ const Home = templateId => { | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
|         for(let i = 0; i < state.resultChanged.length; ++i) { | ||||
|           const job = state.resultChanged[i]; | ||||
|           if(job.name == data.name) { | ||||
|             job[data.result === 'success' ? 'lastSuccess' : 'lastFailure'] = data.number; | ||||
|             this.$forceUpdate(); | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
|         for(let i = 0; i < state.lowPassRates.length; ++i) { | ||||
|           const job = state.lowPassRates[i]; | ||||
|           if(job.name == data.name) { | ||||
|             job.passRate = ((completedCounts[job.name] - 1) * job.passRate + (data.result === 'success' ? 1 : 0)) / completedCounts[job.name]; | ||||
|             this.$forceUpdate(); | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
|         completedCounts[job.name]++; | ||||
|         chtBuildsPerDay.jobCompleted(data.result === 'success') | ||||
|         chtUtilization.executorBusyChanged(false); | ||||
|         chtBuildsPerJob.jobCompleted(data.name) | ||||
|         chtBuildsPerJob.jobCompleted(data.name); | ||||
|         chtTimePerJob.jobCompleted(data.name, data.completed - data.started); | ||||
|         chtBuildTimeChanges.jobCompleted(data.name, data.completed - data.started); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| @ -494,7 +567,7 @@ const Job = templateId => { | ||||
|     pages: 0, | ||||
|     sort: {} | ||||
|   }; | ||||
|   let chtBt = null; | ||||
|   let chtBuildTime = null; | ||||
|   return { | ||||
|     template: templateId, | ||||
|     props: ['route'], | ||||
| @ -512,12 +585,12 @@ const Job = templateId => { | ||||
| 
 | ||||
|         // "status" comes again if we change page/sorting. Delete the
 | ||||
|         // old chart and recreate it to prevent flickering of old data
 | ||||
|         if(chtBt) | ||||
|           chtBt.destroy(); | ||||
|         if(chtBuildTime) | ||||
|           chtBuildTime.destroy(); | ||||
| 
 | ||||
|         // defer chart to nextTick because they get DOM elements which aren't rendered yet
 | ||||
|         this.$nextTick(() => { | ||||
|           chtBt = Charts.createRunTimeChart("chartBt", msg.recent, msg.averageRuntime); | ||||
|           chtBuildTime = Charts.createRunTimeChart("chartBt", msg.recent, msg.averageRuntime); | ||||
|         }); | ||||
|       }, | ||||
|       job_queued: function() { | ||||
| @ -534,8 +607,8 @@ const Job = templateId => { | ||||
|             state.jobsRunning.splice(i, 1); | ||||
|             state.jobsRecent.splice(0, 0, data); | ||||
|             this.$forceUpdate(); | ||||
|             // TODO: update the chart
 | ||||
|         } | ||||
|         chtBuildTime.jobCompleted(data.number, data.result, data.completed - data.started); | ||||
|       }, | ||||
|       page_next: function() { | ||||
|         state.sort.page++; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user