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.

urls.ts 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  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 { isNil, omitBy, pick } from 'lodash';
  21. import { getProfilePath } from '../apps/quality-profiles/utils';
  22. import { BranchLike, BranchParameters } from '../types/branch-like';
  23. import { ComponentQualifier, isApplication, isPortfolioLike } from '../types/component';
  24. import { MeasurePageView } from '../types/measures';
  25. import { GraphType } from '../types/project-activity';
  26. import { SecurityStandard } from '../types/security';
  27. import { Dict, HomePage } from '../types/types';
  28. import { getBranchLikeQuery, isBranch, isMainBranch, isPullRequest } from './branch-like';
  29. import { IS_SSR } from './browser';
  30. import { serializeOptionalBoolean } from './query';
  31. import { getBaseUrl } from './system';
  32. export interface Location {
  33. pathname: string;
  34. query?: Dict<string | undefined | number>;
  35. }
  36. type Query = Location['query'];
  37. export function getComponentOverviewUrl(
  38. componentKey: string,
  39. componentQualifier: ComponentQualifier | string,
  40. branchParameters?: BranchParameters
  41. ) {
  42. return isPortfolioLike(componentQualifier)
  43. ? getPortfolioUrl(componentKey)
  44. : getProjectQueryUrl(componentKey, branchParameters);
  45. }
  46. export function getComponentAdminUrl(
  47. componentKey: string,
  48. componentQualifier: ComponentQualifier | string
  49. ) {
  50. if (isPortfolioLike(componentQualifier)) {
  51. return getPortfolioAdminUrl(componentKey);
  52. } else if (isApplication(componentQualifier)) {
  53. return getApplicationAdminUrl(componentKey);
  54. } else {
  55. return getProjectUrl(componentKey);
  56. }
  57. }
  58. export function getProjectUrl(project: string, branch?: string): Location {
  59. return { pathname: '/dashboard', query: { id: project, branch } };
  60. }
  61. export function getProjectQueryUrl(project: string, branchParameters?: BranchParameters): Location {
  62. return { pathname: '/dashboard', query: { id: project, ...branchParameters } };
  63. }
  64. export function getPortfolioUrl(key: string): Location {
  65. return { pathname: '/portfolio', query: { id: key } };
  66. }
  67. export function getPortfolioAdminUrl(key: string) {
  68. return {
  69. pathname: '/project/admin/extension/governance/console',
  70. query: { id: key, qualifier: ComponentQualifier.Portfolio }
  71. };
  72. }
  73. export function getApplicationAdminUrl(key: string) {
  74. return {
  75. pathname: '/project/admin/extension/developer-server/application-console',
  76. query: { id: key }
  77. };
  78. }
  79. export function getComponentBackgroundTaskUrl(
  80. componentKey: string,
  81. status?: string,
  82. taskType?: string
  83. ): Location {
  84. return { pathname: '/project/background_tasks', query: { id: componentKey, status, taskType } };
  85. }
  86. export function getBranchLikeUrl(project: string, branchLike?: BranchLike): Location {
  87. if (isPullRequest(branchLike)) {
  88. return getPullRequestUrl(project, branchLike.key);
  89. } else if (isBranch(branchLike) && !isMainBranch(branchLike)) {
  90. return getBranchUrl(project, branchLike.name);
  91. } else {
  92. return getProjectUrl(project);
  93. }
  94. }
  95. export function getBranchUrl(project: string, branch: string): Location {
  96. return { pathname: '/dashboard', query: { branch, id: project } };
  97. }
  98. export function getPullRequestUrl(project: string, pullRequest: string): Location {
  99. return { pathname: '/dashboard', query: { id: project, pullRequest } };
  100. }
  101. /**
  102. * Generate URL for a global issues page
  103. */
  104. export function getIssuesUrl(query: Query): Location {
  105. const pathname = '/issues';
  106. return { pathname, query };
  107. }
  108. /**
  109. * Generate URL for a component's issues page
  110. */
  111. export function getComponentIssuesUrl(componentKey: string, query?: Query): Location {
  112. return { pathname: '/project/issues', query: { ...(query || {}), id: componentKey } };
  113. }
  114. /**
  115. * Generate URL for a component's security hotspot page
  116. */
  117. export function getComponentSecurityHotspotsUrl(componentKey: string, query: Query = {}): Location {
  118. const { branch, pullRequest, sinceLeakPeriod, hotspots, assignedToMe, file } = query;
  119. return {
  120. pathname: '/security_hotspots',
  121. query: {
  122. id: componentKey,
  123. branch,
  124. pullRequest,
  125. sinceLeakPeriod,
  126. hotspots,
  127. assignedToMe,
  128. file,
  129. ...pick(query, [
  130. SecurityStandard.SONARSOURCE,
  131. SecurityStandard.OWASP_TOP10,
  132. SecurityStandard.SANS_TOP25,
  133. SecurityStandard.CWE
  134. ])
  135. }
  136. };
  137. }
  138. /**
  139. * Generate URL for a component's drilldown page
  140. */
  141. export function getComponentDrilldownUrl(options: {
  142. componentKey: string;
  143. metric: string;
  144. branchLike?: BranchLike;
  145. selectionKey?: string;
  146. treemapView?: boolean;
  147. listView?: boolean;
  148. asc?: boolean;
  149. }): Location {
  150. const { componentKey, metric, branchLike, selectionKey, treemapView, listView, asc } = options;
  151. const query: Query = { id: componentKey, metric, ...getBranchLikeQuery(branchLike) };
  152. if (treemapView) {
  153. query.view = 'treemap';
  154. }
  155. if (listView) {
  156. query.view = 'list';
  157. query.asc = serializeOptionalBoolean(asc);
  158. }
  159. if (selectionKey) {
  160. query.selected = selectionKey;
  161. }
  162. return { pathname: '/component_measures', query };
  163. }
  164. export function getComponentDrilldownUrlWithSelection(
  165. componentKey: string,
  166. selectionKey: string,
  167. metric: string,
  168. branchLike?: BranchLike,
  169. view?: MeasurePageView
  170. ): Location {
  171. return getComponentDrilldownUrl({
  172. componentKey,
  173. selectionKey,
  174. metric,
  175. branchLike,
  176. treemapView: view === 'treemap',
  177. listView: view === 'list'
  178. });
  179. }
  180. export function getMeasureTreemapUrl(componentKey: string, metric: string) {
  181. return getComponentDrilldownUrl({ componentKey, metric, treemapView: true });
  182. }
  183. export function getActivityUrl(component: string, branchLike?: BranchLike, graph?: GraphType) {
  184. return {
  185. pathname: '/project/activity',
  186. query: { id: component, graph, ...getBranchLikeQuery(branchLike) }
  187. };
  188. }
  189. /**
  190. * Generate URL for a component's measure history
  191. */
  192. export function getMeasureHistoryUrl(component: string, metric: string, branchLike?: BranchLike) {
  193. return {
  194. pathname: '/project/activity',
  195. query: {
  196. id: component,
  197. graph: 'custom',
  198. custom_metrics: metric,
  199. ...getBranchLikeQuery(branchLike)
  200. }
  201. };
  202. }
  203. /**
  204. * Generate URL for a component's permissions page
  205. */
  206. export function getComponentPermissionsUrl(componentKey: string): Location {
  207. return { pathname: '/project_roles', query: { id: componentKey } };
  208. }
  209. /**
  210. * Generate URL for a quality profile
  211. */
  212. export function getQualityProfileUrl(name: string, language: string): Location {
  213. return getProfilePath(name, language);
  214. }
  215. export function getQualityGateUrl(key: string): Location {
  216. return {
  217. pathname: '/quality_gates/show/' + encodeURIComponent(key)
  218. };
  219. }
  220. export function getQualityGatesUrl(): Location {
  221. return {
  222. pathname: '/quality_gates'
  223. };
  224. }
  225. export function getGlobalSettingsUrl(
  226. category?: string,
  227. query?: Dict<string | undefined | number>
  228. ): Location {
  229. return {
  230. pathname: '/admin/settings',
  231. query: { category, ...query }
  232. };
  233. }
  234. export function getProjectSettingsUrl(id: string, category?: string): Location {
  235. return {
  236. pathname: '/project/settings',
  237. query: { id, category }
  238. };
  239. }
  240. /**
  241. * Generate URL for the rules page
  242. */
  243. export function getRulesUrl(query: Query): Location {
  244. return { pathname: '/coding_rules', query };
  245. }
  246. /**
  247. * Generate URL for the rules page filtering only active deprecated rules
  248. */
  249. export function getDeprecatedActiveRulesUrl(query: Query = {}): Location {
  250. const baseQuery = { activation: 'true', statuses: 'DEPRECATED' };
  251. return getRulesUrl({ ...query, ...baseQuery });
  252. }
  253. export function getRuleUrl(rule: string) {
  254. return getRulesUrl({ open: rule, rule_key: rule });
  255. }
  256. export function getFormattingHelpUrl(): string {
  257. return getBaseUrl() + '/formatting/help';
  258. }
  259. export function getCodeUrl(
  260. project: string,
  261. branchLike?: BranchLike,
  262. selected?: string,
  263. line?: number
  264. ): Location {
  265. return {
  266. pathname: '/code',
  267. query: { id: project, ...getBranchLikeQuery(branchLike), selected, line: line?.toFixed() }
  268. };
  269. }
  270. export function getHomePageUrl(homepage: HomePage) {
  271. switch (homepage.type) {
  272. case 'APPLICATION':
  273. return homepage.branch
  274. ? getProjectUrl(homepage.component, homepage.branch)
  275. : getProjectUrl(homepage.component);
  276. case 'PROJECT':
  277. return homepage.branch
  278. ? getBranchUrl(homepage.component, homepage.branch)
  279. : getProjectUrl(homepage.component);
  280. case 'PORTFOLIO':
  281. return getPortfolioUrl(homepage.component);
  282. case 'PORTFOLIOS':
  283. return '/portfolios';
  284. case 'MY_PROJECTS':
  285. return '/projects';
  286. case 'ISSUES':
  287. case 'MY_ISSUES':
  288. return { pathname: '/issues', query: { resolved: 'false' } };
  289. }
  290. // should never happen, but just in case...
  291. return '/projects';
  292. }
  293. export function convertGithubApiUrlToLink(url: string) {
  294. return url
  295. .replace(/^https?:\/\/api\.github\.com/, 'https://github.com') // GH.com
  296. .replace(/\/api\/v\d+\/?$/, ''); // GH Enterprise
  297. }
  298. export function stripTrailingSlash(url: string) {
  299. return url.replace(/\/$/, '');
  300. }
  301. export function getHostUrl(): string {
  302. if (IS_SSR) {
  303. throw new Error('No host url available on server side.');
  304. }
  305. return window.location.origin + getBaseUrl();
  306. }
  307. export function getPathUrlAsString(path: Location, internal = true): string {
  308. return `${internal ? getBaseUrl() : getHostUrl()}${path.pathname}?${new URLSearchParams(
  309. omitBy(path.query, isNil)
  310. ).toString()}`;
  311. }
  312. export function getReturnUrl(location: { hash?: string; query?: { return_to?: string } }) {
  313. const returnTo = location.query && location.query['return_to'];
  314. if (isRelativeUrl(returnTo)) {
  315. return returnTo + (location.hash ? location.hash : '');
  316. }
  317. return getBaseUrl() + '/';
  318. }
  319. export function isRelativeUrl(url?: string): boolean {
  320. const regex = new RegExp(/^\/[^/\\]/);
  321. return Boolean(url && regex.test(url));
  322. }