Skip to content

Commit

Permalink
Bug 1942262 - support setting job without group as intermittent (mitt…
Browse files Browse the repository at this point in the history
…en icon)
  • Loading branch information
Archaeopteryx committed Jan 22, 2025
1 parent 82671e0 commit e3e2fcb
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 96 deletions.
93 changes: 2 additions & 91 deletions ui/job-view/pushes/JobGroup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }));
};
Expand Down Expand Up @@ -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;
Expand Down
116 changes: 111 additions & 5 deletions ui/job-view/pushes/JobsAndGroups.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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 (
Expand All @@ -47,11 +156,6 @@ export default class JobsAndGroups extends React.Component {
group.visible && (
<JobGroup
group={group}
confirmGroup={
confirmGroups[group.mapKey] === undefined
? {}
: confirmGroups[group.mapKey]
}
repoName={repoName}
filterModel={filterModel}
filterPlatformCb={filterPlatformCb}
Expand All @@ -60,6 +164,7 @@ export default class JobsAndGroups extends React.Component {
duplicateJobsVisible={duplicateJobsVisible}
groupCountsExpanded={groupCountsExpanded}
runnableVisible={runnableVisible}
intermittentJobTypeNames={intermittentJobTypeNames}
toggleSelectedRunnableJob={toggleSelectedRunnableJob}
/>
)
Expand All @@ -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}
/>
));
Expand Down

0 comments on commit e3e2fcb

Please sign in to comment.