blob: e351ae09916c292abfe2e6a485e4cee4b585daa1 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001// Copyright 2020 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @fileoverview Determining Issues' statuses relative to SLO rules.
7 *
8 * See go/monorail-slo-v0 for more info.
9 */
10
11/**
12 * A rule determining the compliance of an issue with regard to an SLO.
13 * @typedef {Object} SloRule
14 * @property {function(Issue): SloStatus} statusFunction
15 */
16
17/**
18 * Potential statuses of an issue relative to an SLO's completion criteria.
19 * @enum {string}
20 */
21export const SloCompletionStatus = {
22 /** The completion criteria for the SloRule have not been satisfied. */
23 INCOMPLETE: 'INCOMPLETE',
24 /** The completion criteria for the SloRule have been satisfied. */
25 COMPLETE: 'COMPLETE',
26};
27
28/**
29 * The status of an issue with regard to an SloRule.
30 * @typedef {Object} SloStatus
31 * @property {SloRule} rule The rule that generated this status.
32 * @property {Date} target The time the Issue must move to completion, or null
33 * if the issue has already moved to completion.
34 * @property {SloCompletionStatus} completion Issue's completion status.
35 */
36
37/**
38 * Chrome OS Software's SLO for issue closure (go/chromeos-software-bug-slos).
39 *
40 * Implementation based on the queries defined in Sheriffbot
41 * https://chrome-internal.googlesource.com/infra/infra_internal/+/refs/heads/main/appengine/sheriffbot/src/sheriffbot/bug_slo_daily_queries.py
42 *
43 * @const {SloRule}
44 * @private Only visible for testing.
45 */
46export const _CROS_CLOSURE_SLO = {
47 statusFunction: (issue) => {
48 if (!_isCrosClosureEligible(issue)) {
49 return null;
50 }
51
52 const pri = getPriFromIssue(issue);
53 const daysToClose = _CROS_CLOSURE_SLO_DAYS_BY_PRIORITY[pri];
54
55 if (!daysToClose) {
56 // No applicable SLO found issues with this priority.
57 return null;
58 }
59 // Return a complete status for closed issues.
60 if (issue.statusRef && !issue.statusRef.meansOpen) {
61 return {
62 rule: _CROS_CLOSURE_SLO,
63 target: null,
64 completion: SloCompletionStatus.COMPLETE};
65 }
66
67 // Set the target based on the opening and the daysToClose.
68 const target = new Date(issue.openedTimestamp * 1000);
69 target.setDate(target.getDate() + daysToClose);
70 return {
71 rule: _CROS_CLOSURE_SLO,
72 target: target,
73 completion: SloCompletionStatus.INCOMPLETE};
74 },
75};
76
77/**
78 * @param {Issue} issue
79 * @return {string?} the pri's value, if found.
80 */
81const getPriFromIssue = (issue) => {
82 for (const fv of issue.fieldValues) {
83 if (fv.fieldRef.fieldName === 'Pri') {
84 return fv.value;
85 }
86 }
87};
88
89/**
90 * The number of days (since the issue was opened) allowed for it to be fixed.
91 * @private Only visible for testing.
92 */
93export const _CROS_CLOSURE_SLO_DAYS_BY_PRIORITY = Object.freeze({
94 '1': 42,
95});
96
97// https://chrome-internal.googlesource.com/infra/infra_internal/+/refs/heads/main/appengine/sheriffbot/src/sheriffbot/bug_slo_daily_queries.py#97
98const CROS_ELIGIBLE_COMPONENT_PATHS = new Set([
99 'OS>Systems>CrashReporting',
100 'OS>Systems>Displays',
101 'OS>Systems>Feedback',
102 'OS>Systems>HaTS',
103 'OS>Systems>Input',
104 'OS>Systems>Input>Keyboard',
105 'OS>Systems>Input>Mouse',
106 'OS>Systems>Input>Shortcuts',
107 'OS>Systems>Input>Touch',
108 'OS>Systems>Metrics',
109 'OS>Systems>Multidevice',
110 'OS>Systems>Multidevice>Messages',
111 'OS>Systems>Multidevice>SmartLock',
112 'OS>Systems>Multidevice>Tethering',
113 'OS>Systems>Network>Bluetooth',
114 'OS>Systems>Network>Cellular',
115 'OS>Systems>Network>VPN',
116 'OS>Systems>Network>WiFi',
117 'OS>Systems>Printing',
118 'OS>Systems>Settings',
119 'OS>Systems>Spellcheck',
120 'OS>Systems>Update',
121 'OS>Systems>Wallpaper',
122 'OS>Systems>WirelessCharging',
123 'Platform>Apps>Feedback',
124 'UI>Shell>Networking',
125]);
126
127/**
128 * Determines if an issue is eligible for _CROS_CLOSURE_SLO.
129 * @param {Issue} issue
130 * @return {boolean}
131 * @private Only visible for testing.
132 */
133export const _isCrosClosureEligible = (issue) => {
134 // If at least one component applies, continue.
135 const hasEligibleComponent = issue.componentRefs.some(
136 (component) => CROS_ELIGIBLE_COMPONENT_PATHS.has(component.path));
137 if (!hasEligibleComponent) {
138 return false;
139 }
140
141 let priority = null;
142 let hasMilestone = false;
143 for (const fv of issue.fieldValues) {
144 if (fv.fieldRef.fieldName === 'Type') {
145 // These types don't apply.
146 if (fv.value === 'Feature' || fv.value === 'FLT-Launch' ||
147 fv.value === 'Postmortem-Followup' || fv.value === 'Design-Review') {
148 return false;
149 }
150 }
151 if (fv.fieldRef.fieldName === 'Pri') {
152 priority = fv.value;
153 }
154 if (fv.fieldRef.fieldName === 'M') {
155 hasMilestone = true;
156 }
157 }
158 // P1 issues with milestones don't apply.
159 if (priority === '1' && hasMilestone) {
160 return false;
161 }
162 // Issues with the ChromeOS_No_SLO label don't apply.
163 for (const labelRef of issue.labelRefs) {
164 if (labelRef.label === 'ChromeOS_No_SLO') {
165 return false;
166 }
167 }
168 return true;
169};
170
171/**
172 * Active SLO Rules.
173 * @const {Array<SloRule>}
174 */
175const SLO_RULES = [_CROS_CLOSURE_SLO];
176
177/**
178 * Determines the SloStatus for the given issue.
179 * @param {Issue} issue The issue to check.
180 * @return {SloStatus} The status of the issue, or null if no rules apply.
181 */
182export const determineSloStatus = (issue) => {
183 try {
184 for (const rule of SLO_RULES) {
185 const status = rule.statusFunction(issue);
186 if (status) {
187 return status;
188 }
189 }
190 } catch (error) {
191 // Don't bubble up any errors in SLO_RULES functions, which might sometimes
192 // be written/updated by client teams.
193 }
194 return null;
195};