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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2020 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 { formatMeasure } from 'sonar-ui-common/helpers/measures';
  21. import {
  22. cleanQuery,
  23. parseAsArray,
  24. parseAsBoolean,
  25. parseAsDate,
  26. parseAsString,
  27. queriesEqual,
  28. serializeDateShort,
  29. serializeString,
  30. serializeStringArray
  31. } from 'sonar-ui-common/helpers/query';
  32. import { scrollToElement } from 'sonar-ui-common/helpers/scrolling';
  33. import { get, save } from 'sonar-ui-common/helpers/storage';
  34. import { searchMembers } from '../../api/organizations';
  35. import { searchUsers } from '../../api/users';
  36. export interface Query {
  37. assigned: boolean;
  38. assignees: string[];
  39. authors: string[];
  40. createdAfter: Date | undefined;
  41. createdAt: string;
  42. createdBefore: Date | undefined;
  43. createdInLast: string;
  44. cwe: string[];
  45. directories: string[];
  46. files: string[];
  47. issues: string[];
  48. languages: string[];
  49. modules: string[];
  50. owaspTop10: string[];
  51. projects: string[];
  52. resolutions: string[];
  53. resolved: boolean;
  54. rules: string[];
  55. sansTop25: string[];
  56. scopes: string[];
  57. severities: string[];
  58. sinceLeakPeriod: boolean;
  59. sonarsourceSecurity: string[];
  60. sort: string;
  61. statuses: string[];
  62. tags: string[];
  63. types: string[];
  64. }
  65. export const STANDARDS = 'standards';
  66. export const STANDARD_TYPES: T.StandardType[] = [
  67. 'owaspTop10',
  68. 'sansTop25',
  69. 'cwe',
  70. 'sonarsourceSecurity'
  71. ];
  72. // allow sorting by CREATION_DATE only
  73. const parseAsSort = (sort: string) => (sort === 'CREATION_DATE' ? 'CREATION_DATE' : '');
  74. const ISSUES_DEFAULT = 'sonarqube.issues.default';
  75. export function parseQuery(query: T.RawQuery): Query {
  76. return {
  77. assigned: parseAsBoolean(query.assigned),
  78. assignees: parseAsArray(query.assignees, parseAsString),
  79. authors: parseAsArray(query.authors, parseAsString),
  80. createdAfter: parseAsDate(query.createdAfter),
  81. createdAt: parseAsString(query.createdAt),
  82. createdBefore: parseAsDate(query.createdBefore),
  83. createdInLast: parseAsString(query.createdInLast),
  84. cwe: parseAsArray(query.cwe, parseAsString),
  85. directories: parseAsArray(query.directories, parseAsString),
  86. files: parseAsArray(query.fileUuids, parseAsString),
  87. issues: parseAsArray(query.issues, parseAsString),
  88. languages: parseAsArray(query.languages, parseAsString),
  89. modules: parseAsArray(query.moduleUuids, parseAsString),
  90. owaspTop10: parseAsArray(query.owaspTop10, parseAsString),
  91. projects: parseAsArray(query.projects, parseAsString),
  92. resolutions: parseAsArray(query.resolutions, parseAsString),
  93. resolved: parseAsBoolean(query.resolved),
  94. rules: parseAsArray(query.rules, parseAsString),
  95. sansTop25: parseAsArray(query.sansTop25, parseAsString),
  96. scopes: parseAsArray(query.scopes, parseAsString),
  97. severities: parseAsArray(query.severities, parseAsString),
  98. sinceLeakPeriod: parseAsBoolean(query.sinceLeakPeriod, false),
  99. sonarsourceSecurity: parseAsArray(query.sonarsourceSecurity, parseAsString),
  100. sort: parseAsSort(query.s),
  101. statuses: parseAsArray(query.statuses, parseAsString),
  102. tags: parseAsArray(query.tags, parseAsString),
  103. types: parseAsArray(query.types, parseAsString)
  104. };
  105. }
  106. export function getOpen(query: T.RawQuery): string | undefined {
  107. return query.open;
  108. }
  109. export const areMyIssuesSelected = (query: T.RawQuery) => query.myIssues === 'true';
  110. export function serializeQuery(query: Query): T.RawQuery {
  111. const filter = {
  112. assigned: query.assigned ? undefined : 'false',
  113. assignees: serializeStringArray(query.assignees),
  114. authors: serializeStringArray(query.authors),
  115. createdAfter: serializeDateShort(query.createdAfter),
  116. createdAt: serializeString(query.createdAt),
  117. createdBefore: serializeDateShort(query.createdBefore),
  118. createdInLast: serializeString(query.createdInLast),
  119. cwe: serializeStringArray(query.cwe),
  120. directories: serializeStringArray(query.directories),
  121. fileUuids: serializeStringArray(query.files),
  122. issues: serializeStringArray(query.issues),
  123. languages: serializeStringArray(query.languages),
  124. moduleUuids: serializeStringArray(query.modules),
  125. owaspTop10: serializeStringArray(query.owaspTop10),
  126. projects: serializeStringArray(query.projects),
  127. resolutions: serializeStringArray(query.resolutions),
  128. resolved: query.resolved ? undefined : 'false',
  129. rules: serializeStringArray(query.rules),
  130. s: serializeString(query.sort),
  131. sansTop25: serializeStringArray(query.sansTop25),
  132. scopes: serializeStringArray(query.scopes),
  133. severities: serializeStringArray(query.severities),
  134. sinceLeakPeriod: query.sinceLeakPeriod ? 'true' : undefined,
  135. sonarsourceSecurity: serializeStringArray(query.sonarsourceSecurity),
  136. statuses: serializeStringArray(query.statuses),
  137. tags: serializeStringArray(query.tags),
  138. types: serializeStringArray(query.types)
  139. };
  140. return cleanQuery(filter);
  141. }
  142. export const areQueriesEqual = (a: T.RawQuery, b: T.RawQuery) =>
  143. queriesEqual(parseQuery(a), parseQuery(b));
  144. export interface RawFacet {
  145. property: string;
  146. values: Array<{ val: string; count: number }>;
  147. }
  148. export interface Facet {
  149. [value: string]: number;
  150. }
  151. export function mapFacet(facet: string) {
  152. const propertyMapping: T.Dict<string> = {
  153. files: 'fileUuids',
  154. modules: 'moduleUuids'
  155. };
  156. return propertyMapping[facet] || facet;
  157. }
  158. export function parseFacets(facets: RawFacet[]): T.Dict<Facet> {
  159. if (!facets) {
  160. return {};
  161. }
  162. // for readability purpose
  163. const propertyMapping: T.Dict<string> = {
  164. fileUuids: 'files',
  165. moduleUuids: 'modules'
  166. };
  167. const result: T.Dict<Facet> = {};
  168. facets.forEach(facet => {
  169. const values: Facet = {};
  170. facet.values.forEach(value => {
  171. values[value.val] = value.count;
  172. });
  173. const finalProperty = propertyMapping[facet.property] || facet.property;
  174. result[finalProperty] = values;
  175. });
  176. return result;
  177. }
  178. export function formatFacetStat(stat: number | undefined) {
  179. return stat && formatMeasure(stat, 'SHORT_INT');
  180. }
  181. export interface ReferencedComponent {
  182. key: string;
  183. name: string;
  184. organization: string;
  185. path?: string;
  186. uuid: string;
  187. }
  188. export interface ReferencedLanguage {
  189. name: string;
  190. }
  191. export interface ReferencedRule {
  192. langName?: string;
  193. name: string;
  194. }
  195. export const searchAssignees = (
  196. query: string,
  197. organization: string | undefined,
  198. page = 1
  199. ): Promise<{ paging: T.Paging; results: T.UserBase[] }> => {
  200. return organization
  201. ? searchMembers({ organization, p: page, ps: 50, q: query }).then(({ paging, users }) => ({
  202. paging,
  203. results: users
  204. }))
  205. : searchUsers({ p: page, q: query }).then(({ paging, users }) => ({ paging, results: users }));
  206. };
  207. const LOCALSTORAGE_MY = 'my';
  208. const LOCALSTORAGE_ALL = 'all';
  209. export const isMySet = () => {
  210. return get(ISSUES_DEFAULT) === LOCALSTORAGE_MY;
  211. };
  212. export const saveMyIssues = (myIssues: boolean) =>
  213. save(ISSUES_DEFAULT, myIssues ? LOCALSTORAGE_MY : LOCALSTORAGE_ALL);
  214. export function getLocations(
  215. { flows, secondaryLocations }: Pick<T.Issue, 'flows' | 'secondaryLocations'>,
  216. selectedFlowIndex: number | undefined
  217. ) {
  218. if (selectedFlowIndex !== undefined) {
  219. return flows[selectedFlowIndex] || [];
  220. } else {
  221. return flows.length > 0 ? flows[0] : secondaryLocations;
  222. }
  223. }
  224. export function getSelectedLocation(
  225. issue: Pick<T.Issue, 'flows' | 'secondaryLocations'>,
  226. selectedFlowIndex: number | undefined,
  227. selectedLocationIndex: number | undefined
  228. ) {
  229. const locations = getLocations(issue, selectedFlowIndex);
  230. if (
  231. selectedLocationIndex !== undefined &&
  232. selectedLocationIndex >= 0 &&
  233. locations.length >= selectedLocationIndex
  234. ) {
  235. return locations[selectedLocationIndex];
  236. } else {
  237. return undefined;
  238. }
  239. }
  240. export function allLocationsEmpty(
  241. issue: Pick<T.Issue, 'flows' | 'secondaryLocations'>,
  242. selectedFlowIndex: number | undefined
  243. ) {
  244. return getLocations(issue, selectedFlowIndex).every(location => !location.msg);
  245. }
  246. export function scrollToIssue(issue: string, smooth = true) {
  247. const element = document.querySelector(`[data-issue="${issue}"]`);
  248. if (element) {
  249. scrollToElement(element, { topOffset: 250, bottomOffset: 100, smooth });
  250. }
  251. }
  252. export function shouldOpenStandardsFacet(
  253. openFacets: T.Dict<boolean>,
  254. query: Partial<Query>
  255. ): boolean {
  256. return (
  257. openFacets[STANDARDS] ||
  258. isFilteredBySecurityIssueTypes(query) ||
  259. isOneStandardChildFacetOpen(openFacets, query)
  260. );
  261. }
  262. export function shouldOpenStandardsChildFacet(
  263. openFacets: T.Dict<boolean>,
  264. query: Partial<Query>,
  265. standardType: T.StandardType
  266. ): boolean {
  267. const filter = query[standardType];
  268. return (
  269. openFacets[STANDARDS] !== false &&
  270. (openFacets[standardType] ||
  271. (standardType !== 'cwe' && filter !== undefined && filter.length > 0))
  272. );
  273. }
  274. export function shouldOpenSonarSourceSecurityFacet(
  275. openFacets: T.Dict<boolean>,
  276. query: Partial<Query>
  277. ): boolean {
  278. // Open it by default if the parent is open, and no other standard is open.
  279. return (
  280. shouldOpenStandardsChildFacet(openFacets, query, 'sonarsourceSecurity') ||
  281. (shouldOpenStandardsFacet(openFacets, query) && !isOneStandardChildFacetOpen(openFacets, query))
  282. );
  283. }
  284. function isFilteredBySecurityIssueTypes(query: Partial<Query>): boolean {
  285. return query.types !== undefined && query.types.includes('VULNERABILITY');
  286. }
  287. function isOneStandardChildFacetOpen(openFacets: T.Dict<boolean>, query: Partial<Query>): boolean {
  288. return STANDARD_TYPES.some(standardType =>
  289. shouldOpenStandardsChildFacet(openFacets, query, standardType)
  290. );
  291. }