@@ -31,7 +31,7 @@ import { | |||
} from 'design-system'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import DocumentationLink from '../../../components/common/DocumentationLink'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { IndexationNotificationType } from '../../../types/indexation'; | |||
@@ -202,7 +202,7 @@ function renderBackgroundTasksPageLink(hasError: boolean, text: string) { | |||
<Link | |||
to={{ | |||
pathname: '/admin/background_tasks', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
taskType: TaskTypes.IssueSync, | |||
status: hasError ? TaskStatuses.Failed : undefined, | |||
}), |
@@ -21,7 +21,7 @@ import { DropdownMenu, InputSearch, ItemDivider, Link } from 'design-system'; | |||
import * as React from 'react'; | |||
import { withRouter } from '~sonar-aligned/components/hoc/withRouter'; | |||
import { isBranch } from '~sonar-aligned/helpers/branch-like'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { ComponentQualifier } from '~sonar-aligned/types/component'; | |||
import { Router } from '~sonar-aligned/types/router'; | |||
import { | |||
@@ -189,7 +189,10 @@ export class Menu extends React.PureComponent<Props, State> { | |||
onClick={() => { | |||
onClose(); | |||
}} | |||
to={{ pathname: '/project/branches', search: queryToSearch({ id: component.key }) }} | |||
to={{ | |||
pathname: '/project/branches', | |||
search: queryToSearchString({ id: component.key }), | |||
}} | |||
> | |||
{translate('branch_like_navigation.manage')} | |||
</Link> |
@@ -29,7 +29,7 @@ import { | |||
import * as React from 'react'; | |||
import { Helmet } from 'react-helmet-async'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import Suggestions from '../../../components/embed-docs-modal/Suggestions'; | |||
import { now } from '../../../helpers/dates'; | |||
import { translate } from '../../../helpers/l10n'; | |||
@@ -96,7 +96,7 @@ export default function AuditAppRenderer(props: Readonly<AuditAppRendererProps>) | |||
<Link | |||
to={{ | |||
pathname: '/admin/settings', | |||
search: queryToSearch({ category: 'housekeeping' }), | |||
search: queryToSearchString({ category: 'housekeeping' }), | |||
hash: '#auditLogs', | |||
}} | |||
> |
@@ -21,7 +21,7 @@ import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react'; | |||
import { Badge, BranchIcon, LightLabel, Note, QualifierIcon } from 'design-system'; | |||
import * as React from 'react'; | |||
import { getBranchLikeQuery } from '~sonar-aligned/helpers/branch-like'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { ComponentQualifier } from '~sonar-aligned/types/component'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { isDefined } from '../../../helpers/types'; | |||
@@ -170,7 +170,7 @@ function renderNameWithIcon( | |||
<LinkStandalone | |||
highlight={LinkHighlight.CurrentColor} | |||
iconLeft={showIcon && <QualifierIcon className="sw-mr-2" qualifier={component.qualifier} />} | |||
to={{ pathname: '/code', search: queryToSearch(query) }} | |||
to={{ pathname: '/code', search: queryToSearchString(query) }} | |||
> | |||
{name} | |||
</LinkStandalone> |
@@ -20,7 +20,7 @@ | |||
import { Accordion, FlagMessage, Link, SearchHighlighter, Spinner } from 'design-system'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import ListFooter from '../../../../components/controls/ListFooter'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { getBaseUrl } from '../../../../helpers/system'; | |||
@@ -78,7 +78,7 @@ export default function AzureProjectAccordion(props: AzureProjectAccordionProps) | |||
<Link | |||
to={{ | |||
pathname: '/projects/create', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
mode: CreateProjectModes.AzureDevOps, | |||
resetPat: 1, | |||
}), |
@@ -27,7 +27,7 @@ import { | |||
} from 'design-system'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { useAppState } from '../../../../app/components/app-state/withAppStateContext'; | |||
import { AvailableFeaturesContext } from '../../../../app/components/available-features/AvailableFeaturesContext'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
@@ -102,7 +102,7 @@ export default function AzureProjectCreateRenderer( | |||
<Link | |||
to={{ | |||
pathname: '/projects/create', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
mode: CreateProjectModes.AzureDevOps, | |||
mono: true, | |||
}), |
@@ -21,7 +21,7 @@ import { FlagMessage, Link } from 'design-system'; | |||
import { uniqBy } from 'lodash'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import ListFooter from '../../../../components/controls/ListFooter'; | |||
import { translate, translateWithParameters } from '../../../../helpers/l10n'; | |||
import { AzureProject, AzureRepository } from '../../../../types/alm-integration'; | |||
@@ -66,7 +66,10 @@ export default function AzureProjectsList(props: AzureProjectsListProps) { | |||
<Link | |||
to={{ | |||
pathname: '/projects/create', | |||
search: queryToSearch({ mode: CreateProjectModes.AzureDevOps, resetPat: 1 }), | |||
search: queryToSearchString({ | |||
mode: CreateProjectModes.AzureDevOps, | |||
resetPat: 1, | |||
}), | |||
}} | |||
> | |||
{translate('onboarding.create_project.update_your_token')} |
@@ -21,7 +21,7 @@ import { Link, Spinner } from '@sonarsource/echoes-react'; | |||
import { LightPrimary, Title } from 'design-system'; | |||
import React, { useContext } from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { AvailableFeaturesContext } from '../../../../app/components/available-features/AvailableFeaturesContext'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { BitbucketCloudRepository } from '../../../../types/alm-integration'; | |||
@@ -86,7 +86,7 @@ export default function BitbucketCloudProjectCreateRenderer( | |||
<Link | |||
to={{ | |||
pathname: '/projects/create', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
mode: CreateProjectModes.BitbucketCloud, | |||
mono: true, | |||
}), |
@@ -20,7 +20,7 @@ | |||
import { FlagMessage, InputSearch, LightPrimary, Link } from 'design-system'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import ListFooter from '../../../../components/controls/ListFooter'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { getBaseUrl } from '../../../../helpers/system'; | |||
@@ -59,7 +59,10 @@ export default function BitbucketCloudSearchForm(props: BitbucketCloudSearchForm | |||
<Link | |||
to={{ | |||
pathname: '/projects/create', | |||
search: queryToSearch({ mode: CreateProjectModes.BitbucketCloud, resetPat: 1 }), | |||
search: queryToSearchString({ | |||
mode: CreateProjectModes.BitbucketCloud, | |||
resetPat: 1, | |||
}), | |||
}} | |||
> | |||
{translate('onboarding.create_project.update_your_token')} |
@@ -20,7 +20,7 @@ | |||
import { FlagMessage, InputSearch, Link } from 'design-system'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { | |||
BitbucketProject, | |||
@@ -55,7 +55,7 @@ export default function BitbucketImportRepositoryForm(props: BitbucketImportRepo | |||
<Link | |||
to={{ | |||
pathname: '/projects/create', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
mode: CreateProjectModes.BitbucketServer, | |||
resetPat: 1, | |||
}), |
@@ -20,7 +20,7 @@ | |||
import { Accordion, FlagMessage, Link } from 'design-system'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { translate, translateWithParameters } from '../../../../helpers/l10n'; | |||
import { getBaseUrl } from '../../../../helpers/system'; | |||
import { BitbucketProject, BitbucketRepository } from '../../../../types/alm-integration'; | |||
@@ -70,7 +70,7 @@ export default function BitbucketProjectAccordion(props: BitbucketProjectAccordi | |||
<Link | |||
to={{ | |||
pathname: '/projects/create', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
mode: CreateProjectModes.BitbucketServer, | |||
resetPat: 1, | |||
}), |
@@ -22,7 +22,7 @@ import { Link, Spinner } from '@sonarsource/echoes-react'; | |||
import { DarkLabel, FlagMessage, InputSelect, LightPrimary, Title } from 'design-system'; | |||
import React, { useContext, useEffect, useState } from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { useAppState } from '../../../../app/components/app-state/withAppStateContext'; | |||
import { AvailableFeaturesContext } from '../../../../app/components/available-features/AvailableFeaturesContext'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
@@ -123,7 +123,7 @@ export default function GitHubProjectCreateRenderer( | |||
<Link | |||
to={{ | |||
pathname: '/projects/create', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
mode: CreateProjectModes.GitHub, | |||
mono: true, | |||
}), |
@@ -21,7 +21,7 @@ import { Link, Spinner } from '@sonarsource/echoes-react'; | |||
import { LightPrimary, Title } from 'design-system'; | |||
import React, { useContext, useEffect, useState } from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { AvailableFeaturesContext } from '../../../../app/components/available-features/AvailableFeaturesContext'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { GitlabProject } from '../../../../types/alm-integration'; | |||
@@ -111,7 +111,7 @@ export default function GitlabProjectCreateRenderer( | |||
<Link | |||
to={{ | |||
pathname: '/projects/create', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
mode: CreateProjectModes.GitLab, | |||
mono: true, | |||
}), |
@@ -21,7 +21,7 @@ import { Link } from '@sonarsource/echoes-react'; | |||
import { FlagMessage, InputSearch, LightPrimary } from 'design-system'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import ListFooter from '../../../../components/controls/ListFooter'; | |||
import Tooltip from '../../../../components/controls/Tooltip'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
@@ -59,7 +59,7 @@ export default function GitlabProjectSelectionForm( | |||
<Link | |||
to={{ | |||
pathname: '/projects/create', | |||
search: queryToSearch({ mode: CreateProjectModes.GitLab, resetPat: 1 }), | |||
search: queryToSearchString({ mode: CreateProjectModes.GitLab, resetPat: 1 }), | |||
}} | |||
> | |||
{translate('onboarding.create_project.update_your_token')} |
@@ -35,7 +35,7 @@ import { useEffect } from 'react'; | |||
import { FormattedMessage, useIntl } from 'react-intl'; | |||
import { useNavigate, unstable_usePrompt as usePrompt } from 'react-router-dom'; | |||
import { useLocation } from '~sonar-aligned/components/hoc/withRouter'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import NewCodeDefinitionSelector from '../../../../components/new-code-definition/NewCodeDefinitionSelector'; | |||
import { useDocUrl } from '../../../../helpers/docs'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
@@ -90,7 +90,7 @@ export default function NewCodeDefinitionSelection(props: Props) { | |||
} else { | |||
navigate({ | |||
pathname: '/projects', | |||
search: queryToSearch({ sort: '-creation_date' }), | |||
search: queryToSearchString({ sort: '-creation_date' }), | |||
}); | |||
} | |||
}; |
@@ -20,7 +20,7 @@ | |||
import { Link } from 'design-system'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { ComponentQualifier } from '~sonar-aligned/types/component'; | |||
import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext'; | |||
import DismissableAlert from '../../../components/ui/DismissableAlert'; | |||
@@ -62,7 +62,7 @@ export function FirstAnalysisNextStepsNotif(props: FirstAnalysisNextStepsNotifPr | |||
<Link | |||
to={{ | |||
pathname: '/tutorials', | |||
search: queryToSearch({ id: component.key }), | |||
search: queryToSearchString({ id: component.key }), | |||
}} | |||
> | |||
{translate('overview.project.next_steps.links.set_up_ci')} | |||
@@ -72,7 +72,7 @@ export function FirstAnalysisNextStepsNotif(props: FirstAnalysisNextStepsNotifPr | |||
<Link | |||
to={{ | |||
pathname: '/project/settings', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
id: component.key, | |||
category: PULL_REQUEST_DECORATION_BINDING_CATEGORY, | |||
}), |
@@ -22,7 +22,7 @@ import { Note, getTabPanelId } from 'design-system'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { getBranchLikeQuery } from '~sonar-aligned/helpers/branch-like'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { ComponentQualifier } from '~sonar-aligned/types/component'; | |||
import DocumentationLink from '../../../components/common/DocumentationLink'; | |||
import { Image } from '../../../components/common/Image'; | |||
@@ -89,7 +89,10 @@ export default function MeasuresPanelNoNewCode(props: MeasuresPanelNoNewCodeProp | |||
<Link | |||
to={{ | |||
pathname: '/project/baseline', | |||
search: queryToSearch({ id: component.key, ...getBranchLikeQuery(branch) }), | |||
search: queryToSearchString({ | |||
id: component.key, | |||
...getBranchLikeQuery(branch), | |||
}), | |||
}} | |||
> | |||
{translate('settings.new_code_period.category')} |
@@ -90,7 +90,7 @@ export default class QualityGateCondition extends React.PureComponent<Props> { | |||
const metricKey = condition.measure.metric.key; | |||
const METRICS_TO_URL_MAPPING: Dict<() => Path> = { | |||
const METRICS_TO_URL_MAPPING: Dict<() => Partial<Path>> = { | |||
[MetricKey.reliability_rating]: () => | |||
this.getUrlForBugsOrVulnerabilities(IssueType.Bug, false), | |||
[MetricKey.new_reliability_rating]: () => |
@@ -31,7 +31,7 @@ interface IssueMeasuresCardInnerProps extends React.HTMLAttributes<HTMLDivElemen | |||
metric: MetricKey; | |||
value?: string; | |||
header: React.ReactNode; | |||
url: Path; | |||
url: Partial<Path>; | |||
failed?: boolean; | |||
icon?: React.ReactNode; | |||
disabled?: boolean; |
@@ -21,7 +21,7 @@ import { ActionsDropdown, ItemButton, ItemLink, PopupZLevel } from 'design-syste | |||
import { difference } from 'lodash'; | |||
import * as React from 'react'; | |||
import { withRouter } from '~sonar-aligned/components/hoc/withRouter'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { Router } from '~sonar-aligned/types/router'; | |||
import { | |||
deletePermissionTemplate, | |||
@@ -169,7 +169,10 @@ class ActionsCell extends React.PureComponent<Props, State> { | |||
{!this.props.fromDetails && ( | |||
<ItemLink | |||
to={{ pathname: PERMISSION_TEMPLATES_PATH, search: queryToSearch({ id: t.id }) }} | |||
to={{ | |||
pathname: PERMISSION_TEMPLATES_PATH, | |||
search: queryToSearchString({ id: t.id }), | |||
}} | |||
> | |||
{translate('edit_permissions')} | |||
</ItemLink> |
@@ -19,7 +19,7 @@ | |||
*/ | |||
import { CodeSnippet, ContentCell, Link } from 'design-system'; | |||
import * as React from 'react'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { PermissionTemplate } from '../../../types/types'; | |||
import { PERMISSION_TEMPLATES_PATH } from '../utils'; | |||
import Defaults from './Defaults'; | |||
@@ -35,7 +35,7 @@ export default function NameCell({ template }: Props) { | |||
<ContentCell> | |||
<div className="sw-flex sw-flex-col"> | |||
<span> | |||
<Link to={{ pathname, search: queryToSearch({ id: template.id }) }}> | |||
<Link to={{ pathname, search: queryToSearchString({ id: template.id }) }}> | |||
<span className="js-name">{template.name}</span> | |||
</Link> | |||
</span> |
@@ -20,7 +20,7 @@ | |||
import { FishVisual, Highlight, StandoutLink } from 'design-system'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { Dict } from '../../../types/types'; | |||
import { Query } from '../query'; | |||
@@ -41,7 +41,7 @@ export default function EmptyFavoriteSearch({ query }: { query: Query }) { | |||
<StandoutLink | |||
to={{ | |||
pathname: '/projects', | |||
search: queryToSearch(query as Dict<string | undefined | number>), | |||
search: queryToSearchString(query as Dict<string | undefined | number>), | |||
}} | |||
> | |||
{translate('all')} |
@@ -20,7 +20,7 @@ | |||
import { ItemLink } from 'design-system'; | |||
import * as React from 'react'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { Image } from '../../../components/common/Image'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { AlmKeys } from '../../../types/alm-settings'; | |||
@@ -38,7 +38,7 @@ export default function ProjectCreationMenuItem(props: ProjectCreationMenuItemPr | |||
return ( | |||
<ItemLink | |||
className="sw-flex sw-items-center" | |||
to={{ pathname: '/projects/create', search: queryToSearch({ mode: alm }) }} | |||
to={{ pathname: '/projects/create', search: queryToSearchString({ mode: alm }) }} | |||
> | |||
{alm !== 'manual' && ( | |||
<Image alt={alm} className="sw-mr-2" width={16} src={`/images/alm/${almIcon}.svg`} /> |
@@ -19,7 +19,7 @@ | |||
*/ | |||
import userEvent from '@testing-library/user-event'; | |||
import * as React from 'react'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { ComponentQualifier } from '~sonar-aligned/types/component'; | |||
import { createApplication } from '../../../../api/application'; | |||
import { getComponentNavigation } from '../../../../api/navigation'; | |||
@@ -109,7 +109,7 @@ it('should be able to create application when user is logged in and has permissi | |||
); | |||
expect(routerPush).toHaveBeenCalledWith({ | |||
pathname: '/project/admin/extension/developer-server/application-console', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
id: 'app', | |||
}), | |||
}); |
@@ -19,7 +19,7 @@ | |||
*/ | |||
import { differenceInYears } from 'date-fns'; | |||
import { sortBy } from 'lodash'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { Profile as BaseProfile } from '../../api/quality-profiles'; | |||
import { isValidDate, parseDate } from '../../helpers/dates'; | |||
import { PROFILE_COMPARE_PATH, PROFILE_PATH } from './constants'; | |||
@@ -68,12 +68,12 @@ export function isStagnant(profile: Profile): boolean { | |||
export const getProfilesForLanguagePath = (language: string) => ({ | |||
pathname: PROFILE_PATH, | |||
search: queryToSearch({ language }), | |||
search: queryToSearchString({ language }), | |||
}); | |||
export const getProfilePath = (name: string, language: string) => ({ | |||
pathname: `${PROFILE_PATH}/show`, | |||
search: queryToSearch({ name, language }), | |||
search: queryToSearchString({ name, language }), | |||
}); | |||
export const getProfileComparePath = (name: string, language: string, withKey?: string) => { | |||
@@ -83,7 +83,7 @@ export const getProfileComparePath = (name: string, language: string, withKey?: | |||
} | |||
return { | |||
pathname: PROFILE_COMPARE_PATH, | |||
search: queryToSearch(query), | |||
search: queryToSearchString(query), | |||
}; | |||
}; | |||
@@ -103,7 +103,7 @@ export const getProfileChangelogPath = ( | |||
} | |||
return { | |||
pathname: `${PROFILE_PATH}/changelog`, | |||
search: queryToSearch(query), | |||
search: queryToSearchString(query), | |||
}; | |||
}; | |||
@@ -19,7 +19,7 @@ | |||
*/ | |||
import { Badge, Card, LinkBox, LinkIcon, SubHeading, Tabs } from 'design-system'; | |||
import * as React from 'react'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { WebApi } from '../../../types/types'; | |||
import { getActionKey, serializeQuery } from '../utils'; | |||
@@ -71,7 +71,7 @@ export default function Action(props: Props) { | |||
<LinkBox | |||
to={{ | |||
pathname: '/web_api/' + actionKey, | |||
search: queryToSearch( | |||
search: queryToSearchString( | |||
serializeQuery({ | |||
deprecated: Boolean(action.deprecatedSince), | |||
internal: Boolean(action.internal), |
@@ -20,7 +20,7 @@ | |||
import { SubnavigationGroup, SubnavigationItem } from 'design-system'; | |||
import * as React from 'react'; | |||
import { useNavigate } from 'react-router-dom'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { WebApi } from '../../../types/types'; | |||
import { Query, actionsFilter, isDomainPathActive, serializeQuery } from '../utils'; | |||
import DeprecatedBadge from './DeprecatedBadge'; | |||
@@ -41,7 +41,7 @@ export default function Menu(props: Props) { | |||
(domainPath: string) => { | |||
navigateTo({ | |||
pathname: '/web_api/' + domainPath, | |||
search: queryToSearch(serializeQuery(query)), | |||
search: queryToSearchString(serializeQuery(query)), | |||
}); | |||
}, | |||
[query, navigateTo], |
@@ -20,7 +20,7 @@ | |||
import { Banner, Link } from 'design-system'; | |||
import React, { useCallback, useEffect, useMemo, useState } from 'react'; | |||
import { FormattedMessage, useIntl } from 'react-intl'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { MessageTypes, checkMessageDismissed, setMessageDismissed } from '../../api/messages'; | |||
import { CurrentUserContextInterface } from '../../app/components/current-user/CurrentUserContext'; | |||
import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext'; | |||
@@ -60,13 +60,13 @@ function NCDAutoUpdateMessage(props: Readonly<NCDAutoUpdateMessageProps>) { | |||
isGlobalBanner | |||
? { | |||
pathname: '/admin/settings', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
category: NEW_CODE_PERIOD_CATEGORY, | |||
}), | |||
} | |||
: { | |||
pathname: '/project/baseline', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
id: component.key, | |||
}), | |||
}, |
@@ -18,7 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { searchParamsToQuery } from '~sonar-aligned/helpers/router'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { ComponentQualifier } from '~sonar-aligned/types/component'; | |||
import { AlmKeys } from '../../types/alm-settings'; | |||
import { IssueType } from '../../types/issues'; | |||
@@ -93,7 +93,7 @@ describe('#getComponentOverviewUrl', () => { | |||
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Portfolio)).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/portfolio', | |||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY }), | |||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }), | |||
}), | |||
); | |||
}); | |||
@@ -101,7 +101,7 @@ describe('#getComponentOverviewUrl', () => { | |||
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.SubPortfolio)).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/portfolio', | |||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY }), | |||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }), | |||
}), | |||
); | |||
}); | |||
@@ -109,7 +109,7 @@ describe('#getComponentOverviewUrl', () => { | |||
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Project)).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/dashboard', | |||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY }), | |||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }), | |||
}), | |||
); | |||
}); | |||
@@ -124,7 +124,7 @@ describe('#getComponentOverviewUrl', () => { | |||
).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/dashboard', | |||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY, code_scope: 'new' }), | |||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY, code_scope: 'new' }), | |||
}), | |||
); | |||
}); | |||
@@ -139,7 +139,7 @@ describe('#getComponentOverviewUrl', () => { | |||
).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/dashboard', | |||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY, code_scope: 'overall' }), | |||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY, code_scope: 'overall' }), | |||
}), | |||
); | |||
}); | |||
@@ -147,7 +147,7 @@ describe('#getComponentOverviewUrl', () => { | |||
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Application)).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/dashboard', | |||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY }), | |||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }), | |||
}), | |||
); | |||
}); | |||
@@ -160,7 +160,7 @@ describe('#getComponentDrilldownUrl', () => { | |||
).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/component_measures', | |||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY, metric: METRIC }), | |||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY, metric: METRIC }), | |||
}), | |||
); | |||
}); | |||
@@ -171,7 +171,7 @@ describe('#getComponentDrilldownUrl', () => { | |||
).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/component_measures', | |||
search: queryToSearch({ id: COMPLEX_COMPONENT_KEY, metric: METRIC }), | |||
search: queryToSearchString({ id: COMPLEX_COMPONENT_KEY, metric: METRIC }), | |||
}), | |||
); | |||
}); | |||
@@ -182,7 +182,7 @@ describe('#getComponentDrilldownUrl', () => { | |||
).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/component_measures', | |||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY, metric: METRIC }), | |||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY, metric: METRIC }), | |||
}), | |||
); | |||
@@ -196,7 +196,7 @@ describe('#getComponentDrilldownUrl', () => { | |||
).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/component_measures', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
id: SIMPLE_COMPONENT_KEY, | |||
metric: METRIC, | |||
view: 'list', | |||
@@ -214,7 +214,7 @@ describe('#getComponentDrilldownUrlWithSelection', () => { | |||
).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/component_measures', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
id: SIMPLE_COMPONENT_KEY, | |||
metric: METRIC, | |||
selected: COMPLEX_COMPONENT_KEY, | |||
@@ -234,7 +234,7 @@ describe('#getComponentDrilldownUrlWithSelection', () => { | |||
).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/component_measures', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
id: SIMPLE_COMPONENT_KEY, | |||
metric: METRIC, | |||
branch: 'foo', | |||
@@ -256,7 +256,7 @@ describe('#getComponentDrilldownUrlWithSelection', () => { | |||
).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/component_measures', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
id: SIMPLE_COMPONENT_KEY, | |||
metric: METRIC, | |||
view: MeasurePageView.list, | |||
@@ -276,7 +276,7 @@ describe('#getComponentDrilldownUrlWithSelection', () => { | |||
).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/component_measures', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
id: SIMPLE_COMPONENT_KEY, | |||
metric: METRIC, | |||
view: MeasurePageView.treemap, | |||
@@ -296,7 +296,7 @@ describe('#getComponentDrilldownUrlWithSelection', () => { | |||
).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/component_measures', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
id: SIMPLE_COMPONENT_KEY, | |||
metric: METRIC, | |||
pullRequest: '1', | |||
@@ -334,7 +334,7 @@ describe('#getIssuesUrl', () => { | |||
const type = IssueType.Bug; | |||
expect(getIssuesUrl({ type })).toEqual({ | |||
pathname: '/issues', | |||
search: queryToSearch({ type }), | |||
search: queryToSearchString({ type }), | |||
}); | |||
}); | |||
}); | |||
@@ -343,11 +343,11 @@ describe('#getGlobalSettingsUrl', () => { | |||
it('should work as expected', () => { | |||
expect(getGlobalSettingsUrl('foo')).toEqual({ | |||
pathname: '/admin/settings', | |||
search: queryToSearch({ category: 'foo' }), | |||
search: queryToSearchString({ category: 'foo' }), | |||
}); | |||
expect(getGlobalSettingsUrl('foo', { alm: AlmKeys.GitHub })).toEqual({ | |||
pathname: '/admin/settings', | |||
search: queryToSearch({ category: 'foo', alm: AlmKeys.GitHub }), | |||
search: queryToSearchString({ category: 'foo', alm: AlmKeys.GitHub }), | |||
}); | |||
}); | |||
}); | |||
@@ -356,11 +356,11 @@ describe('#getProjectSettingsUrl', () => { | |||
it('should work as expected', () => { | |||
expect(getProjectSettingsUrl('foo')).toEqual({ | |||
pathname: '/project/settings', | |||
search: queryToSearch({ id: 'foo' }), | |||
search: queryToSearchString({ id: 'foo' }), | |||
}); | |||
expect(getProjectSettingsUrl('foo', 'bar')).toEqual({ | |||
pathname: '/project/settings', | |||
search: queryToSearch({ id: 'foo', category: 'bar' }), | |||
search: queryToSearchString({ id: 'foo', category: 'bar' }), | |||
}); | |||
}); | |||
}); | |||
@@ -370,7 +370,7 @@ describe('#getPathUrlAsString', () => { | |||
expect( | |||
getPathUrlAsString({ | |||
pathname: '/dashboard', | |||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY }), | |||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }), | |||
}), | |||
).toBe('/dashboard?id=' + SIMPLE_COMPONENT_KEY); | |||
}); | |||
@@ -379,7 +379,7 @@ describe('#getPathUrlAsString', () => { | |||
expect( | |||
getPathUrlAsString({ | |||
pathname: '/dashboard', | |||
search: queryToSearch({ id: COMPLEX_COMPONENT_KEY }), | |||
search: queryToSearchString({ id: COMPLEX_COMPONENT_KEY }), | |||
}), | |||
).toBe('/dashboard?id=' + COMPLEX_COMPONENT_KEY_ENCODED); | |||
}); | |||
@@ -435,27 +435,6 @@ describe('searchParamsToQuery', () => { | |||
}); | |||
}); | |||
describe('queryToSearch', () => { | |||
it('should handle all types', () => { | |||
const query = { | |||
author: ['GRRM', 'JKR', 'Stross'], | |||
b1: true, | |||
b2: false, | |||
emptyArray: [], | |||
normalString: 'hello', | |||
undef: undefined, | |||
}; | |||
expect(queryToSearch(query)).toBe( | |||
'?b1=true&b2=false&normalString=hello&author=GRRM&author=JKR&author=Stross', | |||
); | |||
}); | |||
it('should handle an missing query', () => { | |||
expect(queryToSearch()).toBe('?'); | |||
}); | |||
}); | |||
describe('convertToTo', () => { | |||
it('should handle locations with a query', () => { | |||
expect(convertToTo(mockLocation({ pathname: '/account', query: { id: 1 } }))).toEqual({ |
@@ -19,7 +19,7 @@ | |||
*/ | |||
import { Path, To } from 'react-router-dom'; | |||
import { getBranchLikeQuery, isBranch, isMainBranch } from '~sonar-aligned/helpers/branch-like'; | |||
import { queryToSearch } from '~sonar-aligned/helpers/urls'; | |||
import { queryToSearchString } from '~sonar-aligned/helpers/urls'; | |||
import { BranchParameters } from '~sonar-aligned/types/branch-like'; | |||
import { ComponentQualifier } from '~sonar-aligned/types/component'; | |||
import { getProfilePath } from '../apps/quality-profiles/utils'; | |||
@@ -81,14 +81,18 @@ export function getProjectUrl( | |||
): Partial<Path> { | |||
return { | |||
pathname: PROJECT_BASE_URL, | |||
search: queryToSearch({ id: project, branch, ...(codeScope && { code_scope: codeScope }) }), | |||
search: queryToSearchString({ | |||
id: project, | |||
branch, | |||
...(codeScope && { code_scope: codeScope }), | |||
}), | |||
}; | |||
} | |||
export function getProjectSecurityHotspots(project: string): To { | |||
return { | |||
pathname: '/security_hotspots', | |||
search: queryToSearch({ id: project }), | |||
search: queryToSearchString({ id: project }), | |||
}; | |||
} | |||
@@ -99,7 +103,7 @@ export function getProjectQueryUrl( | |||
): To { | |||
return { | |||
pathname: PROJECT_BASE_URL, | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
id: project, | |||
...branchParameters, | |||
...(codeScope && { code_scope: codeScope }), | |||
@@ -108,20 +112,20 @@ export function getProjectQueryUrl( | |||
} | |||
export function getPortfolioUrl(key: string): To { | |||
return { pathname: '/portfolio', search: queryToSearch({ id: key }) }; | |||
return { pathname: '/portfolio', search: queryToSearchString({ id: key }) }; | |||
} | |||
export function getPortfolioAdminUrl(key: string): To { | |||
return { | |||
pathname: '/project/admin/extension/governance/console', | |||
search: queryToSearch({ id: key, qualifier: ComponentQualifier.Portfolio }), | |||
search: queryToSearchString({ id: key, qualifier: ComponentQualifier.Portfolio }), | |||
}; | |||
} | |||
export function getApplicationAdminUrl(key: string): To { | |||
return { | |||
pathname: '/project/admin/extension/developer-server/application-console', | |||
search: queryToSearch({ id: key }), | |||
search: queryToSearchString({ id: key }), | |||
}; | |||
} | |||
@@ -129,10 +133,10 @@ export function getComponentBackgroundTaskUrl( | |||
componentKey: string, | |||
status?: string, | |||
taskType?: string, | |||
): Path { | |||
): Partial<Path> { | |||
return { | |||
pathname: '/project/background_tasks', | |||
search: queryToSearch({ id: componentKey, status, taskType }), | |||
search: queryToSearchString({ id: componentKey, status, taskType }), | |||
hash: '', | |||
}; | |||
} | |||
@@ -147,11 +151,11 @@ export function getBranchLikeUrl(project: string, branchLike?: BranchLike): Part | |||
} | |||
export function getBranchUrl(project: string, branch: string): Partial<Path> { | |||
return { pathname: PROJECT_BASE_URL, search: queryToSearch({ branch, id: project }) }; | |||
return { pathname: PROJECT_BASE_URL, search: queryToSearchString({ branch, id: project }) }; | |||
} | |||
export function getPullRequestUrl(project: string, pullRequest: string): Partial<Path> { | |||
return { pathname: PROJECT_BASE_URL, search: queryToSearch({ id: project, pullRequest }) }; | |||
return { pathname: PROJECT_BASE_URL, search: queryToSearchString({ id: project, pullRequest }) }; | |||
} | |||
/** | |||
@@ -159,7 +163,7 @@ export function getPullRequestUrl(project: string, pullRequest: string): Partial | |||
*/ | |||
export function getIssuesUrl(query: Query): To { | |||
const pathname = '/issues'; | |||
return { pathname, search: queryToSearch(query) }; | |||
return { pathname, search: queryToSearchString(query) }; | |||
} | |||
/** | |||
@@ -186,7 +190,7 @@ export function getComponentDrilldownUrl(options: { | |||
if (selectionKey) { | |||
query.selected = selectionKey; | |||
} | |||
return { pathname: '/component_measures', search: queryToSearch(query) }; | |||
return { pathname: '/component_measures', search: queryToSearchString(query) }; | |||
} | |||
export function getComponentDrilldownUrlWithSelection( | |||
@@ -213,7 +217,7 @@ export function getMeasureTreemapUrl(componentKey: string, metric: string) { | |||
export function getActivityUrl(component: string, branchLike?: BranchLike, graph?: GraphType) { | |||
return { | |||
pathname: '/project/activity', | |||
search: queryToSearch({ id: component, graph, ...getBranchLikeQuery(branchLike) }), | |||
search: queryToSearchString({ id: component, graph, ...getBranchLikeQuery(branchLike) }), | |||
}; | |||
} | |||
@@ -223,7 +227,7 @@ export function getActivityUrl(component: string, branchLike?: BranchLike, graph | |||
export function getMeasureHistoryUrl(component: string, metric: string, branchLike?: BranchLike) { | |||
return { | |||
pathname: '/project/activity', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
id: component, | |||
graph: 'custom', | |||
custom_metrics: metric, | |||
@@ -236,7 +240,7 @@ export function getMeasureHistoryUrl(component: string, metric: string, branchLi | |||
* Generate URL for a component's permissions page | |||
*/ | |||
export function getComponentPermissionsUrl(componentKey: string): To { | |||
return { pathname: '/project_roles', search: queryToSearch({ id: componentKey }) }; | |||
return { pathname: '/project_roles', search: queryToSearchString({ id: componentKey }) }; | |||
} | |||
/** | |||
@@ -263,7 +267,7 @@ export function getProjectTutorialLocation( | |||
): Partial<Path> { | |||
return { | |||
pathname: '/tutorials', | |||
search: queryToSearch({ id: project, selectedTutorial }), | |||
search: queryToSearchString({ id: project, selectedTutorial }), | |||
}; | |||
} | |||
@@ -272,7 +276,7 @@ export function getProjectTutorialLocation( | |||
*/ | |||
export function getCreateProjectModeLocation(mode?: string): Partial<Path> { | |||
return { | |||
search: queryToSearch({ mode }), | |||
search: queryToSearchString({ mode }), | |||
}; | |||
} | |||
@@ -288,14 +292,14 @@ export function getGlobalSettingsUrl( | |||
): Partial<Path> { | |||
return { | |||
pathname: '/admin/settings', | |||
search: queryToSearch({ category, ...query }), | |||
search: queryToSearchString({ category, ...query }), | |||
}; | |||
} | |||
export function getProjectSettingsUrl(id: string, category?: string): Partial<Path> { | |||
return { | |||
pathname: '/project/settings', | |||
search: queryToSearch({ id, category }), | |||
search: queryToSearchString({ id, category }), | |||
}; | |||
} | |||
@@ -303,7 +307,7 @@ export function getProjectSettingsUrl(id: string, category?: string): Partial<Pa | |||
* Generate URL for the rules page | |||
*/ | |||
export function getRulesUrl(query: Query): Partial<Path> { | |||
return { pathname: '/coding_rules', search: queryToSearch(query) }; | |||
return { pathname: '/coding_rules', search: queryToSearchString(query) }; | |||
} | |||
/** | |||
@@ -330,7 +334,7 @@ export function getCodeUrl( | |||
): Partial<Path> { | |||
return { | |||
pathname: '/code', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
id: project, | |||
...getBranchLikeQuery(branchLike), | |||
selected, | |||
@@ -398,7 +402,7 @@ export function isRelativeUrl(url?: string): boolean { | |||
export function convertToTo(link: string | Location) { | |||
if (linkIsLocation(link)) { | |||
return { pathname: link.pathname, search: queryToSearch(link.query) } as Partial<Path>; | |||
return { pathname: link.pathname, search: queryToSearchString(link.query) } as Partial<Path>; | |||
} | |||
return link; | |||
} |
@@ -27,7 +27,7 @@ import { | |||
useSearchParams, | |||
} from 'react-router-dom'; | |||
import { searchParamsToQuery } from '../../helpers/router'; | |||
import { queryToSearch } from '../../helpers/urls'; | |||
import { queryToSearchString } from '../../helpers/urls'; | |||
import { Location, Router } from '../../types/router'; | |||
import { getWrappedDisplayName } from './utils'; | |||
@@ -62,13 +62,13 @@ export function useRouter() { | |||
() => ({ | |||
replace: (path: string | Partial<Location>) => { | |||
if ((path as Location).query) { | |||
path.search = queryToSearch((path as Location).query); | |||
path.search = queryToSearchString((path as Location).query); | |||
} | |||
navigate(path, { replace: true }); | |||
}, | |||
push: (path: string | Partial<Location>) => { | |||
if ((path as Location).query) { | |||
path.search = queryToSearch((path as Location).query); | |||
path.search = queryToSearchString((path as Location).query); | |||
} | |||
navigate(path); | |||
}, |
@@ -19,25 +19,51 @@ | |||
*/ | |||
import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; | |||
import { SecurityStandard } from '../../../types/security'; | |||
import { getComponentIssuesUrl, getComponentSecurityHotspotsUrl, queryToSearch } from '../urls'; | |||
import { | |||
getComponentIssuesUrl, | |||
getComponentSecurityHotspotsUrl, | |||
queryToSearchString, | |||
} from '../urls'; | |||
const SIMPLE_COMPONENT_KEY = 'sonarqube'; | |||
describe('queryToSearch', () => { | |||
it('should return query by default', () => { | |||
expect(queryToSearch()).toBe('?'); | |||
describe('#queryToSearchString', () => { | |||
it('should handle query as array', () => { | |||
expect( | |||
queryToSearchString([ | |||
['key1', 'value1'], | |||
['key1', 'value2'], | |||
['key2', 'value1'], | |||
]), | |||
).toBe('?key1=value1&key1=value2&key2=value1'); | |||
}); | |||
it('should handle query as string', () => { | |||
expect(queryToSearchString('a=1')).toBe('?a=1'); | |||
}); | |||
it('should return query with string values', () => { | |||
expect(queryToSearch({ key: 'value' })).toBe('?key=value'); | |||
it('should handle query as URLSearchParams', () => { | |||
expect(queryToSearchString(new URLSearchParams({ a: '1', b: '2' }))).toBe('?a=1&b=2'); | |||
}); | |||
it('should remove empty values', () => { | |||
expect(queryToSearch({ key: 'value', anotherKey: '' })).toBe('?key=value'); | |||
it('should handle all types', () => { | |||
const query = { | |||
author: ['GRRM', 'JKR', 'Stross'], | |||
b1: true, | |||
b2: false, | |||
number: 0, | |||
emptyArray: [], | |||
normalString: 'hello', | |||
undef: undefined, | |||
}; | |||
expect(queryToSearchString(query)).toBe( | |||
'?author=GRRM&author=JKR&author=Stross&b1=true&b2=false&number=0&normalString=hello', | |||
); | |||
}); | |||
it('should return query with array values', () => { | |||
expect(queryToSearch({ key: ['value1', 'value2'] })).toBe('?key=value1&key=value2'); | |||
it('should handle an missing query', () => { | |||
expect(queryToSearchString()).toBeUndefined(); | |||
}); | |||
}); | |||
@@ -46,7 +72,7 @@ describe('#getComponentIssuesUrl', () => { | |||
expect(getComponentIssuesUrl(SIMPLE_COMPONENT_KEY)).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/project/issues', | |||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY }), | |||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }), | |||
}), | |||
); | |||
}); | |||
@@ -55,7 +81,7 @@ describe('#getComponentIssuesUrl', () => { | |||
expect(getComponentIssuesUrl(SIMPLE_COMPONENT_KEY, DEFAULT_ISSUES_QUERY)).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/project/issues', | |||
search: queryToSearch({ ...DEFAULT_ISSUES_QUERY, id: SIMPLE_COMPONENT_KEY }), | |||
search: queryToSearchString({ ...DEFAULT_ISSUES_QUERY, id: SIMPLE_COMPONENT_KEY }), | |||
}), | |||
); | |||
}); | |||
@@ -66,7 +92,7 @@ describe('#getComponentSecurityHotspotsUrl', () => { | |||
expect(getComponentSecurityHotspotsUrl(SIMPLE_COMPONENT_KEY)).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/security_hotspots', | |||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY }), | |||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }), | |||
}), | |||
); | |||
}); | |||
@@ -86,7 +112,7 @@ describe('#getComponentSecurityHotspotsUrl', () => { | |||
).toEqual( | |||
expect.objectContaining({ | |||
pathname: '/security_hotspots', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
id: SIMPLE_COMPONENT_KEY, | |||
inNewCodePeriod: 'true', | |||
[SecurityStandard.OWASP_TOP10_2021]: 'a1', |
@@ -17,50 +17,36 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { isArray, mapValues, omitBy, pick } from 'lodash'; | |||
import { Path } from 'react-router-dom'; | |||
import { mapValues, omitBy, pick } from 'lodash'; | |||
import { Path, URLSearchParamsInit, createSearchParams } from 'react-router-dom'; | |||
import { cleanQuery } from '../../helpers/query'; | |||
import { Query } from '../../helpers/urls'; | |||
import { BranchLike } from '../../types/branch-like'; | |||
import { SecurityStandard } from '../../types/security'; | |||
import { getBranchLikeQuery } from '../helpers/branch-like'; | |||
import { RawQuery } from '../types/router'; | |||
export function queryToSearch(query: RawQuery = {}) { | |||
const arrayParams: Array<{ key: string; values: string[] }> = []; | |||
export function queryToSearchString(query: RawQuery | URLSearchParamsInit = {}) { | |||
let filteredQuery = query; | |||
const stringParams = mapValues(query, (value, key) => { | |||
// array values are added afterwards | |||
if (isArray(value)) { | |||
arrayParams.push({ key, values: value }); | |||
return ''; | |||
} | |||
if (typeof query !== 'string' && !Array.isArray(query) && !(query instanceof URLSearchParams)) { | |||
filteredQuery = cleanQuery(query); | |||
mapValues(filteredQuery, (value) => (value as string).toString()); | |||
filteredQuery = omitBy(filteredQuery, (value) => value.length === 0); | |||
} | |||
return value != null ? `${value}` : ''; | |||
}); | |||
const filteredParams = omitBy(stringParams, (v: string) => v.length === 0); | |||
const searchParams = new URLSearchParams(filteredParams); | |||
const queryString = createSearchParams(filteredQuery as URLSearchParamsInit).toString(); | |||
/* | |||
* Add each value separately | |||
* e.g. author: ['a', 'b'] should be serialized as | |||
* author=a&author=b | |||
*/ | |||
arrayParams.forEach(({ key, values }) => { | |||
values.forEach((value) => { | |||
searchParams.append(key, value); | |||
}); | |||
}); | |||
return `?${searchParams.toString()}`; | |||
return queryString ? `?${queryString}` : undefined; | |||
} | |||
/** | |||
* Generate URL for a component's issues page | |||
*/ | |||
export function getComponentIssuesUrl(componentKey: string, query?: Query): Path { | |||
export function getComponentIssuesUrl(componentKey: string, query?: Query): Partial<Path> { | |||
return { | |||
pathname: '/project/issues', | |||
search: queryToSearch({ ...(query || {}), id: componentKey }), | |||
search: queryToSearchString({ ...(query || {}), id: componentKey }), | |||
hash: '', | |||
}; | |||
} | |||
@@ -72,11 +58,11 @@ export function getComponentSecurityHotspotsUrl( | |||
componentKey: string, | |||
branchLike?: BranchLike, | |||
query: Query = {}, | |||
): Path { | |||
): Partial<Path> { | |||
const { inNewCodePeriod, hotspots, assignedToMe, files } = query; | |||
return { | |||
pathname: '/security_hotspots', | |||
search: queryToSearch({ | |||
search: queryToSearchString({ | |||
id: componentKey, | |||
inNewCodePeriod, | |||
hotspots, |