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.

issues.ts 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2022 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 { flatten, sortBy } from 'lodash';
  21. import BugIcon from '../components/icons/BugIcon';
  22. import CodeSmellIcon from '../components/icons/CodeSmellIcon';
  23. import SecurityHotspotIcon from '../components/icons/SecurityHotspotIcon';
  24. import VulnerabilityIcon from '../components/icons/VulnerabilityIcon';
  25. import { IssueType, RawIssue } from '../types/issues';
  26. import { MetricKey } from '../types/metrics';
  27. import { Dict, FlowLocation, Issue, TextRange, UserBase } from '../types/types';
  28. import { ISSUE_TYPES } from './constants';
  29. interface Rule {}
  30. interface Component {
  31. key: string;
  32. name: string;
  33. }
  34. export function sortByType<T extends Pick<Issue, 'type'>>(issues: T[]): T[] {
  35. return sortBy(issues, issue => ISSUE_TYPES.indexOf(issue.type as IssueType));
  36. }
  37. function injectRelational(
  38. issue: Dict<any>,
  39. source: any[] | undefined,
  40. baseField: string,
  41. lookupField: string
  42. ) {
  43. const newFields: Dict<any> = {};
  44. const baseValue = issue[baseField];
  45. if (baseValue !== undefined && source !== undefined) {
  46. const lookupValue = source.find(candidate => candidate[lookupField] === baseValue);
  47. if (lookupValue != null) {
  48. Object.keys(lookupValue).forEach(key => {
  49. const newKey = baseField + key.charAt(0).toUpperCase() + key.slice(1);
  50. newFields[newKey] = lookupValue[key];
  51. });
  52. }
  53. }
  54. return newFields;
  55. }
  56. function injectCommentsRelational(issue: RawIssue, users?: UserBase[]) {
  57. if (!issue.comments) {
  58. return {};
  59. }
  60. const comments = issue.comments.map(comment => {
  61. const commentWithAuthor = { ...comment, author: comment.login, login: undefined };
  62. return {
  63. ...commentWithAuthor,
  64. ...injectRelational(commentWithAuthor, users, 'author', 'login')
  65. };
  66. });
  67. return { comments };
  68. }
  69. function prepareClosed(
  70. issue: RawIssue,
  71. secondaryLocations: FlowLocation[],
  72. flows: FlowLocation[][]
  73. ) {
  74. return issue.status === 'CLOSED'
  75. ? { flows: [], line: undefined, textRange: undefined, secondaryLocations: [] }
  76. : { flows, secondaryLocations };
  77. }
  78. function ensureTextRange(issue: RawIssue): { textRange?: TextRange } {
  79. return issue.line && !issue.textRange
  80. ? {
  81. textRange: {
  82. startLine: issue.line,
  83. endLine: issue.line,
  84. startOffset: 0,
  85. endOffset: 999999
  86. }
  87. }
  88. : {};
  89. }
  90. function reverseLocations(locations: FlowLocation[]): FlowLocation[] {
  91. const x = [...locations];
  92. x.reverse();
  93. return x;
  94. }
  95. function splitFlows(
  96. issue: RawIssue,
  97. components: Component[] = []
  98. ): { secondaryLocations: FlowLocation[]; flows: FlowLocation[][] } {
  99. const parsedFlows: FlowLocation[][] = (issue.flows || [])
  100. .filter(flow => flow.locations !== undefined)
  101. .map(flow => flow.locations!.filter(location => location.textRange != null))
  102. .map(flow =>
  103. flow.map(location => {
  104. const component = components.find(component => component.key === location.component);
  105. return { ...location, componentName: component && component.name };
  106. })
  107. );
  108. const onlySecondaryLocations = parsedFlows.every(flow => flow.length === 1);
  109. return onlySecondaryLocations
  110. ? { secondaryLocations: orderLocations(flatten(parsedFlows)), flows: [] }
  111. : { secondaryLocations: [], flows: parsedFlows.map(reverseLocations) };
  112. }
  113. function orderLocations(locations: FlowLocation[]) {
  114. return sortBy(
  115. locations,
  116. location => location.textRange && location.textRange.startLine,
  117. location => location.textRange && location.textRange.startOffset
  118. );
  119. }
  120. export function parseIssueFromResponse(
  121. issue: RawIssue,
  122. components?: Component[],
  123. users?: UserBase[],
  124. rules?: Rule[]
  125. ): Issue {
  126. const { secondaryLocations, flows } = splitFlows(issue, components);
  127. return {
  128. ...issue,
  129. ...injectRelational(issue, components, 'component', 'key'),
  130. ...injectRelational(issue, components, 'project', 'key'),
  131. ...injectRelational(issue, components, 'subProject', 'key'),
  132. ...injectRelational(issue, rules, 'rule', 'key'),
  133. ...injectRelational(issue, users, 'assignee', 'login'),
  134. ...injectCommentsRelational(issue, users),
  135. ...prepareClosed(issue, secondaryLocations, flows),
  136. ...ensureTextRange(issue)
  137. } as Issue;
  138. }
  139. export const ISSUETYPE_METRIC_KEYS_MAP = {
  140. [IssueType.CodeSmell]: {
  141. metric: MetricKey.code_smells,
  142. newMetric: MetricKey.new_code_smells,
  143. rating: MetricKey.sqale_rating,
  144. newRating: MetricKey.new_maintainability_rating,
  145. ratingName: 'Maintainability',
  146. iconClass: CodeSmellIcon
  147. },
  148. [IssueType.Vulnerability]: {
  149. metric: MetricKey.vulnerabilities,
  150. newMetric: MetricKey.new_vulnerabilities,
  151. rating: MetricKey.security_rating,
  152. newRating: MetricKey.new_security_rating,
  153. ratingName: 'Security',
  154. iconClass: VulnerabilityIcon
  155. },
  156. [IssueType.Bug]: {
  157. metric: MetricKey.bugs,
  158. newMetric: MetricKey.new_bugs,
  159. rating: MetricKey.reliability_rating,
  160. newRating: MetricKey.new_reliability_rating,
  161. ratingName: 'Reliability',
  162. iconClass: BugIcon
  163. },
  164. [IssueType.SecurityHotspot]: {
  165. metric: MetricKey.security_hotspots,
  166. newMetric: MetricKey.new_security_hotspots,
  167. rating: MetricKey.security_review_rating,
  168. newRating: MetricKey.new_security_review_rating,
  169. ratingName: 'SecurityReview',
  170. iconClass: SecurityHotspotIcon
  171. }
  172. };