Просмотр исходного кода

SONAR-22049 Align queryToSearch method

master
stanislavh 2 недель назад
Родитель
Сommit
639bde5900
33 измененных файлов: 183 добавлений и 173 удалений
  1. 2
    2
      server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx
  2. 5
    2
      server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx
  3. 2
    2
      server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx
  4. 2
    2
      server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx
  5. 2
    2
      server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectAccordion.tsx
  6. 2
    2
      server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectCreateRenderer.tsx
  7. 5
    2
      server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectsList.tsx
  8. 2
    2
      server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreateRender.tsx
  9. 5
    2
      server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudSearchForm.tsx
  10. 2
    2
      server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketImportRepositoryForm.tsx
  11. 2
    2
      server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketProjectAccordion.tsx
  12. 2
    2
      server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx
  13. 2
    2
      server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreateRenderer.tsx
  14. 2
    2
      server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectSelectionForm.tsx
  15. 2
    2
      server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx
  16. 3
    3
      server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx
  17. 5
    2
      server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx
  18. 1
    1
      server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx
  19. 1
    1
      server/sonar-web/src/main/js/apps/overview/components/IssueMeasuresCardInner.tsx
  20. 5
    2
      server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx
  21. 2
    2
      server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.tsx
  22. 2
    2
      server/sonar-web/src/main/js/apps/projects/components/EmptyFavoriteSearch.tsx
  23. 2
    2
      server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx
  24. 2
    2
      server/sonar-web/src/main/js/apps/projects/components/__tests__/CreateApplication-test.tsx
  25. 5
    5
      server/sonar-web/src/main/js/apps/quality-profiles/utils.ts
  26. 2
    2
      server/sonar-web/src/main/js/apps/web-api/components/Action.tsx
  27. 2
    2
      server/sonar-web/src/main/js/apps/web-api/components/Menu.tsx
  28. 3
    3
      server/sonar-web/src/main/js/components/new-code-definition/NCDAutoUpdateMessage.tsx
  29. 23
    44
      server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts
  30. 27
    23
      server/sonar-web/src/main/js/helpers/urls.ts
  31. 3
    3
      server/sonar-web/src/main/js/sonar-aligned/components/hoc/withRouter.tsx
  32. 40
    14
      server/sonar-web/src/main/js/sonar-aligned/helpers/__tests__/urls-test.ts
  33. 16
    30
      server/sonar-web/src/main/js/sonar-aligned/helpers/urls.ts

+ 2
- 2
server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx Просмотреть файл

@@ -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,
}),

+ 5
- 2
server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx Просмотреть файл

@@ -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>

+ 2
- 2
server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx Просмотреть файл

@@ -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',
}}
>

+ 2
- 2
server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx Просмотреть файл

@@ -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>

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectAccordion.tsx Просмотреть файл

@@ -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,
}),

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectCreateRenderer.tsx Просмотреть файл

@@ -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,
}),

+ 5
- 2
server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectsList.tsx Просмотреть файл

@@ -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')}

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreateRender.tsx Просмотреть файл

@@ -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,
}),

+ 5
- 2
server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudSearchForm.tsx Просмотреть файл

@@ -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')}

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketImportRepositoryForm.tsx Просмотреть файл

@@ -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,
}),

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketProjectAccordion.tsx Просмотреть файл

@@ -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,
}),

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx Просмотреть файл

@@ -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,
}),

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreateRenderer.tsx Просмотреть файл

@@ -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,
}),

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectSelectionForm.tsx Просмотреть файл

@@ -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')}

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx Просмотреть файл

@@ -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' }),
});
}
};

+ 3
- 3
server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx Просмотреть файл

@@ -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,
}),

+ 5
- 2
server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx Просмотреть файл

@@ -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')}

+ 1
- 1
server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx Просмотреть файл

@@ -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]: () =>

+ 1
- 1
server/sonar-web/src/main/js/apps/overview/components/IssueMeasuresCardInner.tsx Просмотреть файл

@@ -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;

+ 5
- 2
server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx Просмотреть файл

@@ -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>

+ 2
- 2
server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.tsx Просмотреть файл

@@ -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>

+ 2
- 2
server/sonar-web/src/main/js/apps/projects/components/EmptyFavoriteSearch.tsx Просмотреть файл

@@ -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')}

+ 2
- 2
server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx Просмотреть файл

@@ -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`} />

+ 2
- 2
server/sonar-web/src/main/js/apps/projects/components/__tests__/CreateApplication-test.tsx Просмотреть файл

@@ -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',
}),
});

+ 5
- 5
server/sonar-web/src/main/js/apps/quality-profiles/utils.ts Просмотреть файл

@@ -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),
};
};


+ 2
- 2
server/sonar-web/src/main/js/apps/web-api/components/Action.tsx Просмотреть файл

@@ -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),

+ 2
- 2
server/sonar-web/src/main/js/apps/web-api/components/Menu.tsx Просмотреть файл

@@ -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],

+ 3
- 3
server/sonar-web/src/main/js/components/new-code-definition/NCDAutoUpdateMessage.tsx Просмотреть файл

@@ -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,
}),
},

+ 23
- 44
server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts Просмотреть файл

@@ -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({

+ 27
- 23
server/sonar-web/src/main/js/helpers/urls.ts Просмотреть файл

@@ -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;
}

+ 3
- 3
server/sonar-web/src/main/js/sonar-aligned/components/hoc/withRouter.tsx Просмотреть файл

@@ -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);
},

+ 40
- 14
server/sonar-web/src/main/js/sonar-aligned/helpers/__tests__/urls-test.ts Просмотреть файл

@@ -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',

+ 16
- 30
server/sonar-web/src/main/js/sonar-aligned/helpers/urls.ts Просмотреть файл

@@ -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,

Загрузка…
Отмена
Сохранить