} from 'design-system'; | } from 'design-system'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 DocumentationLink from '../../../components/common/DocumentationLink'; | ||||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | import { translate, translateWithParameters } from '../../../helpers/l10n'; | ||||
import { IndexationNotificationType } from '../../../types/indexation'; | import { IndexationNotificationType } from '../../../types/indexation'; | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/admin/background_tasks', | pathname: '/admin/background_tasks', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
taskType: TaskTypes.IssueSync, | taskType: TaskTypes.IssueSync, | ||||
status: hasError ? TaskStatuses.Failed : undefined, | status: hasError ? TaskStatuses.Failed : undefined, | ||||
}), | }), |
import * as React from 'react'; | import * as React from 'react'; | ||||
import { withRouter } from '~sonar-aligned/components/hoc/withRouter'; | import { withRouter } from '~sonar-aligned/components/hoc/withRouter'; | ||||
import { isBranch } from '~sonar-aligned/helpers/branch-like'; | 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 { ComponentQualifier } from '~sonar-aligned/types/component'; | ||||
import { Router } from '~sonar-aligned/types/router'; | import { Router } from '~sonar-aligned/types/router'; | ||||
import { | import { | ||||
onClick={() => { | onClick={() => { | ||||
onClose(); | onClose(); | ||||
}} | }} | ||||
to={{ pathname: '/project/branches', search: queryToSearch({ id: component.key }) }} | |||||
to={{ | |||||
pathname: '/project/branches', | |||||
search: queryToSearchString({ id: component.key }), | |||||
}} | |||||
> | > | ||||
{translate('branch_like_navigation.manage')} | {translate('branch_like_navigation.manage')} | ||||
</Link> | </Link> |
import * as React from 'react'; | import * as React from 'react'; | ||||
import { Helmet } from 'react-helmet-async'; | import { Helmet } from 'react-helmet-async'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 Suggestions from '../../../components/embed-docs-modal/Suggestions'; | ||||
import { now } from '../../../helpers/dates'; | import { now } from '../../../helpers/dates'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/admin/settings', | pathname: '/admin/settings', | ||||
search: queryToSearch({ category: 'housekeeping' }), | |||||
search: queryToSearchString({ category: 'housekeeping' }), | |||||
hash: '#auditLogs', | hash: '#auditLogs', | ||||
}} | }} | ||||
> | > |
import { Badge, BranchIcon, LightLabel, Note, QualifierIcon } from 'design-system'; | import { Badge, BranchIcon, LightLabel, Note, QualifierIcon } from 'design-system'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { getBranchLikeQuery } from '~sonar-aligned/helpers/branch-like'; | 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 { ComponentQualifier } from '~sonar-aligned/types/component'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { isDefined } from '../../../helpers/types'; | import { isDefined } from '../../../helpers/types'; | ||||
<LinkStandalone | <LinkStandalone | ||||
highlight={LinkHighlight.CurrentColor} | highlight={LinkHighlight.CurrentColor} | ||||
iconLeft={showIcon && <QualifierIcon className="sw-mr-2" qualifier={component.qualifier} />} | iconLeft={showIcon && <QualifierIcon className="sw-mr-2" qualifier={component.qualifier} />} | ||||
to={{ pathname: '/code', search: queryToSearch(query) }} | |||||
to={{ pathname: '/code', search: queryToSearchString(query) }} | |||||
> | > | ||||
{name} | {name} | ||||
</LinkStandalone> | </LinkStandalone> |
import { Accordion, FlagMessage, Link, SearchHighlighter, Spinner } from 'design-system'; | import { Accordion, FlagMessage, Link, SearchHighlighter, Spinner } from 'design-system'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 ListFooter from '../../../../components/controls/ListFooter'; | ||||
import { translate } from '../../../../helpers/l10n'; | import { translate } from '../../../../helpers/l10n'; | ||||
import { getBaseUrl } from '../../../../helpers/system'; | import { getBaseUrl } from '../../../../helpers/system'; | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/projects/create', | pathname: '/projects/create', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
mode: CreateProjectModes.AzureDevOps, | mode: CreateProjectModes.AzureDevOps, | ||||
resetPat: 1, | resetPat: 1, | ||||
}), | }), |
} from 'design-system'; | } from 'design-system'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 { useAppState } from '../../../../app/components/app-state/withAppStateContext'; | ||||
import { AvailableFeaturesContext } from '../../../../app/components/available-features/AvailableFeaturesContext'; | import { AvailableFeaturesContext } from '../../../../app/components/available-features/AvailableFeaturesContext'; | ||||
import { translate } from '../../../../helpers/l10n'; | import { translate } from '../../../../helpers/l10n'; | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/projects/create', | pathname: '/projects/create', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
mode: CreateProjectModes.AzureDevOps, | mode: CreateProjectModes.AzureDevOps, | ||||
mono: true, | mono: true, | ||||
}), | }), |
import { uniqBy } from 'lodash'; | import { uniqBy } from 'lodash'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 ListFooter from '../../../../components/controls/ListFooter'; | ||||
import { translate, translateWithParameters } from '../../../../helpers/l10n'; | import { translate, translateWithParameters } from '../../../../helpers/l10n'; | ||||
import { AzureProject, AzureRepository } from '../../../../types/alm-integration'; | import { AzureProject, AzureRepository } from '../../../../types/alm-integration'; | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/projects/create', | pathname: '/projects/create', | ||||
search: queryToSearch({ mode: CreateProjectModes.AzureDevOps, resetPat: 1 }), | |||||
search: queryToSearchString({ | |||||
mode: CreateProjectModes.AzureDevOps, | |||||
resetPat: 1, | |||||
}), | |||||
}} | }} | ||||
> | > | ||||
{translate('onboarding.create_project.update_your_token')} | {translate('onboarding.create_project.update_your_token')} |
import { LightPrimary, Title } from 'design-system'; | import { LightPrimary, Title } from 'design-system'; | ||||
import React, { useContext } from 'react'; | import React, { useContext } from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 { AvailableFeaturesContext } from '../../../../app/components/available-features/AvailableFeaturesContext'; | ||||
import { translate } from '../../../../helpers/l10n'; | import { translate } from '../../../../helpers/l10n'; | ||||
import { BitbucketCloudRepository } from '../../../../types/alm-integration'; | import { BitbucketCloudRepository } from '../../../../types/alm-integration'; | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/projects/create', | pathname: '/projects/create', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
mode: CreateProjectModes.BitbucketCloud, | mode: CreateProjectModes.BitbucketCloud, | ||||
mono: true, | mono: true, | ||||
}), | }), |
import { FlagMessage, InputSearch, LightPrimary, Link } from 'design-system'; | import { FlagMessage, InputSearch, LightPrimary, Link } from 'design-system'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 ListFooter from '../../../../components/controls/ListFooter'; | ||||
import { translate } from '../../../../helpers/l10n'; | import { translate } from '../../../../helpers/l10n'; | ||||
import { getBaseUrl } from '../../../../helpers/system'; | import { getBaseUrl } from '../../../../helpers/system'; | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/projects/create', | pathname: '/projects/create', | ||||
search: queryToSearch({ mode: CreateProjectModes.BitbucketCloud, resetPat: 1 }), | |||||
search: queryToSearchString({ | |||||
mode: CreateProjectModes.BitbucketCloud, | |||||
resetPat: 1, | |||||
}), | |||||
}} | }} | ||||
> | > | ||||
{translate('onboarding.create_project.update_your_token')} | {translate('onboarding.create_project.update_your_token')} |
import { FlagMessage, InputSearch, Link } from 'design-system'; | import { FlagMessage, InputSearch, Link } from 'design-system'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 { translate } from '../../../../helpers/l10n'; | ||||
import { | import { | ||||
BitbucketProject, | BitbucketProject, | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/projects/create', | pathname: '/projects/create', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
mode: CreateProjectModes.BitbucketServer, | mode: CreateProjectModes.BitbucketServer, | ||||
resetPat: 1, | resetPat: 1, | ||||
}), | }), |
import { Accordion, FlagMessage, Link } from 'design-system'; | import { Accordion, FlagMessage, Link } from 'design-system'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 { translate, translateWithParameters } from '../../../../helpers/l10n'; | ||||
import { getBaseUrl } from '../../../../helpers/system'; | import { getBaseUrl } from '../../../../helpers/system'; | ||||
import { BitbucketProject, BitbucketRepository } from '../../../../types/alm-integration'; | import { BitbucketProject, BitbucketRepository } from '../../../../types/alm-integration'; | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/projects/create', | pathname: '/projects/create', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
mode: CreateProjectModes.BitbucketServer, | mode: CreateProjectModes.BitbucketServer, | ||||
resetPat: 1, | resetPat: 1, | ||||
}), | }), |
import { DarkLabel, FlagMessage, InputSelect, LightPrimary, Title } from 'design-system'; | import { DarkLabel, FlagMessage, InputSelect, LightPrimary, Title } from 'design-system'; | ||||
import React, { useContext, useEffect, useState } from 'react'; | import React, { useContext, useEffect, useState } from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 { useAppState } from '../../../../app/components/app-state/withAppStateContext'; | ||||
import { AvailableFeaturesContext } from '../../../../app/components/available-features/AvailableFeaturesContext'; | import { AvailableFeaturesContext } from '../../../../app/components/available-features/AvailableFeaturesContext'; | ||||
import { translate } from '../../../../helpers/l10n'; | import { translate } from '../../../../helpers/l10n'; | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/projects/create', | pathname: '/projects/create', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
mode: CreateProjectModes.GitHub, | mode: CreateProjectModes.GitHub, | ||||
mono: true, | mono: true, | ||||
}), | }), |
import { LightPrimary, Title } from 'design-system'; | import { LightPrimary, Title } from 'design-system'; | ||||
import React, { useContext, useEffect, useState } from 'react'; | import React, { useContext, useEffect, useState } from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 { AvailableFeaturesContext } from '../../../../app/components/available-features/AvailableFeaturesContext'; | ||||
import { translate } from '../../../../helpers/l10n'; | import { translate } from '../../../../helpers/l10n'; | ||||
import { GitlabProject } from '../../../../types/alm-integration'; | import { GitlabProject } from '../../../../types/alm-integration'; | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/projects/create', | pathname: '/projects/create', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
mode: CreateProjectModes.GitLab, | mode: CreateProjectModes.GitLab, | ||||
mono: true, | mono: true, | ||||
}), | }), |
import { FlagMessage, InputSearch, LightPrimary } from 'design-system'; | import { FlagMessage, InputSearch, LightPrimary } from 'design-system'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 ListFooter from '../../../../components/controls/ListFooter'; | ||||
import Tooltip from '../../../../components/controls/Tooltip'; | import Tooltip from '../../../../components/controls/Tooltip'; | ||||
import { translate } from '../../../../helpers/l10n'; | import { translate } from '../../../../helpers/l10n'; | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/projects/create', | pathname: '/projects/create', | ||||
search: queryToSearch({ mode: CreateProjectModes.GitLab, resetPat: 1 }), | |||||
search: queryToSearchString({ mode: CreateProjectModes.GitLab, resetPat: 1 }), | |||||
}} | }} | ||||
> | > | ||||
{translate('onboarding.create_project.update_your_token')} | {translate('onboarding.create_project.update_your_token')} |
import { FormattedMessage, useIntl } from 'react-intl'; | import { FormattedMessage, useIntl } from 'react-intl'; | ||||
import { useNavigate, unstable_usePrompt as usePrompt } from 'react-router-dom'; | import { useNavigate, unstable_usePrompt as usePrompt } from 'react-router-dom'; | ||||
import { useLocation } from '~sonar-aligned/components/hoc/withRouter'; | 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 NewCodeDefinitionSelector from '../../../../components/new-code-definition/NewCodeDefinitionSelector'; | ||||
import { useDocUrl } from '../../../../helpers/docs'; | import { useDocUrl } from '../../../../helpers/docs'; | ||||
import { translate } from '../../../../helpers/l10n'; | import { translate } from '../../../../helpers/l10n'; | ||||
} else { | } else { | ||||
navigate({ | navigate({ | ||||
pathname: '/projects', | pathname: '/projects', | ||||
search: queryToSearch({ sort: '-creation_date' }), | |||||
search: queryToSearchString({ sort: '-creation_date' }), | |||||
}); | }); | ||||
} | } | ||||
}; | }; |
import { Link } from 'design-system'; | import { Link } from 'design-system'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 { ComponentQualifier } from '~sonar-aligned/types/component'; | ||||
import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext'; | import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext'; | ||||
import DismissableAlert from '../../../components/ui/DismissableAlert'; | import DismissableAlert from '../../../components/ui/DismissableAlert'; | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/tutorials', | pathname: '/tutorials', | ||||
search: queryToSearch({ id: component.key }), | |||||
search: queryToSearchString({ id: component.key }), | |||||
}} | }} | ||||
> | > | ||||
{translate('overview.project.next_steps.links.set_up_ci')} | {translate('overview.project.next_steps.links.set_up_ci')} | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/project/settings', | pathname: '/project/settings', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
id: component.key, | id: component.key, | ||||
category: PULL_REQUEST_DECORATION_BINDING_CATEGORY, | category: PULL_REQUEST_DECORATION_BINDING_CATEGORY, | ||||
}), | }), |
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | import { FormattedMessage } from 'react-intl'; | ||||
import { getBranchLikeQuery } from '~sonar-aligned/helpers/branch-like'; | 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 { ComponentQualifier } from '~sonar-aligned/types/component'; | ||||
import DocumentationLink from '../../../components/common/DocumentationLink'; | import DocumentationLink from '../../../components/common/DocumentationLink'; | ||||
import { Image } from '../../../components/common/Image'; | import { Image } from '../../../components/common/Image'; | ||||
<Link | <Link | ||||
to={{ | to={{ | ||||
pathname: '/project/baseline', | pathname: '/project/baseline', | ||||
search: queryToSearch({ id: component.key, ...getBranchLikeQuery(branch) }), | |||||
search: queryToSearchString({ | |||||
id: component.key, | |||||
...getBranchLikeQuery(branch), | |||||
}), | |||||
}} | }} | ||||
> | > | ||||
{translate('settings.new_code_period.category')} | {translate('settings.new_code_period.category')} |
const metricKey = condition.measure.metric.key; | const metricKey = condition.measure.metric.key; | ||||
const METRICS_TO_URL_MAPPING: Dict<() => Path> = { | |||||
const METRICS_TO_URL_MAPPING: Dict<() => Partial<Path>> = { | |||||
[MetricKey.reliability_rating]: () => | [MetricKey.reliability_rating]: () => | ||||
this.getUrlForBugsOrVulnerabilities(IssueType.Bug, false), | this.getUrlForBugsOrVulnerabilities(IssueType.Bug, false), | ||||
[MetricKey.new_reliability_rating]: () => | [MetricKey.new_reliability_rating]: () => |
metric: MetricKey; | metric: MetricKey; | ||||
value?: string; | value?: string; | ||||
header: React.ReactNode; | header: React.ReactNode; | ||||
url: Path; | |||||
url: Partial<Path>; | |||||
failed?: boolean; | failed?: boolean; | ||||
icon?: React.ReactNode; | icon?: React.ReactNode; | ||||
disabled?: boolean; | disabled?: boolean; |
import { difference } from 'lodash'; | import { difference } from 'lodash'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { withRouter } from '~sonar-aligned/components/hoc/withRouter'; | 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 { Router } from '~sonar-aligned/types/router'; | ||||
import { | import { | ||||
deletePermissionTemplate, | deletePermissionTemplate, | ||||
{!this.props.fromDetails && ( | {!this.props.fromDetails && ( | ||||
<ItemLink | <ItemLink | ||||
to={{ pathname: PERMISSION_TEMPLATES_PATH, search: queryToSearch({ id: t.id }) }} | |||||
to={{ | |||||
pathname: PERMISSION_TEMPLATES_PATH, | |||||
search: queryToSearchString({ id: t.id }), | |||||
}} | |||||
> | > | ||||
{translate('edit_permissions')} | {translate('edit_permissions')} | ||||
</ItemLink> | </ItemLink> |
*/ | */ | ||||
import { CodeSnippet, ContentCell, Link } from 'design-system'; | import { CodeSnippet, ContentCell, Link } from 'design-system'; | ||||
import * as React from 'react'; | 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 { PermissionTemplate } from '../../../types/types'; | ||||
import { PERMISSION_TEMPLATES_PATH } from '../utils'; | import { PERMISSION_TEMPLATES_PATH } from '../utils'; | ||||
import Defaults from './Defaults'; | import Defaults from './Defaults'; | ||||
<ContentCell> | <ContentCell> | ||||
<div className="sw-flex sw-flex-col"> | <div className="sw-flex sw-flex-col"> | ||||
<span> | <span> | ||||
<Link to={{ pathname, search: queryToSearch({ id: template.id }) }}> | |||||
<Link to={{ pathname, search: queryToSearchString({ id: template.id }) }}> | |||||
<span className="js-name">{template.name}</span> | <span className="js-name">{template.name}</span> | ||||
</Link> | </Link> | ||||
</span> | </span> |
import { FishVisual, Highlight, StandoutLink } from 'design-system'; | import { FishVisual, Highlight, StandoutLink } from 'design-system'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 { translate } from '../../../helpers/l10n'; | ||||
import { Dict } from '../../../types/types'; | import { Dict } from '../../../types/types'; | ||||
import { Query } from '../query'; | import { Query } from '../query'; | ||||
<StandoutLink | <StandoutLink | ||||
to={{ | to={{ | ||||
pathname: '/projects', | pathname: '/projects', | ||||
search: queryToSearch(query as Dict<string | undefined | number>), | |||||
search: queryToSearchString(query as Dict<string | undefined | number>), | |||||
}} | }} | ||||
> | > | ||||
{translate('all')} | {translate('all')} |
import { ItemLink } from 'design-system'; | import { ItemLink } from 'design-system'; | ||||
import * as React from 'react'; | 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 { Image } from '../../../components/common/Image'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { AlmKeys } from '../../../types/alm-settings'; | import { AlmKeys } from '../../../types/alm-settings'; | ||||
return ( | return ( | ||||
<ItemLink | <ItemLink | ||||
className="sw-flex sw-items-center" | className="sw-flex sw-items-center" | ||||
to={{ pathname: '/projects/create', search: queryToSearch({ mode: alm }) }} | |||||
to={{ pathname: '/projects/create', search: queryToSearchString({ mode: alm }) }} | |||||
> | > | ||||
{alm !== 'manual' && ( | {alm !== 'manual' && ( | ||||
<Image alt={alm} className="sw-mr-2" width={16} src={`/images/alm/${almIcon}.svg`} /> | <Image alt={alm} className="sw-mr-2" width={16} src={`/images/alm/${almIcon}.svg`} /> |
*/ | */ | ||||
import userEvent from '@testing-library/user-event'; | import userEvent from '@testing-library/user-event'; | ||||
import * as React from 'react'; | 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 { ComponentQualifier } from '~sonar-aligned/types/component'; | ||||
import { createApplication } from '../../../../api/application'; | import { createApplication } from '../../../../api/application'; | ||||
import { getComponentNavigation } from '../../../../api/navigation'; | import { getComponentNavigation } from '../../../../api/navigation'; | ||||
); | ); | ||||
expect(routerPush).toHaveBeenCalledWith({ | expect(routerPush).toHaveBeenCalledWith({ | ||||
pathname: '/project/admin/extension/developer-server/application-console', | pathname: '/project/admin/extension/developer-server/application-console', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
id: 'app', | id: 'app', | ||||
}), | }), | ||||
}); | }); |
*/ | */ | ||||
import { differenceInYears } from 'date-fns'; | import { differenceInYears } from 'date-fns'; | ||||
import { sortBy } from 'lodash'; | 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 { Profile as BaseProfile } from '../../api/quality-profiles'; | ||||
import { isValidDate, parseDate } from '../../helpers/dates'; | import { isValidDate, parseDate } from '../../helpers/dates'; | ||||
import { PROFILE_COMPARE_PATH, PROFILE_PATH } from './constants'; | import { PROFILE_COMPARE_PATH, PROFILE_PATH } from './constants'; | ||||
export const getProfilesForLanguagePath = (language: string) => ({ | export const getProfilesForLanguagePath = (language: string) => ({ | ||||
pathname: PROFILE_PATH, | pathname: PROFILE_PATH, | ||||
search: queryToSearch({ language }), | |||||
search: queryToSearchString({ language }), | |||||
}); | }); | ||||
export const getProfilePath = (name: string, language: string) => ({ | export const getProfilePath = (name: string, language: string) => ({ | ||||
pathname: `${PROFILE_PATH}/show`, | pathname: `${PROFILE_PATH}/show`, | ||||
search: queryToSearch({ name, language }), | |||||
search: queryToSearchString({ name, language }), | |||||
}); | }); | ||||
export const getProfileComparePath = (name: string, language: string, withKey?: string) => { | export const getProfileComparePath = (name: string, language: string, withKey?: string) => { | ||||
} | } | ||||
return { | return { | ||||
pathname: PROFILE_COMPARE_PATH, | pathname: PROFILE_COMPARE_PATH, | ||||
search: queryToSearch(query), | |||||
search: queryToSearchString(query), | |||||
}; | }; | ||||
}; | }; | ||||
} | } | ||||
return { | return { | ||||
pathname: `${PROFILE_PATH}/changelog`, | pathname: `${PROFILE_PATH}/changelog`, | ||||
search: queryToSearch(query), | |||||
search: queryToSearchString(query), | |||||
}; | }; | ||||
}; | }; | ||||
*/ | */ | ||||
import { Badge, Card, LinkBox, LinkIcon, SubHeading, Tabs } from 'design-system'; | import { Badge, Card, LinkBox, LinkIcon, SubHeading, Tabs } from 'design-system'; | ||||
import * as React from 'react'; | 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 { translate, translateWithParameters } from '../../../helpers/l10n'; | ||||
import { WebApi } from '../../../types/types'; | import { WebApi } from '../../../types/types'; | ||||
import { getActionKey, serializeQuery } from '../utils'; | import { getActionKey, serializeQuery } from '../utils'; | ||||
<LinkBox | <LinkBox | ||||
to={{ | to={{ | ||||
pathname: '/web_api/' + actionKey, | pathname: '/web_api/' + actionKey, | ||||
search: queryToSearch( | |||||
search: queryToSearchString( | |||||
serializeQuery({ | serializeQuery({ | ||||
deprecated: Boolean(action.deprecatedSince), | deprecated: Boolean(action.deprecatedSince), | ||||
internal: Boolean(action.internal), | internal: Boolean(action.internal), |
import { SubnavigationGroup, SubnavigationItem } from 'design-system'; | import { SubnavigationGroup, SubnavigationItem } from 'design-system'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { useNavigate } from 'react-router-dom'; | 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 { WebApi } from '../../../types/types'; | ||||
import { Query, actionsFilter, isDomainPathActive, serializeQuery } from '../utils'; | import { Query, actionsFilter, isDomainPathActive, serializeQuery } from '../utils'; | ||||
import DeprecatedBadge from './DeprecatedBadge'; | import DeprecatedBadge from './DeprecatedBadge'; | ||||
(domainPath: string) => { | (domainPath: string) => { | ||||
navigateTo({ | navigateTo({ | ||||
pathname: '/web_api/' + domainPath, | pathname: '/web_api/' + domainPath, | ||||
search: queryToSearch(serializeQuery(query)), | |||||
search: queryToSearchString(serializeQuery(query)), | |||||
}); | }); | ||||
}, | }, | ||||
[query, navigateTo], | [query, navigateTo], |
import { Banner, Link } from 'design-system'; | import { Banner, Link } from 'design-system'; | ||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'; | import React, { useCallback, useEffect, useMemo, useState } from 'react'; | ||||
import { FormattedMessage, useIntl } from 'react-intl'; | 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 { MessageTypes, checkMessageDismissed, setMessageDismissed } from '../../api/messages'; | ||||
import { CurrentUserContextInterface } from '../../app/components/current-user/CurrentUserContext'; | import { CurrentUserContextInterface } from '../../app/components/current-user/CurrentUserContext'; | ||||
import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext'; | import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext'; | ||||
isGlobalBanner | isGlobalBanner | ||||
? { | ? { | ||||
pathname: '/admin/settings', | pathname: '/admin/settings', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
category: NEW_CODE_PERIOD_CATEGORY, | category: NEW_CODE_PERIOD_CATEGORY, | ||||
}), | }), | ||||
} | } | ||||
: { | : { | ||||
pathname: '/project/baseline', | pathname: '/project/baseline', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
id: component.key, | id: component.key, | ||||
}), | }), | ||||
}, | }, |
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import { searchParamsToQuery } from '~sonar-aligned/helpers/router'; | 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 { ComponentQualifier } from '~sonar-aligned/types/component'; | ||||
import { AlmKeys } from '../../types/alm-settings'; | import { AlmKeys } from '../../types/alm-settings'; | ||||
import { IssueType } from '../../types/issues'; | import { IssueType } from '../../types/issues'; | ||||
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Portfolio)).toEqual( | expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Portfolio)).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/portfolio', | pathname: '/portfolio', | ||||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY }), | |||||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }), | |||||
}), | }), | ||||
); | ); | ||||
}); | }); | ||||
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.SubPortfolio)).toEqual( | expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.SubPortfolio)).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/portfolio', | pathname: '/portfolio', | ||||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY }), | |||||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }), | |||||
}), | }), | ||||
); | ); | ||||
}); | }); | ||||
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Project)).toEqual( | expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Project)).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/dashboard', | pathname: '/dashboard', | ||||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY }), | |||||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }), | |||||
}), | }), | ||||
); | ); | ||||
}); | }); | ||||
).toEqual( | ).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/dashboard', | pathname: '/dashboard', | ||||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY, code_scope: 'new' }), | |||||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY, code_scope: 'new' }), | |||||
}), | }), | ||||
); | ); | ||||
}); | }); | ||||
).toEqual( | ).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/dashboard', | pathname: '/dashboard', | ||||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY, code_scope: 'overall' }), | |||||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY, code_scope: 'overall' }), | |||||
}), | }), | ||||
); | ); | ||||
}); | }); | ||||
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Application)).toEqual( | expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Application)).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/dashboard', | pathname: '/dashboard', | ||||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY }), | |||||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }), | |||||
}), | }), | ||||
); | ); | ||||
}); | }); | ||||
).toEqual( | ).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/component_measures', | pathname: '/component_measures', | ||||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY, metric: METRIC }), | |||||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY, metric: METRIC }), | |||||
}), | }), | ||||
); | ); | ||||
}); | }); | ||||
).toEqual( | ).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/component_measures', | pathname: '/component_measures', | ||||
search: queryToSearch({ id: COMPLEX_COMPONENT_KEY, metric: METRIC }), | |||||
search: queryToSearchString({ id: COMPLEX_COMPONENT_KEY, metric: METRIC }), | |||||
}), | }), | ||||
); | ); | ||||
}); | }); | ||||
).toEqual( | ).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/component_measures', | pathname: '/component_measures', | ||||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY, metric: METRIC }), | |||||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY, metric: METRIC }), | |||||
}), | }), | ||||
); | ); | ||||
).toEqual( | ).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/component_measures', | pathname: '/component_measures', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
id: SIMPLE_COMPONENT_KEY, | id: SIMPLE_COMPONENT_KEY, | ||||
metric: METRIC, | metric: METRIC, | ||||
view: 'list', | view: 'list', | ||||
).toEqual( | ).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/component_measures', | pathname: '/component_measures', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
id: SIMPLE_COMPONENT_KEY, | id: SIMPLE_COMPONENT_KEY, | ||||
metric: METRIC, | metric: METRIC, | ||||
selected: COMPLEX_COMPONENT_KEY, | selected: COMPLEX_COMPONENT_KEY, | ||||
).toEqual( | ).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/component_measures', | pathname: '/component_measures', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
id: SIMPLE_COMPONENT_KEY, | id: SIMPLE_COMPONENT_KEY, | ||||
metric: METRIC, | metric: METRIC, | ||||
branch: 'foo', | branch: 'foo', | ||||
).toEqual( | ).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/component_measures', | pathname: '/component_measures', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
id: SIMPLE_COMPONENT_KEY, | id: SIMPLE_COMPONENT_KEY, | ||||
metric: METRIC, | metric: METRIC, | ||||
view: MeasurePageView.list, | view: MeasurePageView.list, | ||||
).toEqual( | ).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/component_measures', | pathname: '/component_measures', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
id: SIMPLE_COMPONENT_KEY, | id: SIMPLE_COMPONENT_KEY, | ||||
metric: METRIC, | metric: METRIC, | ||||
view: MeasurePageView.treemap, | view: MeasurePageView.treemap, | ||||
).toEqual( | ).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/component_measures', | pathname: '/component_measures', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
id: SIMPLE_COMPONENT_KEY, | id: SIMPLE_COMPONENT_KEY, | ||||
metric: METRIC, | metric: METRIC, | ||||
pullRequest: '1', | pullRequest: '1', | ||||
const type = IssueType.Bug; | const type = IssueType.Bug; | ||||
expect(getIssuesUrl({ type })).toEqual({ | expect(getIssuesUrl({ type })).toEqual({ | ||||
pathname: '/issues', | pathname: '/issues', | ||||
search: queryToSearch({ type }), | |||||
search: queryToSearchString({ type }), | |||||
}); | }); | ||||
}); | }); | ||||
}); | }); | ||||
it('should work as expected', () => { | it('should work as expected', () => { | ||||
expect(getGlobalSettingsUrl('foo')).toEqual({ | expect(getGlobalSettingsUrl('foo')).toEqual({ | ||||
pathname: '/admin/settings', | pathname: '/admin/settings', | ||||
search: queryToSearch({ category: 'foo' }), | |||||
search: queryToSearchString({ category: 'foo' }), | |||||
}); | }); | ||||
expect(getGlobalSettingsUrl('foo', { alm: AlmKeys.GitHub })).toEqual({ | expect(getGlobalSettingsUrl('foo', { alm: AlmKeys.GitHub })).toEqual({ | ||||
pathname: '/admin/settings', | pathname: '/admin/settings', | ||||
search: queryToSearch({ category: 'foo', alm: AlmKeys.GitHub }), | |||||
search: queryToSearchString({ category: 'foo', alm: AlmKeys.GitHub }), | |||||
}); | }); | ||||
}); | }); | ||||
}); | }); | ||||
it('should work as expected', () => { | it('should work as expected', () => { | ||||
expect(getProjectSettingsUrl('foo')).toEqual({ | expect(getProjectSettingsUrl('foo')).toEqual({ | ||||
pathname: '/project/settings', | pathname: '/project/settings', | ||||
search: queryToSearch({ id: 'foo' }), | |||||
search: queryToSearchString({ id: 'foo' }), | |||||
}); | }); | ||||
expect(getProjectSettingsUrl('foo', 'bar')).toEqual({ | expect(getProjectSettingsUrl('foo', 'bar')).toEqual({ | ||||
pathname: '/project/settings', | pathname: '/project/settings', | ||||
search: queryToSearch({ id: 'foo', category: 'bar' }), | |||||
search: queryToSearchString({ id: 'foo', category: 'bar' }), | |||||
}); | }); | ||||
}); | }); | ||||
}); | }); | ||||
expect( | expect( | ||||
getPathUrlAsString({ | getPathUrlAsString({ | ||||
pathname: '/dashboard', | pathname: '/dashboard', | ||||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY }), | |||||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }), | |||||
}), | }), | ||||
).toBe('/dashboard?id=' + SIMPLE_COMPONENT_KEY); | ).toBe('/dashboard?id=' + SIMPLE_COMPONENT_KEY); | ||||
}); | }); | ||||
expect( | expect( | ||||
getPathUrlAsString({ | getPathUrlAsString({ | ||||
pathname: '/dashboard', | pathname: '/dashboard', | ||||
search: queryToSearch({ id: COMPLEX_COMPONENT_KEY }), | |||||
search: queryToSearchString({ id: COMPLEX_COMPONENT_KEY }), | |||||
}), | }), | ||||
).toBe('/dashboard?id=' + COMPLEX_COMPONENT_KEY_ENCODED); | ).toBe('/dashboard?id=' + COMPLEX_COMPONENT_KEY_ENCODED); | ||||
}); | }); | ||||
}); | }); | ||||
}); | }); | ||||
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', () => { | describe('convertToTo', () => { | ||||
it('should handle locations with a query', () => { | it('should handle locations with a query', () => { | ||||
expect(convertToTo(mockLocation({ pathname: '/account', query: { id: 1 } }))).toEqual({ | expect(convertToTo(mockLocation({ pathname: '/account', query: { id: 1 } }))).toEqual({ |
*/ | */ | ||||
import { Path, To } from 'react-router-dom'; | import { Path, To } from 'react-router-dom'; | ||||
import { getBranchLikeQuery, isBranch, isMainBranch } from '~sonar-aligned/helpers/branch-like'; | 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 { BranchParameters } from '~sonar-aligned/types/branch-like'; | ||||
import { ComponentQualifier } from '~sonar-aligned/types/component'; | import { ComponentQualifier } from '~sonar-aligned/types/component'; | ||||
import { getProfilePath } from '../apps/quality-profiles/utils'; | import { getProfilePath } from '../apps/quality-profiles/utils'; | ||||
): Partial<Path> { | ): Partial<Path> { | ||||
return { | return { | ||||
pathname: PROJECT_BASE_URL, | 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 { | export function getProjectSecurityHotspots(project: string): To { | ||||
return { | return { | ||||
pathname: '/security_hotspots', | pathname: '/security_hotspots', | ||||
search: queryToSearch({ id: project }), | |||||
search: queryToSearchString({ id: project }), | |||||
}; | }; | ||||
} | } | ||||
): To { | ): To { | ||||
return { | return { | ||||
pathname: PROJECT_BASE_URL, | pathname: PROJECT_BASE_URL, | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
id: project, | id: project, | ||||
...branchParameters, | ...branchParameters, | ||||
...(codeScope && { code_scope: codeScope }), | ...(codeScope && { code_scope: codeScope }), | ||||
} | } | ||||
export function getPortfolioUrl(key: string): To { | 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 { | export function getPortfolioAdminUrl(key: string): To { | ||||
return { | return { | ||||
pathname: '/project/admin/extension/governance/console', | 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 { | export function getApplicationAdminUrl(key: string): To { | ||||
return { | return { | ||||
pathname: '/project/admin/extension/developer-server/application-console', | pathname: '/project/admin/extension/developer-server/application-console', | ||||
search: queryToSearch({ id: key }), | |||||
search: queryToSearchString({ id: key }), | |||||
}; | }; | ||||
} | } | ||||
componentKey: string, | componentKey: string, | ||||
status?: string, | status?: string, | ||||
taskType?: string, | taskType?: string, | ||||
): Path { | |||||
): Partial<Path> { | |||||
return { | return { | ||||
pathname: '/project/background_tasks', | pathname: '/project/background_tasks', | ||||
search: queryToSearch({ id: componentKey, status, taskType }), | |||||
search: queryToSearchString({ id: componentKey, status, taskType }), | |||||
hash: '', | hash: '', | ||||
}; | }; | ||||
} | } | ||||
} | } | ||||
export function getBranchUrl(project: string, branch: string): Partial<Path> { | 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> { | 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 }) }; | |||||
} | } | ||||
/** | /** | ||||
*/ | */ | ||||
export function getIssuesUrl(query: Query): To { | export function getIssuesUrl(query: Query): To { | ||||
const pathname = '/issues'; | const pathname = '/issues'; | ||||
return { pathname, search: queryToSearch(query) }; | |||||
return { pathname, search: queryToSearchString(query) }; | |||||
} | } | ||||
/** | /** | ||||
if (selectionKey) { | if (selectionKey) { | ||||
query.selected = selectionKey; | query.selected = selectionKey; | ||||
} | } | ||||
return { pathname: '/component_measures', search: queryToSearch(query) }; | |||||
return { pathname: '/component_measures', search: queryToSearchString(query) }; | |||||
} | } | ||||
export function getComponentDrilldownUrlWithSelection( | export function getComponentDrilldownUrlWithSelection( | ||||
export function getActivityUrl(component: string, branchLike?: BranchLike, graph?: GraphType) { | export function getActivityUrl(component: string, branchLike?: BranchLike, graph?: GraphType) { | ||||
return { | return { | ||||
pathname: '/project/activity', | pathname: '/project/activity', | ||||
search: queryToSearch({ id: component, graph, ...getBranchLikeQuery(branchLike) }), | |||||
search: queryToSearchString({ id: component, graph, ...getBranchLikeQuery(branchLike) }), | |||||
}; | }; | ||||
} | } | ||||
export function getMeasureHistoryUrl(component: string, metric: string, branchLike?: BranchLike) { | export function getMeasureHistoryUrl(component: string, metric: string, branchLike?: BranchLike) { | ||||
return { | return { | ||||
pathname: '/project/activity', | pathname: '/project/activity', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
id: component, | id: component, | ||||
graph: 'custom', | graph: 'custom', | ||||
custom_metrics: metric, | custom_metrics: metric, | ||||
* Generate URL for a component's permissions page | * Generate URL for a component's permissions page | ||||
*/ | */ | ||||
export function getComponentPermissionsUrl(componentKey: string): To { | export function getComponentPermissionsUrl(componentKey: string): To { | ||||
return { pathname: '/project_roles', search: queryToSearch({ id: componentKey }) }; | |||||
return { pathname: '/project_roles', search: queryToSearchString({ id: componentKey }) }; | |||||
} | } | ||||
/** | /** | ||||
): Partial<Path> { | ): Partial<Path> { | ||||
return { | return { | ||||
pathname: '/tutorials', | pathname: '/tutorials', | ||||
search: queryToSearch({ id: project, selectedTutorial }), | |||||
search: queryToSearchString({ id: project, selectedTutorial }), | |||||
}; | }; | ||||
} | } | ||||
*/ | */ | ||||
export function getCreateProjectModeLocation(mode?: string): Partial<Path> { | export function getCreateProjectModeLocation(mode?: string): Partial<Path> { | ||||
return { | return { | ||||
search: queryToSearch({ mode }), | |||||
search: queryToSearchString({ mode }), | |||||
}; | }; | ||||
} | } | ||||
): Partial<Path> { | ): Partial<Path> { | ||||
return { | return { | ||||
pathname: '/admin/settings', | pathname: '/admin/settings', | ||||
search: queryToSearch({ category, ...query }), | |||||
search: queryToSearchString({ category, ...query }), | |||||
}; | }; | ||||
} | } | ||||
export function getProjectSettingsUrl(id: string, category?: string): Partial<Path> { | export function getProjectSettingsUrl(id: string, category?: string): Partial<Path> { | ||||
return { | return { | ||||
pathname: '/project/settings', | pathname: '/project/settings', | ||||
search: queryToSearch({ id, category }), | |||||
search: queryToSearchString({ id, category }), | |||||
}; | }; | ||||
} | } | ||||
* Generate URL for the rules page | * Generate URL for the rules page | ||||
*/ | */ | ||||
export function getRulesUrl(query: Query): Partial<Path> { | export function getRulesUrl(query: Query): Partial<Path> { | ||||
return { pathname: '/coding_rules', search: queryToSearch(query) }; | |||||
return { pathname: '/coding_rules', search: queryToSearchString(query) }; | |||||
} | } | ||||
/** | /** | ||||
): Partial<Path> { | ): Partial<Path> { | ||||
return { | return { | ||||
pathname: '/code', | pathname: '/code', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
id: project, | id: project, | ||||
...getBranchLikeQuery(branchLike), | ...getBranchLikeQuery(branchLike), | ||||
selected, | selected, | ||||
export function convertToTo(link: string | Location) { | export function convertToTo(link: string | Location) { | ||||
if (linkIsLocation(link)) { | 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; | return link; | ||||
} | } |
useSearchParams, | useSearchParams, | ||||
} from 'react-router-dom'; | } from 'react-router-dom'; | ||||
import { searchParamsToQuery } from '../../helpers/router'; | import { searchParamsToQuery } from '../../helpers/router'; | ||||
import { queryToSearch } from '../../helpers/urls'; | |||||
import { queryToSearchString } from '../../helpers/urls'; | |||||
import { Location, Router } from '../../types/router'; | import { Location, Router } from '../../types/router'; | ||||
import { getWrappedDisplayName } from './utils'; | import { getWrappedDisplayName } from './utils'; | ||||
() => ({ | () => ({ | ||||
replace: (path: string | Partial<Location>) => { | replace: (path: string | Partial<Location>) => { | ||||
if ((path as Location).query) { | if ((path as Location).query) { | ||||
path.search = queryToSearch((path as Location).query); | |||||
path.search = queryToSearchString((path as Location).query); | |||||
} | } | ||||
navigate(path, { replace: true }); | navigate(path, { replace: true }); | ||||
}, | }, | ||||
push: (path: string | Partial<Location>) => { | push: (path: string | Partial<Location>) => { | ||||
if ((path as Location).query) { | if ((path as Location).query) { | ||||
path.search = queryToSearch((path as Location).query); | |||||
path.search = queryToSearchString((path as Location).query); | |||||
} | } | ||||
navigate(path); | navigate(path); | ||||
}, | }, |
*/ | */ | ||||
import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; | import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; | ||||
import { SecurityStandard } from '../../../types/security'; | import { SecurityStandard } from '../../../types/security'; | ||||
import { getComponentIssuesUrl, getComponentSecurityHotspotsUrl, queryToSearch } from '../urls'; | |||||
import { | |||||
getComponentIssuesUrl, | |||||
getComponentSecurityHotspotsUrl, | |||||
queryToSearchString, | |||||
} from '../urls'; | |||||
const SIMPLE_COMPONENT_KEY = 'sonarqube'; | 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(); | |||||
}); | }); | ||||
}); | }); | ||||
expect(getComponentIssuesUrl(SIMPLE_COMPONENT_KEY)).toEqual( | expect(getComponentIssuesUrl(SIMPLE_COMPONENT_KEY)).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/project/issues', | pathname: '/project/issues', | ||||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY }), | |||||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }), | |||||
}), | }), | ||||
); | ); | ||||
}); | }); | ||||
expect(getComponentIssuesUrl(SIMPLE_COMPONENT_KEY, DEFAULT_ISSUES_QUERY)).toEqual( | expect(getComponentIssuesUrl(SIMPLE_COMPONENT_KEY, DEFAULT_ISSUES_QUERY)).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/project/issues', | pathname: '/project/issues', | ||||
search: queryToSearch({ ...DEFAULT_ISSUES_QUERY, id: SIMPLE_COMPONENT_KEY }), | |||||
search: queryToSearchString({ ...DEFAULT_ISSUES_QUERY, id: SIMPLE_COMPONENT_KEY }), | |||||
}), | }), | ||||
); | ); | ||||
}); | }); | ||||
expect(getComponentSecurityHotspotsUrl(SIMPLE_COMPONENT_KEY)).toEqual( | expect(getComponentSecurityHotspotsUrl(SIMPLE_COMPONENT_KEY)).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/security_hotspots', | pathname: '/security_hotspots', | ||||
search: queryToSearch({ id: SIMPLE_COMPONENT_KEY }), | |||||
search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }), | |||||
}), | }), | ||||
); | ); | ||||
}); | }); | ||||
).toEqual( | ).toEqual( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
pathname: '/security_hotspots', | pathname: '/security_hotspots', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
id: SIMPLE_COMPONENT_KEY, | id: SIMPLE_COMPONENT_KEY, | ||||
inNewCodePeriod: 'true', | inNewCodePeriod: 'true', | ||||
[SecurityStandard.OWASP_TOP10_2021]: 'a1', | [SecurityStandard.OWASP_TOP10_2021]: 'a1', |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * 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 { Query } from '../../helpers/urls'; | ||||
import { BranchLike } from '../../types/branch-like'; | import { BranchLike } from '../../types/branch-like'; | ||||
import { SecurityStandard } from '../../types/security'; | import { SecurityStandard } from '../../types/security'; | ||||
import { getBranchLikeQuery } from '../helpers/branch-like'; | import { getBranchLikeQuery } from '../helpers/branch-like'; | ||||
import { RawQuery } from '../types/router'; | 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 | * 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 { | return { | ||||
pathname: '/project/issues', | pathname: '/project/issues', | ||||
search: queryToSearch({ ...(query || {}), id: componentKey }), | |||||
search: queryToSearchString({ ...(query || {}), id: componentKey }), | |||||
hash: '', | hash: '', | ||||
}; | }; | ||||
} | } | ||||
componentKey: string, | componentKey: string, | ||||
branchLike?: BranchLike, | branchLike?: BranchLike, | ||||
query: Query = {}, | query: Query = {}, | ||||
): Path { | |||||
): Partial<Path> { | |||||
const { inNewCodePeriod, hotspots, assignedToMe, files } = query; | const { inNewCodePeriod, hotspots, assignedToMe, files } = query; | ||||
return { | return { | ||||
pathname: '/security_hotspots', | pathname: '/security_hotspots', | ||||
search: queryToSearch({ | |||||
search: queryToSearchString({ | |||||
id: componentKey, | id: componentKey, | ||||
inNewCodePeriod, | inNewCodePeriod, | ||||
hotspots, | hotspots, |