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 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 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 { groupBy, memoize, sortBy, toPairs } from 'lodash';
  21. import { enhanceMeasure } from '../../components/measure/utils';
  22. import { isBranch, isPullRequest } from '../../helpers/branch-like';
  23. import { getLocalizedMetricName } from '../../helpers/l10n';
  24. import { getDisplayMetrics, isDiffMetric } from '../../helpers/measures';
  25. import { cleanQuery, parseAsString, serializeString } from '../../helpers/query';
  26. import { BranchLike } from '../../types/branch-like';
  27. import { ComponentQualifier } from '../../types/component';
  28. import { MeasurePageView } from '../../types/measures';
  29. import { MetricKey } from '../../types/metrics';
  30. import { bubbles } from './config/bubbles';
  31. import { domains } from './config/domains';
  32. export const BUBBLES_FETCH_LIMIT = 500;
  33. export const PROJECT_OVERVEW = 'project_overview';
  34. export const DEFAULT_VIEW: MeasurePageView = 'tree';
  35. export const DEFAULT_METRIC = PROJECT_OVERVEW;
  36. export const KNOWN_DOMAINS = [
  37. 'Releasability',
  38. 'Reliability',
  39. 'Security',
  40. 'SecurityReview',
  41. 'Maintainability',
  42. 'Coverage',
  43. 'Duplications',
  44. 'Size',
  45. 'Complexity'
  46. ];
  47. const BANNED_MEASURES = [
  48. 'blocker_violations',
  49. 'new_blocker_violations',
  50. 'critical_violations',
  51. 'new_critical_violations',
  52. 'major_violations',
  53. 'new_major_violations',
  54. 'minor_violations',
  55. 'new_minor_violations',
  56. 'info_violations',
  57. 'new_info_violations'
  58. ];
  59. export function filterMeasures(measures: T.MeasureEnhanced[]): T.MeasureEnhanced[] {
  60. return measures.filter(measure => !BANNED_MEASURES.includes(measure.metric.key));
  61. }
  62. export function sortMeasures(
  63. domainName: string,
  64. measures: Array<T.MeasureEnhanced | string>
  65. ): Array<T.MeasureEnhanced | string> {
  66. const config = domains[domainName] || {};
  67. const configOrder = config.order || [];
  68. return sortBy(measures, [
  69. (item: T.MeasureEnhanced | string) => {
  70. if (typeof item === 'string') {
  71. return configOrder.indexOf(item);
  72. }
  73. const idx = configOrder.indexOf(item.metric.key);
  74. return idx >= 0 ? idx : configOrder.length;
  75. },
  76. (item: T.MeasureEnhanced | string) =>
  77. typeof item === 'string' ? item : getLocalizedMetricName(item.metric)
  78. ]);
  79. }
  80. export function addMeasureCategories(domainName: string, measures: T.MeasureEnhanced[]) {
  81. const categories = domains[domainName] && domains[domainName].categories;
  82. if (categories && categories.length > 0) {
  83. return [...categories, ...measures];
  84. }
  85. return measures;
  86. }
  87. export function enhanceComponent(
  88. component: T.ComponentMeasure,
  89. metric: Pick<T.Metric, 'key'> | undefined,
  90. metrics: T.Dict<T.Metric>
  91. ): T.ComponentMeasureEnhanced {
  92. if (!component.measures) {
  93. return { ...component, measures: [] };
  94. }
  95. const enhancedMeasures = component.measures.map(measure => enhanceMeasure(measure, metrics));
  96. const measure = metric && enhancedMeasures.find(measure => measure.metric.key === metric.key);
  97. const value = measure && measure.value;
  98. const leak = measure && measure.leak;
  99. return { ...component, value, leak, measures: enhancedMeasures };
  100. }
  101. export function isSecurityReviewMetric(metricKey: MetricKey | string): boolean {
  102. return [
  103. MetricKey.security_hotspots,
  104. MetricKey.security_hotspots_reviewed,
  105. MetricKey.security_review_rating,
  106. MetricKey.new_security_hotspots,
  107. MetricKey.new_security_hotspots_reviewed,
  108. MetricKey.new_security_review_rating
  109. ].includes(metricKey as MetricKey);
  110. }
  111. export function banQualityGateMeasure({
  112. measures = [],
  113. qualifier
  114. }: T.ComponentMeasure): T.Measure[] {
  115. const bannedMetrics: string[] = [];
  116. if (ComponentQualifier.Portfolio !== qualifier && ComponentQualifier.SubPortfolio !== qualifier) {
  117. bannedMetrics.push('alert_status');
  118. }
  119. if (qualifier === ComponentQualifier.Application) {
  120. bannedMetrics.push('releasability_rating', 'releasability_effort');
  121. }
  122. return measures.filter(measure => !bannedMetrics.includes(measure.metric));
  123. }
  124. export const groupByDomains = memoize((measures: T.MeasureEnhanced[]) => {
  125. const domains = toPairs(groupBy(measures, measure => measure.metric.domain)).map(r => ({
  126. name: r[0],
  127. measures: r[1]
  128. }));
  129. return sortBy(domains, [
  130. (domain: { name: string; measures: T.MeasureEnhanced[] }) => {
  131. const idx = KNOWN_DOMAINS.indexOf(domain.name);
  132. return idx >= 0 ? idx : KNOWN_DOMAINS.length;
  133. },
  134. 'name'
  135. ]);
  136. });
  137. export function hasList(metric: string): boolean {
  138. return !['releasability_rating', 'releasability_effort'].includes(metric);
  139. }
  140. export function hasTree(metric: string): boolean {
  141. return metric !== 'alert_status';
  142. }
  143. export function hasTreemap(metric: string, type: string): boolean {
  144. return ['PERCENT', 'RATING', 'LEVEL'].includes(type) && hasTree(metric);
  145. }
  146. export function hasBubbleChart(domainName: string): boolean {
  147. return bubbles[domainName] !== undefined;
  148. }
  149. export function hasFacetStat(metric: string): boolean {
  150. return metric !== 'alert_status';
  151. }
  152. export function hasFullMeasures(branch?: BranchLike) {
  153. return !branch || isBranch(branch);
  154. }
  155. export function getMeasuresPageMetricKeys(metrics: T.Dict<T.Metric>, branch?: BranchLike) {
  156. const metricKeys = getDisplayMetrics(Object.values(metrics)).map(metric => metric.key);
  157. if (isPullRequest(branch)) {
  158. return metricKeys.filter(key => isDiffMetric(key));
  159. } else {
  160. return metricKeys;
  161. }
  162. }
  163. export function getBubbleMetrics(domain: string, metrics: T.Dict<T.Metric>) {
  164. const conf = bubbles[domain];
  165. return {
  166. x: metrics[conf.x],
  167. y: metrics[conf.y],
  168. size: metrics[conf.size],
  169. colors: conf.colors && conf.colors.map(color => metrics[color])
  170. };
  171. }
  172. export function getBubbleYDomain(domain: string) {
  173. return bubbles[domain].yDomain;
  174. }
  175. export function isProjectOverview(metric: string) {
  176. return metric === PROJECT_OVERVEW;
  177. }
  178. function parseView(metric: string, rawView?: string): MeasurePageView {
  179. const view = (parseAsString(rawView) || DEFAULT_VIEW) as MeasurePageView;
  180. if (!hasTree(metric)) {
  181. return 'list';
  182. } else if (view === 'list' && !hasList(metric)) {
  183. return 'tree';
  184. }
  185. return view;
  186. }
  187. export interface Query {
  188. metric: string;
  189. selected?: string;
  190. view: MeasurePageView;
  191. }
  192. export const parseQuery = memoize(
  193. (urlQuery: T.RawQuery): Query => {
  194. const metric = parseAsString(urlQuery['metric']) || DEFAULT_METRIC;
  195. return {
  196. metric,
  197. selected: parseAsString(urlQuery['selected']),
  198. view: parseView(metric, urlQuery['view'])
  199. };
  200. }
  201. );
  202. export const serializeQuery = memoize((query: Query) => {
  203. return cleanQuery({
  204. metric: query.metric === DEFAULT_METRIC ? undefined : serializeString(query.metric),
  205. selected: serializeString(query.selected),
  206. view: query.view === DEFAULT_VIEW ? undefined : serializeString(query.view)
  207. });
  208. });