/* * SonarQube * Copyright (C) 2009-2021 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { isNil, omitBy, pick } from 'lodash'; import { getProfilePath } from '../apps/quality-profiles/utils'; import { BranchLike, BranchParameters } from '../types/branch-like'; import { ComponentQualifier, isApplication, isPortfolioLike } from '../types/component'; import { MeasurePageView } from '../types/measures'; import { GraphType } from '../types/project-activity'; import { SecurityStandard } from '../types/security'; import { getBranchLikeQuery, isBranch, isMainBranch, isPullRequest } from './branch-like'; import { IS_SSR } from './browser'; import { getBaseUrl } from './system'; export interface Location { pathname: string; query?: T.Dict<string | undefined | number>; } type Query = Location['query']; export function getComponentOverviewUrl( componentKey: string, componentQualifier: ComponentQualifier | string, branchParameters?: BranchParameters ) { return isPortfolioLike(componentQualifier) ? getPortfolioUrl(componentKey) : getProjectQueryUrl(componentKey, branchParameters); } export function getComponentAdminUrl( componentKey: string, componentQualifier: ComponentQualifier | string ) { if (isPortfolioLike(componentQualifier)) { return getPortfolioAdminUrl(componentKey); } else if (isApplication(componentQualifier)) { return getApplicationAdminUrl(componentKey); } else { return getProjectUrl(componentKey); } } export function getProjectUrl(project: string, branch?: string): Location { return { pathname: '/dashboard', query: { id: project, branch } }; } export function getProjectQueryUrl(project: string, branchParameters?: BranchParameters): Location { return { pathname: '/dashboard', query: { id: project, ...branchParameters } }; } export function getPortfolioUrl(key: string): Location { return { pathname: '/portfolio', query: { id: key } }; } export function getPortfolioAdminUrl(key: string) { return { pathname: '/project/admin/extension/governance/console', query: { id: key, qualifier: ComponentQualifier.Portfolio } }; } export function getApplicationAdminUrl(key: string) { return { pathname: '/application/console', query: { id: key } }; } export function getComponentBackgroundTaskUrl(componentKey: string, status?: string): Location { return { pathname: '/project/background_tasks', query: { id: componentKey, status } }; } export function getBranchLikeUrl(project: string, branchLike?: BranchLike): Location { if (isPullRequest(branchLike)) { return getPullRequestUrl(project, branchLike.key); } else if (isBranch(branchLike) && !isMainBranch(branchLike)) { return getBranchUrl(project, branchLike.name); } else { return getProjectUrl(project); } } export function getBranchUrl(project: string, branch: string): Location { return { pathname: '/dashboard', query: { branch, id: project } }; } export function getPullRequestUrl(project: string, pullRequest: string): Location { return { pathname: '/dashboard', query: { id: project, pullRequest } }; } /** * Generate URL for a global issues page */ export function getIssuesUrl(query: Query): Location { const pathname = '/issues'; return { pathname, query }; } /** * Generate URL for a component's issues page */ export function getComponentIssuesUrl(componentKey: string, query?: Query): Location { return { pathname: '/project/issues', query: { ...(query || {}), id: componentKey } }; } /** * Generate URL for a component's security hotspot page */ export function getComponentSecurityHotspotsUrl(componentKey: string, query: Query = {}): Location { const { branch, pullRequest, sinceLeakPeriod, hotspots, assignedToMe, file } = query; return { pathname: '/security_hotspots', query: { id: componentKey, branch, pullRequest, sinceLeakPeriod, hotspots, assignedToMe, file, ...pick(query, [ SecurityStandard.SONARSOURCE, SecurityStandard.OWASP_TOP10, SecurityStandard.SANS_TOP25, SecurityStandard.CWE ]) } }; } /** * Generate URL for a component's drilldown page */ export function getComponentDrilldownUrl(options: { componentKey: string; metric: string; branchLike?: BranchLike; selectionKey?: string; treemapView?: boolean; listView?: boolean; }): Location { const { componentKey, metric, branchLike, selectionKey, treemapView, listView } = options; const query: Query = { id: componentKey, metric, ...getBranchLikeQuery(branchLike) }; if (treemapView) { query.view = 'treemap'; } if (listView) { query.view = 'list'; } if (selectionKey) { query.selected = selectionKey; } return { pathname: '/component_measures', query }; } export function getComponentDrilldownUrlWithSelection( componentKey: string, selectionKey: string, metric: string, branchLike?: BranchLike, view?: MeasurePageView ): Location { return getComponentDrilldownUrl({ componentKey, selectionKey, metric, branchLike, treemapView: view === 'treemap', listView: view === 'list' }); } export function getMeasureTreemapUrl(componentKey: string, metric: string) { return getComponentDrilldownUrl({ componentKey, metric, treemapView: true }); } export function getActivityUrl(component: string, branchLike?: BranchLike, graph?: GraphType) { return { pathname: '/project/activity', query: { id: component, graph, ...getBranchLikeQuery(branchLike) } }; } /** * Generate URL for a component's measure history */ export function getMeasureHistoryUrl(component: string, metric: string, branchLike?: BranchLike) { return { pathname: '/project/activity', query: { id: component, graph: 'custom', custom_metrics: metric, ...getBranchLikeQuery(branchLike) } }; } /** * Generate URL for a component's permissions page */ export function getComponentPermissionsUrl(componentKey: string): Location { return { pathname: '/project_roles', query: { id: componentKey } }; } /** * Generate URL for a quality profile */ export function getQualityProfileUrl(name: string, language: string): Location { return getProfilePath(name, language); } export function getQualityGateUrl(key: string): Location { return { pathname: '/quality_gates/show/' + encodeURIComponent(key) }; } export function getQualityGatesUrl(): Location { return { pathname: '/quality_gates' }; } export function getGlobalSettingsUrl( category?: string, query?: T.Dict<string | undefined | number> ): Location { return { pathname: '/admin/settings', query: { category, ...query } }; } export function getProjectSettingsUrl(id: string, category?: string): Location { return { pathname: '/project/settings', query: { id, category } }; } /** * Generate URL for the rules page */ export function getRulesUrl(query: Query): Location { return { pathname: '/coding_rules', query }; } /** * Generate URL for the rules page filtering only active deprecated rules */ export function getDeprecatedActiveRulesUrl(query: Query = {}): Location { const baseQuery = { activation: 'true', statuses: 'DEPRECATED' }; return getRulesUrl({ ...query, ...baseQuery }); } export function getRuleUrl(rule: string) { return getRulesUrl({ open: rule, rule_key: rule }); } export function getFormattingHelpUrl(): string { return getBaseUrl() + '/formatting/help'; } export function getCodeUrl( project: string, branchLike?: BranchLike, selected?: string, line?: number ): Location { return { pathname: '/code', query: { id: project, ...getBranchLikeQuery(branchLike), selected, line: line?.toFixed() } }; } export function getHomePageUrl(homepage: T.HomePage) { switch (homepage.type) { case 'APPLICATION': return homepage.branch ? getProjectUrl(homepage.component, homepage.branch) : getProjectUrl(homepage.component); case 'PROJECT': return homepage.branch ? getBranchUrl(homepage.component, homepage.branch) : getProjectUrl(homepage.component); case 'PORTFOLIO': return getPortfolioUrl(homepage.component); case 'PORTFOLIOS': return '/portfolios'; case 'MY_PROJECTS': return '/projects'; case 'ISSUES': case 'MY_ISSUES': return { pathname: '/issues', query: { resolved: 'false' } }; } // should never happen, but just in case... return '/projects'; } export function convertGithubApiUrlToLink(url: string) { return url .replace(/^https?:\/\/api\.github\.com/, 'https://github.com') // GH.com .replace(/\/api\/v\d+\/?$/, ''); // GH Enterprise } export function stripTrailingSlash(url: string) { return url.replace(/\/$/, ''); } export function getHostUrl(): string { if (IS_SSR) { throw new Error('No host url available on server side.'); } return window.location.origin + getBaseUrl(); } export function getPathUrlAsString(path: Location, internal = true): string { return `${internal ? getBaseUrl() : getHostUrl()}${path.pathname}?${new URLSearchParams( omitBy(path.query, isNil) ).toString()}`; } export function getReturnUrl(location: { hash?: string; query?: { return_to?: string } }) { const returnTo = location.query && location.query['return_to']; if (isRelativeUrl(returnTo)) { return returnTo + (location.hash ? location.hash : ''); } return getBaseUrl() + '/'; } export function isRelativeUrl(url?: string): boolean { const regex = new RegExp(/^\/[^/\\]/); return Boolean(url && regex.test(url)); }