blob: 09f2b6cc4feea8bf16524c66e29a37cad5cad78a [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Determining Issues' statuses relative to SLO rules.
*
* See go/monorail-slo-v0 for more info.
*/
/**
* A rule determining the compliance of an issue with regard to an SLO.
* @typedef {Object} SloRule
* @property {function(Issue): SloStatus} statusFunction
*/
/**
* Potential statuses of an issue relative to an SLO's completion criteria.
* @enum {string}
*/
export const SloCompletionStatus = {
/** The completion criteria for the SloRule have not been satisfied. */
INCOMPLETE: 'INCOMPLETE',
/** The completion criteria for the SloRule have been satisfied. */
COMPLETE: 'COMPLETE',
};
/**
* The status of an issue with regard to an SloRule.
* @typedef {Object} SloStatus
* @property {SloRule} rule The rule that generated this status.
* @property {Date} target The time the Issue must move to completion, or null
* if the issue has already moved to completion.
* @property {SloCompletionStatus} completion Issue's completion status.
*/
/**
* Chrome OS Software's SLO for issue closure (go/chromeos-software-bug-slos).
*
* Implementation based on the queries defined in Sheriffbot
* https://chrome-internal.googlesource.com/infra/infra_internal/+/refs/heads/main/appengine/sheriffbot/src/sheriffbot/bug_slo_daily_queries.py
*
* @const {SloRule}
* @private Only visible for testing.
*/
export const _CROS_CLOSURE_SLO = {
statusFunction: (issue) => {
if (!_isCrosClosureEligible(issue)) {
return null;
}
const pri = getPriFromIssue(issue);
const daysToClose = _CROS_CLOSURE_SLO_DAYS_BY_PRIORITY[pri];
if (!daysToClose) {
// No applicable SLO found issues with this priority.
return null;
}
// Return a complete status for closed issues.
if (issue.statusRef && !issue.statusRef.meansOpen) {
return {
rule: _CROS_CLOSURE_SLO,
target: null,
completion: SloCompletionStatus.COMPLETE};
}
// Set the target based on the opening and the daysToClose.
const target = new Date(issue.openedTimestamp * 1000);
target.setDate(target.getDate() + daysToClose);
return {
rule: _CROS_CLOSURE_SLO,
target: target,
completion: SloCompletionStatus.INCOMPLETE};
},
};
/**
* @param {Issue} issue
* @return {string?} the pri's value, if found.
*/
const getPriFromIssue = (issue) => {
for (const fv of issue.fieldValues) {
if (fv.fieldRef.fieldName === 'Pri') {
return fv.value;
}
}
};
/**
* The number of days (since the issue was opened) allowed for it to be fixed.
* @private Only visible for testing.
*/
export const _CROS_CLOSURE_SLO_DAYS_BY_PRIORITY = Object.freeze({
'1': 42,
});
// https://chrome-internal.googlesource.com/infra/infra_internal/+/refs/heads/main/appengine/sheriffbot/src/sheriffbot/bug_slo_daily_queries.py#97
const CROS_ELIGIBLE_COMPONENT_PATHS = new Set([
'OS>Systems>CrashReporting',
'OS>Systems>Displays',
'OS>Systems>Feedback',
'OS>Systems>HaTS',
'OS>Systems>Input',
'OS>Systems>Input>Keyboard',
'OS>Systems>Input>Mouse',
'OS>Systems>Input>Shortcuts',
'OS>Systems>Input>Touch',
'OS>Systems>Metrics',
'OS>Systems>Multidevice',
'OS>Systems>Multidevice>Messages',
'OS>Systems>Multidevice>SmartLock',
'OS>Systems>Multidevice>Tethering',
'OS>Systems>Network>Bluetooth',
'OS>Systems>Network>Cellular',
'OS>Systems>Network>VPN',
'OS>Systems>Network>WiFi',
'OS>Systems>Printing',
'OS>Systems>Settings',
'OS>Systems>Spellcheck',
'OS>Systems>Update',
'OS>Systems>Wallpaper',
'OS>Systems>WirelessCharging',
'Platform>Apps>Feedback',
'UI>Shell>Networking',
]);
/**
* Determines if an issue is eligible for _CROS_CLOSURE_SLO.
* @param {Issue} issue
* @return {boolean}
* @private Only visible for testing.
*/
export const _isCrosClosureEligible = (issue) => {
// If at least one component applies, continue.
const hasEligibleComponent = issue.componentRefs.some(
(component) => CROS_ELIGIBLE_COMPONENT_PATHS.has(component.path));
if (!hasEligibleComponent) {
return false;
}
let priority = null;
let hasMilestone = false;
for (const fv of issue.fieldValues) {
if (fv.fieldRef.fieldName === 'Type') {
// These types don't apply.
if (fv.value === 'Feature' || fv.value === 'FLT-Launch' ||
fv.value === 'Postmortem-Followup' || fv.value === 'Design-Review') {
return false;
}
}
if (fv.fieldRef.fieldName === 'Pri') {
priority = fv.value;
}
if (fv.fieldRef.fieldName === 'M') {
hasMilestone = true;
}
}
// P1 issues with milestones don't apply.
if (priority === '1' && hasMilestone) {
return false;
}
// Issues with the ChromeOS_No_SLO label don't apply.
for (const labelRef of issue.labelRefs) {
if (labelRef.label === 'ChromeOS_No_SLO') {
return false;
}
}
return true;
};
/**
* Active SLO Rules.
* @const {Array<SloRule>}
*/
const SLO_RULES = [_CROS_CLOSURE_SLO];
/**
* Determines the SloStatus for the given issue.
* @param {Issue} issue The issue to check.
* @return {SloStatus} The status of the issue, or null if no rules apply.
*/
export const determineSloStatus = (issue) => {
try {
for (const rule of SLO_RULES) {
const status = rule.statusFunction(issue);
if (status) {
return status;
}
}
} catch (error) {
// Don't bubble up any errors in SLO_RULES functions, which might sometimes
// be written/updated by client teams.
}
return null;
};