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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 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 { getBreadcrumbs, getChildren, getComponent, getComponentData } from '../../api/components';
  21. import { getBranchLikeQuery, isPullRequest } from '../../helpers/branch-like';
  22. import { CCT_SOFTWARE_QUALITY_METRICS, OLD_TAXONOMY_METRICS } from '../../helpers/constants';
  23. import { BranchLike } from '../../types/branch-like';
  24. import { ComponentQualifier, isPortfolioLike } from '../../types/component';
  25. import { MetricKey } from '../../types/metrics';
  26. import { Breadcrumb, ComponentMeasure } from '../../types/types';
  27. import {
  28. addComponent,
  29. addComponentBreadcrumbs,
  30. addComponentChildren,
  31. getComponentBreadcrumbs,
  32. getComponentChildren,
  33. getComponent as getComponentFromBucket,
  34. } from './bucket';
  35. const METRICS = [
  36. MetricKey.ncloc,
  37. ...CCT_SOFTWARE_QUALITY_METRICS,
  38. ...OLD_TAXONOMY_METRICS,
  39. MetricKey.security_hotspots,
  40. MetricKey.coverage,
  41. MetricKey.duplicated_lines_density,
  42. ];
  43. const APPLICATION_METRICS = [MetricKey.alert_status, ...METRICS];
  44. const PORTFOLIO_METRICS = [
  45. MetricKey.releasability_rating,
  46. MetricKey.security_rating,
  47. MetricKey.reliability_rating,
  48. MetricKey.sqale_rating,
  49. MetricKey.security_review_rating,
  50. MetricKey.ncloc,
  51. ];
  52. const NEW_PORTFOLIO_METRICS = [
  53. MetricKey.releasability_rating,
  54. MetricKey.new_security_rating,
  55. MetricKey.new_reliability_rating,
  56. MetricKey.new_maintainability_rating,
  57. MetricKey.new_security_review_rating,
  58. MetricKey.new_lines,
  59. ];
  60. const LEAK_METRICS = [
  61. MetricKey.new_lines,
  62. ...CCT_SOFTWARE_QUALITY_METRICS,
  63. ...OLD_TAXONOMY_METRICS,
  64. MetricKey.security_hotspots,
  65. MetricKey.new_coverage,
  66. MetricKey.new_duplicated_lines_density,
  67. ];
  68. const PAGE_SIZE = 100;
  69. interface Children {
  70. components: ComponentMeasure[];
  71. page: number;
  72. total: number;
  73. }
  74. function prepareChildren(r: any): Children {
  75. return {
  76. components: r.components,
  77. total: r.paging.total,
  78. page: r.paging.pageIndex,
  79. };
  80. }
  81. function skipRootDir(breadcrumbs: ComponentMeasure[]) {
  82. return breadcrumbs.filter((component) => {
  83. return !(component.qualifier === ComponentQualifier.Directory && component.name === '/');
  84. });
  85. }
  86. function storeChildrenBase(children: ComponentMeasure[]) {
  87. children.forEach(addComponent);
  88. }
  89. function storeChildrenBreadcrumbs(parentComponentKey: string, children: Breadcrumb[]) {
  90. const parentBreadcrumbs = getComponentBreadcrumbs(parentComponentKey);
  91. if (parentBreadcrumbs) {
  92. children.forEach((child) => {
  93. const breadcrumbs = [...parentBreadcrumbs, child];
  94. addComponentBreadcrumbs(child.key, breadcrumbs);
  95. });
  96. }
  97. }
  98. export function getCodeMetrics(
  99. qualifier: string,
  100. branchLike?: BranchLike,
  101. options: { includeQGStatus?: boolean; newCode?: boolean } = {},
  102. ) {
  103. if (isPortfolioLike(qualifier)) {
  104. let metrics: MetricKey[] = [];
  105. if (options?.newCode === undefined) {
  106. metrics = [...NEW_PORTFOLIO_METRICS, ...PORTFOLIO_METRICS];
  107. } else if (options?.newCode) {
  108. metrics = [...NEW_PORTFOLIO_METRICS];
  109. } else {
  110. metrics = [...PORTFOLIO_METRICS];
  111. }
  112. return options.includeQGStatus ? metrics.concat(MetricKey.alert_status) : metrics;
  113. }
  114. if (qualifier === ComponentQualifier.Application) {
  115. return [...APPLICATION_METRICS];
  116. }
  117. if (isPullRequest(branchLike)) {
  118. return [...LEAK_METRICS];
  119. }
  120. return [...METRICS];
  121. }
  122. function retrieveComponentBase(
  123. componentKey: string,
  124. qualifier: string,
  125. instance: { mounted: boolean },
  126. branchLike?: BranchLike,
  127. ) {
  128. const existing = getComponentFromBucket(componentKey);
  129. if (existing) {
  130. return Promise.resolve(existing);
  131. }
  132. const metrics = getCodeMetrics(qualifier, branchLike);
  133. // eslint-disable-next-line local-rules/no-api-imports
  134. return getComponent({
  135. component: componentKey,
  136. metricKeys: metrics.join(),
  137. ...getBranchLikeQuery(branchLike),
  138. }).then(({ component }) => {
  139. if (instance.mounted) {
  140. addComponent(component);
  141. }
  142. return component;
  143. });
  144. }
  145. export async function retrieveComponentChildren(
  146. componentKey: string,
  147. qualifier: string,
  148. instance: { mounted: boolean },
  149. branchLike?: BranchLike,
  150. ): Promise<{ components: ComponentMeasure[]; page: number; total: number }> {
  151. const existing = getComponentChildren(componentKey);
  152. if (existing) {
  153. return Promise.resolve({
  154. components: existing.children,
  155. total: existing.total,
  156. page: existing.page,
  157. });
  158. }
  159. const metrics = getCodeMetrics(qualifier, branchLike, {
  160. includeQGStatus: true,
  161. });
  162. // eslint-disable-next-line local-rules/no-api-imports
  163. const result = await getChildren(componentKey, metrics, {
  164. ps: PAGE_SIZE,
  165. s: 'qualifier,name',
  166. ...getBranchLikeQuery(branchLike),
  167. }).then(prepareChildren);
  168. if (instance.mounted && isPortfolioLike(qualifier)) {
  169. await Promise.all(
  170. // eslint-disable-next-line local-rules/no-api-imports
  171. result.components.map((c) =>
  172. getComponentData({ component: c.refKey ?? c.key, branch: c.branch }),
  173. ),
  174. ).then(
  175. (data) => {
  176. data.forEach(({ component: { analysisDate } }, i) => {
  177. result.components[i].analysisDate = analysisDate;
  178. });
  179. },
  180. () => {
  181. // noop
  182. },
  183. );
  184. }
  185. if (instance.mounted) {
  186. addComponentChildren(componentKey, result.components, result.total, result.page);
  187. storeChildrenBase(result.components);
  188. storeChildrenBreadcrumbs(componentKey, result.components);
  189. }
  190. return result;
  191. }
  192. function retrieveComponentBreadcrumbs(
  193. component: string,
  194. instance: { mounted: boolean },
  195. branchLike?: BranchLike,
  196. ): Promise<Breadcrumb[]> {
  197. const existing = getComponentBreadcrumbs(component);
  198. if (existing) {
  199. return Promise.resolve(existing);
  200. }
  201. // eslint-disable-next-line local-rules/no-api-imports
  202. return getBreadcrumbs({ component, ...getBranchLikeQuery(branchLike) })
  203. .then(skipRootDir)
  204. .then((breadcrumbs) => {
  205. if (instance.mounted) {
  206. addComponentBreadcrumbs(component, breadcrumbs);
  207. }
  208. return breadcrumbs;
  209. });
  210. }
  211. export function retrieveComponent(
  212. componentKey: string,
  213. qualifier: string,
  214. instance: { mounted: boolean },
  215. branchLike?: BranchLike,
  216. ): Promise<{
  217. breadcrumbs: Breadcrumb[];
  218. component: ComponentMeasure;
  219. components: ComponentMeasure[];
  220. page: number;
  221. total: number;
  222. }> {
  223. return Promise.all([
  224. retrieveComponentBase(componentKey, qualifier, instance, branchLike),
  225. retrieveComponentChildren(componentKey, qualifier, instance, branchLike),
  226. retrieveComponentBreadcrumbs(componentKey, instance, branchLike),
  227. ]).then((r) => {
  228. return {
  229. breadcrumbs: r[2],
  230. component: r[0],
  231. components: r[1].components,
  232. page: r[1].page,
  233. total: r[1].total,
  234. };
  235. });
  236. }
  237. export function loadMoreChildren(
  238. componentKey: string,
  239. page: number,
  240. qualifier: string,
  241. instance: { mounted: boolean },
  242. branchLike?: BranchLike,
  243. ): Promise<Children> {
  244. const metrics = getCodeMetrics(qualifier, branchLike, {
  245. includeQGStatus: true,
  246. });
  247. // eslint-disable-next-line local-rules/no-api-imports
  248. return getChildren(componentKey, metrics, {
  249. ps: PAGE_SIZE,
  250. p: page,
  251. s: 'qualifier,name',
  252. ...getBranchLikeQuery(branchLike),
  253. })
  254. .then(prepareChildren)
  255. .then((r) => {
  256. if (instance.mounted) {
  257. addComponentChildren(componentKey, r.components, r.total, r.page);
  258. storeChildrenBase(r.components);
  259. storeChildrenBreadcrumbs(componentKey, r.components);
  260. }
  261. return r;
  262. });
  263. }
  264. export function mostCommonPrefix(strings: string[]) {
  265. const sortedStrings = strings.slice(0).sort((a, b) => a.localeCompare(b));
  266. const firstString = sortedStrings[0];
  267. const firstStringLength = firstString.length;
  268. const lastString = sortedStrings[sortedStrings.length - 1];
  269. let i = 0;
  270. while (i < firstStringLength && firstString.charAt(i) === lastString.charAt(i)) {
  271. i++;
  272. }
  273. const prefix = firstString.slice(0, i);
  274. const prefixTokens = prefix.split(/[\s\\/]/);
  275. const lastPrefixPart = prefixTokens[prefixTokens.length - 1];
  276. return prefix.slice(0, prefix.length - lastPrefixPart.length);
  277. }