You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

utils.ts 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import { getLocalizedMetricName } from '../../helpers/l10n';
  21. import { isDiffMetric } from '../../helpers/measures';
  22. import { MetricKey } from '../../types/metrics';
  23. import { CaycStatus, Condition, Dict, Metric, QualityGate } from '../../types/types';
  24. interface GroupedByMetricConditions {
  25. overallCodeConditions: Condition[];
  26. newCodeConditions: Condition[];
  27. caycConditions: Condition[];
  28. }
  29. type CommonCaycMetricKeys =
  30. | MetricKey.new_security_hotspots_reviewed
  31. | MetricKey.new_coverage
  32. | MetricKey.new_duplicated_lines_density;
  33. type OptimizedCaycMetricKeys = MetricKey.new_violations | CommonCaycMetricKeys;
  34. type UnoptimizedCaycMetricKeys =
  35. | MetricKey.new_reliability_rating
  36. | MetricKey.new_security_rating
  37. | MetricKey.new_maintainability_rating
  38. | CommonCaycMetricKeys;
  39. type AllCaycMetricKeys = OptimizedCaycMetricKeys | UnoptimizedCaycMetricKeys;
  40. const COMMON_CONDITIONS: Record<
  41. CommonCaycMetricKeys,
  42. Condition & { shouldRenderOperator?: boolean }
  43. > = {
  44. [MetricKey.new_security_hotspots_reviewed]: {
  45. id: MetricKey.new_security_hotspots_reviewed,
  46. metric: MetricKey.new_security_hotspots_reviewed,
  47. op: 'LT',
  48. error: '100',
  49. isCaycCondition: true,
  50. },
  51. [MetricKey.new_coverage]: {
  52. id: MetricKey.new_coverage,
  53. metric: MetricKey.new_coverage,
  54. op: 'LT',
  55. error: '80',
  56. isCaycCondition: true,
  57. shouldRenderOperator: true,
  58. },
  59. [MetricKey.new_duplicated_lines_density]: {
  60. id: MetricKey.new_duplicated_lines_density,
  61. metric: MetricKey.new_duplicated_lines_density,
  62. op: 'GT',
  63. error: '3',
  64. isCaycCondition: true,
  65. shouldRenderOperator: true,
  66. },
  67. };
  68. export const OPTIMIZED_CAYC_CONDITIONS: Record<
  69. OptimizedCaycMetricKeys,
  70. Condition & { shouldRenderOperator?: boolean }
  71. > = {
  72. [MetricKey.new_violations]: {
  73. id: MetricKey.new_violations,
  74. metric: MetricKey.new_violations,
  75. op: 'GT',
  76. error: '0',
  77. isCaycCondition: true,
  78. },
  79. ...COMMON_CONDITIONS,
  80. };
  81. const UNOPTIMIZED_CAYC_CONDITIONS: Record<
  82. UnoptimizedCaycMetricKeys,
  83. Condition & { shouldRenderOperator?: boolean }
  84. > = {
  85. [MetricKey.new_reliability_rating]: {
  86. id: MetricKey.new_reliability_rating,
  87. metric: MetricKey.new_reliability_rating,
  88. op: 'GT',
  89. error: '1',
  90. isCaycCondition: true,
  91. },
  92. [MetricKey.new_security_rating]: {
  93. id: MetricKey.new_security_rating,
  94. metric: MetricKey.new_security_rating,
  95. op: 'GT',
  96. error: '1',
  97. isCaycCondition: true,
  98. },
  99. [MetricKey.new_maintainability_rating]: {
  100. id: MetricKey.new_maintainability_rating,
  101. metric: MetricKey.new_maintainability_rating,
  102. op: 'GT',
  103. error: '1',
  104. isCaycCondition: true,
  105. },
  106. ...COMMON_CONDITIONS,
  107. };
  108. const ALL_CAYC_CONDITIONS: Record<
  109. AllCaycMetricKeys,
  110. Condition & { shouldRenderOperator?: boolean }
  111. > = {
  112. ...OPTIMIZED_CAYC_CONDITIONS,
  113. ...UNOPTIMIZED_CAYC_CONDITIONS,
  114. };
  115. const CAYC_CONDITION_ORDER_PRIORITIES: Dict<number> = [
  116. MetricKey.new_violations,
  117. MetricKey.new_security_hotspots_reviewed,
  118. MetricKey.new_coverage,
  119. MetricKey.new_duplicated_lines_density,
  120. ]
  121. .reverse()
  122. .reduce((acc, key, i) => ({ ...acc, [key.toString()]: i + 1 }), {} as Dict<number>);
  123. const CAYC_CONDITIONS_WITHOUT_FIXED_VALUE: AllCaycMetricKeys[] = [
  124. MetricKey.new_duplicated_lines_density,
  125. MetricKey.new_coverage,
  126. ];
  127. const CAYC_CONDITIONS_WITH_FIXED_VALUE: AllCaycMetricKeys[] = [
  128. MetricKey.new_security_hotspots_reviewed,
  129. MetricKey.new_violations,
  130. MetricKey.new_reliability_rating,
  131. MetricKey.new_security_rating,
  132. MetricKey.new_maintainability_rating,
  133. ];
  134. export function isConditionWithFixedValue(condition: Condition) {
  135. return CAYC_CONDITIONS_WITH_FIXED_VALUE.includes(condition.metric as OptimizedCaycMetricKeys);
  136. }
  137. export function getCaycConditionMetadata(condition: Condition) {
  138. const foundCondition = OPTIMIZED_CAYC_CONDITIONS[condition.metric as OptimizedCaycMetricKeys];
  139. return {
  140. shouldRenderOperator: foundCondition?.shouldRenderOperator,
  141. };
  142. }
  143. export function isQualityGateOptimized(qualityGate: QualityGate) {
  144. return (
  145. !qualityGate.isBuiltIn &&
  146. qualityGate.caycStatus !== CaycStatus.NonCompliant &&
  147. Object.values(OPTIMIZED_CAYC_CONDITIONS).every((condition) => {
  148. const foundCondition = qualityGate.conditions?.find((c) => c.metric === condition.metric);
  149. return (
  150. foundCondition &&
  151. !isWeakCondition(condition.metric as OptimizedCaycMetricKeys, foundCondition)
  152. );
  153. })
  154. );
  155. }
  156. function isWeakCondition(key: AllCaycMetricKeys, selectedCondition: Condition) {
  157. return (
  158. !CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(key) &&
  159. ALL_CAYC_CONDITIONS[key]?.error !== selectedCondition.error
  160. );
  161. }
  162. export function getWeakMissingAndNonCaycConditions(conditions: Condition[]) {
  163. const result: {
  164. weakConditions: Condition[];
  165. missingConditions: Condition[];
  166. } = {
  167. weakConditions: [],
  168. missingConditions: [],
  169. };
  170. Object.keys(OPTIMIZED_CAYC_CONDITIONS).forEach((key: OptimizedCaycMetricKeys) => {
  171. const selectedCondition = conditions.find((condition) => condition.metric === key);
  172. if (!selectedCondition) {
  173. result.missingConditions.push(OPTIMIZED_CAYC_CONDITIONS[key]);
  174. } else if (isWeakCondition(key, selectedCondition)) {
  175. result.weakConditions.push(selectedCondition);
  176. }
  177. });
  178. return result;
  179. }
  180. function groupConditionsByMetric(
  181. conditions: Condition[],
  182. isBuiltInQG = false,
  183. ): GroupedByMetricConditions {
  184. return conditions.reduce(
  185. (result, condition) => {
  186. const isNewCode = isDiffMetric(condition.metric);
  187. if (condition.isCaycCondition && isBuiltInQG) {
  188. result.caycConditions.push(condition);
  189. } else if (isNewCode) {
  190. result.newCodeConditions.push(condition);
  191. } else {
  192. result.overallCodeConditions.push(condition);
  193. }
  194. return result;
  195. },
  196. {
  197. overallCodeConditions: [] as Condition[],
  198. newCodeConditions: [] as Condition[],
  199. caycConditions: [] as Condition[],
  200. },
  201. );
  202. }
  203. export function groupAndSortByPriorityConditions(
  204. conditions: Condition[],
  205. metrics: Dict<Metric>,
  206. isBuiltInQG = false,
  207. ): GroupedByMetricConditions {
  208. const groupedConditions = groupConditionsByMetric(conditions, isBuiltInQG);
  209. function sortFn(a: Condition, b: Condition) {
  210. const priorityA = CAYC_CONDITION_ORDER_PRIORITIES[a.metric] ?? 0;
  211. const priorityB = CAYC_CONDITION_ORDER_PRIORITIES[b.metric] ?? 0;
  212. const diff = priorityB - priorityA;
  213. if (diff !== 0) {
  214. return diff;
  215. }
  216. return metrics[a.metric].name.localeCompare(metrics[b.metric].name, undefined, {
  217. sensitivity: 'base',
  218. });
  219. }
  220. groupedConditions.newCodeConditions.sort(sortFn);
  221. groupedConditions.overallCodeConditions.sort(sortFn);
  222. groupedConditions.caycConditions.sort(sortFn);
  223. return groupedConditions;
  224. }
  225. export function getCorrectCaycCondition(condition: Condition) {
  226. const conditionMetric = condition.metric as OptimizedCaycMetricKeys;
  227. if (CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(conditionMetric)) {
  228. return condition;
  229. }
  230. return OPTIMIZED_CAYC_CONDITIONS[conditionMetric];
  231. }
  232. export function addCondition(qualityGate: QualityGate, condition: Condition): QualityGate {
  233. const oldConditions = qualityGate.conditions || [];
  234. const conditions = [...oldConditions, condition];
  235. if (conditions) {
  236. qualityGate.caycStatus = updateCaycCompliantStatus(conditions);
  237. }
  238. return { ...qualityGate, conditions };
  239. }
  240. export function deleteCondition(qualityGate: QualityGate, condition: Condition): QualityGate {
  241. const conditions =
  242. qualityGate.conditions && qualityGate.conditions.filter((candidate) => candidate !== condition);
  243. if (conditions) {
  244. qualityGate.caycStatus = updateCaycCompliantStatus(conditions);
  245. }
  246. return { ...qualityGate, conditions };
  247. }
  248. export function replaceCondition(
  249. qualityGate: QualityGate,
  250. newCondition: Condition,
  251. oldCondition: Condition,
  252. ): QualityGate {
  253. const conditions =
  254. qualityGate.conditions &&
  255. qualityGate.conditions.map((candidate) => {
  256. return candidate === oldCondition ? newCondition : candidate;
  257. });
  258. if (conditions) {
  259. qualityGate.caycStatus = updateCaycCompliantStatus(conditions);
  260. }
  261. return { ...qualityGate, conditions };
  262. }
  263. function updateCaycCompliantStatus(conditions: Condition[]) {
  264. const isCompliantOptimized = Object.values(OPTIMIZED_CAYC_CONDITIONS).every((condition) => {
  265. const foundCondition = conditions.find((c) => c.metric === condition.metric);
  266. return (
  267. foundCondition &&
  268. !isWeakCondition(condition.metric as OptimizedCaycMetricKeys, foundCondition)
  269. );
  270. });
  271. const isCompliantUnoptimized = Object.values(UNOPTIMIZED_CAYC_CONDITIONS).every((condition) => {
  272. const foundCondition = conditions.find((c) => c.metric === condition.metric);
  273. return (
  274. foundCondition &&
  275. !isWeakCondition(condition.metric as UnoptimizedCaycMetricKeys, foundCondition)
  276. );
  277. });
  278. if (isCompliantOptimized || isCompliantUnoptimized) {
  279. return CaycStatus.Compliant;
  280. }
  281. return CaycStatus.NonCompliant;
  282. }
  283. export function getPossibleOperators(metric: Metric) {
  284. if (metric.direction === 1) {
  285. return 'LT';
  286. } else if (metric.direction === -1) {
  287. return 'GT';
  288. }
  289. return ['LT', 'GT'];
  290. }
  291. function metricKeyExists(key: string, metrics: Dict<Metric>) {
  292. return metrics[key] !== undefined;
  293. }
  294. function getNoDiffMetric(metric: Metric, metrics: Dict<Metric>) {
  295. const regularMetricKey = metric.key.replace(/^new_/, '');
  296. if (isDiffMetric(metric.key) && metricKeyExists(regularMetricKey, metrics)) {
  297. return metrics[regularMetricKey];
  298. } else if (metric.key === 'new_maintainability_rating') {
  299. return metrics['sqale_rating'] || metric;
  300. }
  301. return metric;
  302. }
  303. export function getLocalizedMetricNameNoDiffMetric(metric: Metric, metrics: Dict<Metric>) {
  304. return getLocalizedMetricName(getNoDiffMetric(metric, metrics));
  305. }