From e3e2fcb5d4e81ffb96529e85b67bb6aa5a46a1ed Mon Sep 17 00:00:00 2001 From: Sebastian Hengst Date: Tue, 21 Jan 2025 21:09:06 +0100 Subject: [PATCH] Bug 1942262 - support setting job without group as intermittent (mitten icon) --- ui/job-view/pushes/JobGroup.jsx | 93 +-------------------- ui/job-view/pushes/JobsAndGroups.jsx | 116 +++++++++++++++++++++++++-- 2 files changed, 113 insertions(+), 96 deletions(-) diff --git a/ui/job-view/pushes/JobGroup.jsx b/ui/job-view/pushes/JobGroup.jsx index 5da08776ca6..823ac56d82e 100644 --- a/ui/job-view/pushes/JobGroup.jsx +++ b/ui/job-view/pushes/JobGroup.jsx @@ -60,92 +60,6 @@ export class JobGroupComponent extends React.Component { this.setState({ expanded: isExpanded }); } - getIntermittentJobTypeNames(groupJobs, confirmGroup) { - /* Given a set of jobs from a group and related confirmFailures: - * collect all failed jobs - * collect all confirm failure job names - * collect all confirm failure jobs that failed (i.e. confirmed) - * if job is retriggered, assuming >50% green == intermittent - * if job has confirm-failure: assuming green is intermittent, orange is failure - * if job has both cf and retrigger: - * confirm-failure is failed: mark as failed - * confirm-failure is green: add +1 to total success jobs - */ - const failedJobTypeNames = {}; - const jobCountByConfirmName = {}; - const jobCountByName = {}; - const confirmedJobNames = []; - - for (const job of groupJobs) { - if (!Object.keys(jobCountByName).includes(job.job_type_name)) { - jobCountByName[job.job_type_name] = 0; - } - jobCountByName[job.job_type_name]++; - - // -cf group can have >1 job of each job_type_name and >1 type of job - if ( - confirmGroup !== undefined && - Object.keys(confirmGroup).includes('jobs') - ) { - for (const sgjob of confirmGroup.jobs) { - if (sgjob.result === 'unknown') continue; - - const cfJobName = sgjob.job_type_name.split('-cf')[0]; - if (cfJobName === job.job_type_name) { - if (!Object.keys(jobCountByConfirmName).includes(cfJobName)) { - jobCountByConfirmName[cfJobName] = 0; - } - jobCountByConfirmName[cfJobName]++; - - // if we find a failing -cf job, then this is a regression! - // TODO: we could fail for infra - if (sgjob.result === 'testfailed') { - confirmedJobNames.push(cfJobName); - } - } - } - } - - if (job.result === 'testfailed') { - if (!Object.keys(failedJobTypeNames).includes(job.job_type_name)) { - failedJobTypeNames[job.job_type_name] = []; - } - // TODO: add list of failures here, specifically NEW failures - failedJobTypeNames[job.job_type_name].push(job.id); - } - } - - const intermittentJobTypeNames = new Set(); - const failedNames = Object.keys(failedJobTypeNames); - for (const job of groupJobs) { - // if failed in -cf mode, do not consider as intermittent - if (confirmedJobNames.includes(job.job_type_name)) continue; - - if (job.result === 'success' && failedNames.includes(job.job_type_name)) { - // TODO: make the default threshold lower, now 1/2 pass, ideally 2/3 pass - let threshold = 0.5; - - // if -cf job exists (only here if green), then job is confirmed intermittent - if (jobCountByConfirmName[job.job_type_name] > 0) threshold = 1; - - if ( - failedJobTypeNames[job.job_type_name].length / - jobCountByName[job.job_type_name] <= - threshold - ) { - intermittentJobTypeNames.add(job.job_type_name); - } - } else if ( - // here if we have at least 1 green -cf job, we can mark the failures as intermittent - jobCountByConfirmName[job.job_type_name] > 0 - ) { - intermittentJobTypeNames.add(job.job_type_name); - } - } - - return intermittentJobTypeNames; - } - toggleExpanded = () => { this.setState((prevState) => ({ expanded: !prevState.expanded })); }; @@ -227,15 +141,12 @@ export class JobGroupComponent extends React.Component { jobs: groupJobs, mapKey: groupMapKey, }, - confirmGroup, + intermittentJobTypeNames, runnableVisible, } = this.props; const { expanded } = this.state; const { buttons, counts } = this.groupButtonsAndCounts(groupJobs, expanded); - const intermittentJobTypeNames = this.getIntermittentJobTypeNames( - groupJobs, - confirmGroup, - ); + function isIntermittent(job) { if (job.result !== 'testfailed') { return false; diff --git a/ui/job-view/pushes/JobsAndGroups.jsx b/ui/job-view/pushes/JobsAndGroups.jsx index 3b1a4306bb0..86a2a2d10e3 100644 --- a/ui/job-view/pushes/JobsAndGroups.jsx +++ b/ui/job-view/pushes/JobsAndGroups.jsx @@ -5,6 +5,92 @@ import JobButton from './JobButton'; import JobGroup from './JobGroup'; export default class JobsAndGroups extends React.Component { + getIntermittentJobTypeNames(groupJobs, confirmGroup) { + /* Given a set of jobs from a group and related confirmFailures: + * collect all failed jobs + * collect all confirm failure job names + * collect all confirm failure jobs that failed (i.e. confirmed) + * if job is retriggered, assuming >50% green == intermittent + * if job has confirm-failure: assuming green is intermittent, orange is failure + * if job has both cf and retrigger: + * confirm-failure is failed: mark as failed + * confirm-failure is green: add +1 to total success jobs + */ + const failedJobTypeNames = {}; + const jobCountByConfirmName = {}; + const jobCountByName = {}; + const confirmedJobNames = []; + + for (const job of groupJobs) { + if (!Object.keys(jobCountByName).includes(job.job_type_name)) { + jobCountByName[job.job_type_name] = 0; + } + jobCountByName[job.job_type_name]++; + + // -cf group can have >1 job of each job_type_name and >1 type of job + if ( + confirmGroup !== undefined && + Object.keys(confirmGroup).includes('jobs') + ) { + for (const sgjob of confirmGroup.jobs) { + if (sgjob.result === 'unknown') continue; + + const cfJobName = sgjob.job_type_name.split('-cf')[0]; + if (cfJobName === job.job_type_name) { + if (!Object.keys(jobCountByConfirmName).includes(cfJobName)) { + jobCountByConfirmName[cfJobName] = 0; + } + jobCountByConfirmName[cfJobName]++; + + // if we find a failing -cf job, then this is a regression! + // TODO: we could fail for infra + if (sgjob.result === 'testfailed') { + confirmedJobNames.push(cfJobName); + } + } + } + } + + if (job.result === 'testfailed') { + if (!Object.keys(failedJobTypeNames).includes(job.job_type_name)) { + failedJobTypeNames[job.job_type_name] = []; + } + // TODO: add list of failures here, specifically NEW failures + failedJobTypeNames[job.job_type_name].push(job.id); + } + } + + const intermittentJobTypeNames = new Set(); + const failedNames = Object.keys(failedJobTypeNames); + for (const job of groupJobs) { + // if failed in -cf mode, do not consider as intermittent + if (confirmedJobNames.includes(job.job_type_name)) continue; + + if (job.result === 'success' && failedNames.includes(job.job_type_name)) { + // TODO: make the default threshold lower, now 1/2 pass, ideally 2/3 pass + let threshold = 0.5; + + // if -cf job exists (only here if green), then job is confirmed intermittent + if (jobCountByConfirmName[job.job_type_name] > 0) threshold = 1; + + if ( + failedJobTypeNames[job.job_type_name].length / + jobCountByName[job.job_type_name] <= + threshold + ) { + intermittentJobTypeNames.add(job.job_type_name); + } + } else if ( + // here if we have at least 1 green -cf job, we can mark the failures as intermittent + jobCountByConfirmName[job.job_type_name] > 0 + ) { + intermittentJobTypeNames.add(job.job_type_name); + } + } + + return intermittentJobTypeNames; + } + render() { const { groups, @@ -18,6 +104,16 @@ export default class JobsAndGroups extends React.Component { toggleSelectedRunnableJob, } = this.props; + const intermittentJobTypeNames = new Set(); + + function isIntermittent(job) { + if (job.result !== 'testfailed') { + return false; + } + + return intermittentJobTypeNames.has(job.job_type_name); + } + const confirmGroups = {}; for (const g of groups) { // group.mapKey == pushID groupSymbol Tier platform buildtype @@ -37,6 +133,19 @@ export default class JobsAndGroups extends React.Component { confirmGroups[gname] = g; } } + + const { jobs } = g; + const confirmGroup = + confirmGroups[jobs.mapKey] === undefined + ? {} + : confirmGroups[jobs.mapKey]; + const intermittentTypes = this.getIntermittentJobTypeNames( + jobs, + confirmGroup, + ); + intermittentTypes.forEach((intermittentType) => + intermittentJobTypeNames.add(intermittentType), + ); } return ( @@ -47,11 +156,6 @@ export default class JobsAndGroups extends React.Component { group.visible && ( ) @@ -74,6 +179,7 @@ export default class JobsAndGroups extends React.Component { resultStatus={job.resultStatus} failureClassificationId={job.failure_classification_id} filterPlatformCb={filterPlatformCb} + intermittent={isIntermittent(job)} key={job.id} /> ));