| // 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; |
| }; |