isMainBranch, | isMainBranch, | ||||
isPullRequest | isPullRequest | ||||
} from '../../helpers/branch-like'; | } from '../../helpers/branch-like'; | ||||
import { isSonarCloud } from '../../helpers/system'; | |||||
import { getPortfolioUrl } from '../../helpers/urls'; | |||||
import { | import { | ||||
fetchOrganization, | fetchOrganization, | ||||
registerBranchStatus, | registerBranchStatus, | ||||
requireAuthorization | requireAuthorization | ||||
} from '../../store/rootActions'; | } from '../../store/rootActions'; | ||||
import { BranchLike } from '../../types/branch-like'; | import { BranchLike } from '../../types/branch-like'; | ||||
import { isPortfolioLike } from '../../types/component'; | |||||
import ComponentContainerNotFound from './ComponentContainerNotFound'; | import ComponentContainerNotFound from './ComponentContainerNotFound'; | ||||
import { ComponentContext } from './ComponentContext'; | import { ComponentContext } from './ComponentContext'; | ||||
import ComponentNav from './nav/component/ComponentNav'; | import ComponentNav from './nav/component/ComponentNav'; | ||||
interface Props { | interface Props { | ||||
children: React.ReactElement; | children: React.ReactElement; | ||||
fetchOrganization: (organization: string) => void; | fetchOrganization: (organization: string) => void; | ||||
location: Pick<Location, 'query'>; | |||||
location: Pick<Location, 'query' | 'pathname'>; | |||||
registerBranchStatus: (branchLike: BranchLike, component: string, status: T.Status) => void; | registerBranchStatus: (branchLike: BranchLike, component: string, status: T.Status) => void; | ||||
requireAuthorization: (router: Pick<Router, 'replace'>) => void; | requireAuthorization: (router: Pick<Router, 'replace'>) => void; | ||||
router: Pick<Router, 'replace'>; | router: Pick<Router, 'replace'>; | ||||
.then(([nav, { component }]) => { | .then(([nav, { component }]) => { | ||||
const componentWithQualifier = this.addQualifier({ ...nav, ...component }); | const componentWithQualifier = this.addQualifier({ ...nav, ...component }); | ||||
if (isSonarCloud()) { | |||||
this.props.fetchOrganization(componentWithQualifier.organization); | |||||
/* | |||||
* There used to be a redirect from /dashboard to /portfolio which caused issues. | |||||
* Links should be fixed to not rely on this redirect, but: | |||||
* This is a fail-safe in case there are still some faulty links remaining. | |||||
*/ | |||||
if ( | |||||
this.props.location.pathname.match('dashboard') && | |||||
isPortfolioLike(componentWithQualifier.qualifier) | |||||
) { | |||||
this.props.router.replace(getPortfolioUrl(component.key)); | |||||
} | } | ||||
return componentWithQualifier; | return componentWithQualifier; | ||||
}, onError) | }, onError) | ||||
.then(this.fetchBranches) | .then(this.fetchBranches) |
import { getComponentNavigation } from '../../../api/nav'; | import { getComponentNavigation } from '../../../api/nav'; | ||||
import { STATUSES } from '../../../apps/background-tasks/constants'; | import { STATUSES } from '../../../apps/background-tasks/constants'; | ||||
import { mockBranch, mockMainBranch, mockPullRequest } from '../../../helpers/mocks/branch-like'; | import { mockBranch, mockMainBranch, mockPullRequest } from '../../../helpers/mocks/branch-like'; | ||||
import { isSonarCloud } from '../../../helpers/system'; | |||||
import { mockComponent, mockLocation, mockRouter } from '../../../helpers/testMocks'; | import { mockComponent, mockLocation, mockRouter } from '../../../helpers/testMocks'; | ||||
import { ComponentQualifier } from '../../../types/component'; | |||||
import { ComponentContainer } from '../ComponentContainer'; | import { ComponentContainer } from '../ComponentContainer'; | ||||
jest.mock('../../../api/branches', () => { | jest.mock('../../../api/branches', () => { | ||||
}) | }) | ||||
})); | })); | ||||
jest.mock('../../../helpers/system', () => ({ | |||||
isSonarCloud: jest.fn().mockReturnValue(false) | |||||
})); | |||||
// mock this, because some of its children are using redux store | // mock this, because some of its children are using redux store | ||||
jest.mock('../nav/component/ComponentNav', () => ({ | jest.mock('../nav/component/ComponentNav', () => ({ | ||||
default: () => null | default: () => null | ||||
expect(registerBranchStatus).toBeCalledTimes(2); | expect(registerBranchStatus).toBeCalledTimes(2); | ||||
}); | }); | ||||
it('loads organization', async () => { | |||||
(isSonarCloud as jest.Mock).mockReturnValue(true); | |||||
(getComponentData as jest.Mock<any>).mockResolvedValueOnce({ | |||||
component: { organization: 'org' } | |||||
}); | |||||
const fetchOrganization = jest.fn(); | |||||
shallowRender({ fetchOrganization }); | |||||
await new Promise(setImmediate); | |||||
expect(fetchOrganization).toBeCalledWith('org'); | |||||
}); | |||||
it('fetches status', async () => { | it('fetches status', async () => { | ||||
(getComponentData as jest.Mock<any>).mockResolvedValueOnce({ | (getComponentData as jest.Mock<any>).mockResolvedValueOnce({ | ||||
component: { organization: 'org' } | component: { organization: 'org' } | ||||
}); | }); | ||||
it('should show component not found if it does not exist', async () => { | it('should show component not found if it does not exist', async () => { | ||||
(getComponentNavigation as jest.Mock).mockRejectedValue({ status: 404 }); | |||||
(getComponentNavigation as jest.Mock).mockRejectedValueOnce({ status: 404 }); | |||||
const wrapper = shallowRender(); | const wrapper = shallowRender(); | ||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
}); | }); | ||||
it('should redirect if the user has no access', async () => { | it('should redirect if the user has no access', async () => { | ||||
(getComponentNavigation as jest.Mock).mockRejectedValue({ status: 403 }); | |||||
(getComponentNavigation as jest.Mock).mockRejectedValueOnce({ status: 403 }); | |||||
const requireAuthorization = jest.fn(); | const requireAuthorization = jest.fn(); | ||||
const wrapper = shallowRender({ requireAuthorization }); | const wrapper = shallowRender({ requireAuthorization }); | ||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); | ||||
expect(requireAuthorization).toBeCalled(); | expect(requireAuthorization).toBeCalled(); | ||||
}); | }); | ||||
it('should redirect if the component is a portfolio', async () => { | |||||
const componentKey = 'comp-key'; | |||||
(getComponentData as jest.Mock<any>).mockResolvedValueOnce({ | |||||
component: { key: componentKey, breadcrumbs: [{ qualifier: ComponentQualifier.Portfolio }] } | |||||
}); | |||||
const replace = jest.fn(); | |||||
const wrapper = shallowRender({ | |||||
location: mockLocation({ pathname: '/dashboard' }), | |||||
router: mockRouter({ replace }) | |||||
}); | |||||
await waitAndUpdate(wrapper); | |||||
expect(replace).toBeCalledWith({ pathname: '/portfolio', query: { id: componentKey } }); | |||||
}); | |||||
function shallowRender(props: Partial<ComponentContainer['props']> = {}) { | function shallowRender(props: Partial<ComponentContainer['props']> = {}) { | ||||
return shallow<ComponentContainer>( | return shallow<ComponentContainer>( | ||||
<ComponentContainer | <ComponentContainer |
import { Link } from 'react-router'; | import { Link } from 'react-router'; | ||||
import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; | import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; | ||||
import { isMainBranch } from '../../../../helpers/branch-like'; | import { isMainBranch } from '../../../../helpers/branch-like'; | ||||
import { getProjectUrl } from '../../../../helpers/urls'; | |||||
import { getComponentOverviewUrl } from '../../../../helpers/urls'; | |||||
import { BranchLike } from '../../../../types/branch-like'; | import { BranchLike } from '../../../../types/branch-like'; | ||||
interface Props { | interface Props { | ||||
<Link | <Link | ||||
className="link-no-underline text-ellipsis" | className="link-no-underline text-ellipsis" | ||||
title={breadcrumbElement.name} | title={breadcrumbElement.name} | ||||
to={getProjectUrl(breadcrumbElement.key)}> | |||||
to={getComponentOverviewUrl(breadcrumbElement.key, breadcrumbElement.qualifier)}> | |||||
{breadcrumbElement.name} | {breadcrumbElement.name} | ||||
</Link> | </Link> | ||||
) : ( | ) : ( |
import { withAppState } from '../../../../components/hoc/withAppState'; | import { withAppState } from '../../../../components/hoc/withAppState'; | ||||
import { getBranchLikeQuery, isMainBranch, isPullRequest } from '../../../../helpers/branch-like'; | import { getBranchLikeQuery, isMainBranch, isPullRequest } from '../../../../helpers/branch-like'; | ||||
import { isSonarCloud } from '../../../../helpers/system'; | import { isSonarCloud } from '../../../../helpers/system'; | ||||
import { getPortfolioUrl, getProjectQueryUrl } from '../../../../helpers/urls'; | |||||
import { BranchLike, BranchParameters } from '../../../../types/branch-like'; | import { BranchLike, BranchParameters } from '../../../../types/branch-like'; | ||||
import { ComponentQualifier } from '../../../../types/component'; | |||||
import { ComponentQualifier, isPortfolioLike } from '../../../../types/component'; | |||||
import './Menu.css'; | import './Menu.css'; | ||||
const SETTINGS_URLS = [ | const SETTINGS_URLS = [ | ||||
isPortfolio = () => { | isPortfolio = () => { | ||||
const { qualifier } = this.props.component; | const { qualifier } = this.props.component; | ||||
return ( | |||||
qualifier === ComponentQualifier.Portfolio || qualifier === ComponentQualifier.SubPortfolio | |||||
); | |||||
return isPortfolioLike(qualifier); | |||||
}; | }; | ||||
isApplication = () => { | isApplication = () => { | ||||
return { id: this.props.component.key, ...getBranchLikeQuery(this.props.branchLike) }; | return { id: this.props.component.key, ...getBranchLikeQuery(this.props.branchLike) }; | ||||
}; | }; | ||||
renderDashboardLink = (query: Query, isPortfolio: boolean) => { | |||||
const pathname = isPortfolio ? '/portfolio' : '/dashboard'; | |||||
renderDashboardLink = ({ id, ...branchLike }: Query, isPortfolio: boolean) => { | |||||
return ( | return ( | ||||
<li> | <li> | ||||
<Link activeClassName="active" to={{ pathname, query }}> | |||||
<Link | |||||
activeClassName="active" | |||||
to={isPortfolio ? getPortfolioUrl(id) : getProjectQueryUrl(id, branchLike)}> | |||||
{translate('overview.page')} | {translate('overview.page')} | ||||
</Link> | </Link> | ||||
</li> | </li> |
title="parent-portfolio" | title="parent-portfolio" | ||||
to={ | to={ | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | |||||
"pathname": "/portfolio", | |||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "parent-portfolio", | "id": "parent-portfolio", | ||||
}, | }, | ||||
} | } |
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | ||||
import { scrollToElement } from 'sonar-ui-common/helpers/scrolling'; | import { scrollToElement } from 'sonar-ui-common/helpers/scrolling'; | ||||
import { getSuggestions } from '../../../api/components'; | import { getSuggestions } from '../../../api/components'; | ||||
import { getCodeUrl, getProjectUrl } from '../../../helpers/urls'; | |||||
import { getCodeUrl, getComponentOverviewUrl } from '../../../helpers/urls'; | |||||
import { ComponentQualifier } from '../../../types/component'; | |||||
import RecentHistory from '../RecentHistory'; | import RecentHistory from '../RecentHistory'; | ||||
import './Search.css'; | import './Search.css'; | ||||
import { ComponentResult, More, Results, sortQualifiers } from './utils'; | import { ComponentResult, More, Results, sortQualifiers } from './utils'; | ||||
}; | }; | ||||
openSelected = () => { | openSelected = () => { | ||||
const { selected } = this.state; | |||||
const { results, selected } = this.state; | |||||
if (selected) { | |||||
if (selected.startsWith('qualifier###')) { | |||||
this.searchMore(selected.substr(12)); | |||||
if (!selected) { | |||||
return; | |||||
} | |||||
if (selected.startsWith('qualifier###')) { | |||||
this.searchMore(selected.substr(12)); | |||||
} else { | |||||
const file = this.findFile(selected); | |||||
if (file) { | |||||
this.props.router.push(getCodeUrl(file.project!, undefined, file.key)); | |||||
} else { | } else { | ||||
const file = this.findFile(selected); | |||||
if (file) { | |||||
this.props.router.push(getCodeUrl(file.project!, undefined, file.key)); | |||||
} else { | |||||
this.props.router.push(getProjectUrl(selected)); | |||||
let qualifier = ComponentQualifier.Project; | |||||
if ((results[ComponentQualifier.Portfolio] ?? []).find(r => r.key === selected)) { | |||||
qualifier = ComponentQualifier.Portfolio; | |||||
} else if ((results[ComponentQualifier.SubPortfolio] ?? []).find(r => r.key === selected)) { | |||||
qualifier = ComponentQualifier.SubPortfolio; | |||||
} | } | ||||
this.closeSearch(); | |||||
this.props.router.push(getComponentOverviewUrl(selected, qualifier)); | |||||
} | } | ||||
this.closeSearch(); | |||||
} | } | ||||
}; | }; | ||||
import ClockIcon from 'sonar-ui-common/components/icons/ClockIcon'; | import ClockIcon from 'sonar-ui-common/components/icons/ClockIcon'; | ||||
import FavoriteIcon from 'sonar-ui-common/components/icons/FavoriteIcon'; | import FavoriteIcon from 'sonar-ui-common/components/icons/FavoriteIcon'; | ||||
import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; | import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; | ||||
import { getCodeUrl, getProjectUrl } from '../../../helpers/urls'; | |||||
import { getCodeUrl, getComponentOverviewUrl } from '../../../helpers/urls'; | |||||
import { ComponentResult } from './utils'; | import { ComponentResult } from './utils'; | ||||
interface Props { | interface Props { | ||||
const isFile = component.qualifier === 'FIL' || component.qualifier === 'UTS'; | const isFile = component.qualifier === 'FIL' || component.qualifier === 'UTS'; | ||||
const to = isFile | const to = isFile | ||||
? getCodeUrl(component.project!, undefined, component.key) | ? getCodeUrl(component.project!, undefined, component.key) | ||||
: getProjectUrl(component.key); | |||||
: getComponentOverviewUrl(component.key, component.qualifier); | |||||
return ( | return ( | ||||
<li | <li |
import { shallow, ShallowWrapper } from 'enzyme'; | import { shallow, ShallowWrapper } from 'enzyme'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { elementKeydown } from 'sonar-ui-common/helpers/testUtils'; | import { elementKeydown } from 'sonar-ui-common/helpers/testUtils'; | ||||
import { mockRouter } from '../../../../helpers/testMocks'; | |||||
import { ComponentQualifier } from '../../../../types/component'; | |||||
import { Search } from '../Search'; | import { Search } from '../Search'; | ||||
it('selects results', () => { | it('selects results', () => { | ||||
open: true, | open: true, | ||||
results: { | results: { | ||||
TRK: [component('foo'), component('bar')], | TRK: [component('foo'), component('bar')], | ||||
BRC: [component('qwe', 'BRC')] | |||||
BRC: [component('qwe', ComponentQualifier.SubProject)] | |||||
}, | }, | ||||
selected: 'foo' | selected: 'foo' | ||||
}); | }); | ||||
prev(form, 'foo'); | prev(form, 'foo'); | ||||
}); | }); | ||||
it('opens selected on enter', () => { | |||||
const form = shallowRender(); | |||||
it('opens selected project on enter', () => { | |||||
const router = mockRouter(); | |||||
const form = shallowRender({ router }); | |||||
const selectedKey = 'project'; | |||||
form.setState({ | form.setState({ | ||||
open: true, | open: true, | ||||
results: { TRK: [component('foo')] }, | |||||
selected: 'foo' | |||||
results: { [ComponentQualifier.Project]: [component(selectedKey)] }, | |||||
selected: selectedKey | |||||
}); | |||||
elementKeydown(form.find('SearchBox'), 13); | |||||
expect(router.push).toBeCalledWith({ pathname: '/dashboard', query: { id: selectedKey } }); | |||||
}); | |||||
it('opens selected portfolio on enter', () => { | |||||
const router = mockRouter(); | |||||
const form = shallowRender({ router }); | |||||
const selectedKey = 'portfolio'; | |||||
form.setState({ | |||||
open: true, | |||||
results: { | |||||
[ComponentQualifier.Portfolio]: [component(selectedKey, ComponentQualifier.Portfolio)] | |||||
}, | |||||
selected: selectedKey | |||||
}); | }); | ||||
const openSelected = jest.fn(); | |||||
(form.instance() as Search).openSelected = openSelected; | |||||
elementKeydown(form.find('SearchBox'), 13); | |||||
expect(router.push).toBeCalledWith({ pathname: '/portfolio', query: { id: selectedKey } }); | |||||
}); | |||||
it('opens selected subportfolio on enter', () => { | |||||
const router = mockRouter(); | |||||
const form = shallowRender({ router }); | |||||
const selectedKey = 'sbprtfl'; | |||||
form.setState({ | |||||
open: true, | |||||
results: { | |||||
[ComponentQualifier.SubPortfolio]: [component(selectedKey, ComponentQualifier.SubPortfolio)] | |||||
}, | |||||
selected: selectedKey | |||||
}); | |||||
elementKeydown(form.find('SearchBox'), 13); | elementKeydown(form.find('SearchBox'), 13); | ||||
expect(openSelected).toBeCalled(); | |||||
expect(router.push).toBeCalledWith({ pathname: '/portfolio', query: { id: selectedKey } }); | |||||
}); | }); | ||||
it('shows warning about short input', () => { | it('shows warning about short input', () => { | ||||
); | ); | ||||
} | } | ||||
function component(key: string, qualifier = 'TRK') { | |||||
function component(key: string, qualifier = ComponentQualifier.Project) { | |||||
return { key, name: key, qualifier }; | return { key, name: key, qualifier }; | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "qwe", | "id": "qwe", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } |
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | ||||
import MetaLink from '../../../app/components/nav/component/projectInformation/meta/MetaLink'; | import MetaLink from '../../../app/components/nav/component/projectInformation/meta/MetaLink'; | ||||
import { orderLinks } from '../../../helpers/projectLinks'; | import { orderLinks } from '../../../helpers/projectLinks'; | ||||
import { getProjectUrl } from '../../../helpers/urls'; | |||||
interface Props { | interface Props { | ||||
project: T.MyProject; | project: T.MyProject; | ||||
</aside> | </aside> | ||||
<h3 className="account-project-name"> | <h3 className="account-project-name"> | ||||
<Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link> | |||||
<Link to={getProjectUrl(project.key)}>{project.name}</Link> | |||||
</h3> | </h3> | ||||
{orderedLinks.length > 0 && ( | {orderedLinks.length > 0 && ( |
getProjectUrl, | getProjectUrl, | ||||
getPullRequestUrl | getPullRequestUrl | ||||
} from '../../../helpers/urls'; | } from '../../../helpers/urls'; | ||||
import { ComponentQualifier } from '../../../types/component'; | |||||
import { isPortfolioLike } from '../../../types/component'; | |||||
import TaskType from './TaskType'; | import TaskType from './TaskType'; | ||||
interface Props { | interface Props { | ||||
} | } | ||||
function getTaskComponentUrl(componentKey: string, task: T.Task) { | function getTaskComponentUrl(componentKey: string, task: T.Task) { | ||||
if (task.componentQualifier === ComponentQualifier.Portfolio) { | |||||
if (isPortfolioLike(task.componentQualifier)) { | |||||
return getPortfolioUrl(componentKey); | return getPortfolioUrl(componentKey); | ||||
} else if (task.branch) { | } else if (task.branch) { | ||||
return getBranchUrl(componentKey, task.branch); | return getBranchUrl(componentKey, task.branch); |
import { translate } from 'sonar-ui-common/helpers/l10n'; | import { translate } from 'sonar-ui-common/helpers/l10n'; | ||||
import { colors } from '../../../app/theme'; | import { colors } from '../../../app/theme'; | ||||
import { getBranchLikeQuery } from '../../../helpers/branch-like'; | import { getBranchLikeQuery } from '../../../helpers/branch-like'; | ||||
import { getProjectUrl } from '../../../helpers/urls'; | |||||
import { BranchLike } from '../../../types/branch-like'; | import { BranchLike } from '../../../types/branch-like'; | ||||
export function getTooltip(component: T.ComponentMeasure) { | export function getTooltip(component: T.ComponentMeasure) { | ||||
let inner = null; | let inner = null; | ||||
if (component.refKey && component.qualifier !== 'SVW') { | if (component.refKey && component.qualifier !== 'SVW') { | ||||
const branch = rootComponent.qualifier === 'APP' ? { branch: component.branch } : {}; | |||||
const branch = rootComponent.qualifier === 'APP' ? component.branch : undefined; | |||||
inner = ( | inner = ( | ||||
<Link | |||||
className="link-with-icon" | |||||
to={{ pathname: '/dashboard', query: { id: component.refKey, ...branch } }}> | |||||
<Link className="link-with-icon" to={getProjectUrl(component.refKey, branch)}> | |||||
<QualifierIcon qualifier={component.qualifier} /> <span>{name}</span> | <QualifierIcon qualifier={component.qualifier} /> <span>{name}</span> | ||||
</Link> | </Link> | ||||
); | ); |
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "src/main/ts/app", | "id": "src/main/ts/app", | ||||
}, | }, | ||||
} | } |
import { Router, withRouter } from '../../../components/hoc/withRouter'; | import { Router, withRouter } from '../../../components/hoc/withRouter'; | ||||
import { isPullRequest } from '../../../helpers/branch-like'; | import { isPullRequest } from '../../../helpers/branch-like'; | ||||
import { BranchLike } from '../../../types/branch-like'; | import { BranchLike } from '../../../types/branch-like'; | ||||
import { ComponentQualifier } from '../../../types/component'; | |||||
import { isPortfolioLike } from '../../../types/component'; | |||||
import BranchOverview from '../branches/BranchOverview'; | import BranchOverview from '../branches/BranchOverview'; | ||||
const EmptyOverview = lazyLoadComponent(() => import('./EmptyOverview')); | const EmptyOverview = lazyLoadComponent(() => import('./EmptyOverview')); | ||||
export class App extends React.PureComponent<Props> { | export class App extends React.PureComponent<Props> { | ||||
isPortfolio = () => { | isPortfolio = () => { | ||||
return ([ComponentQualifier.Portfolio, ComponentQualifier.SubPortfolio] as string[]).includes( | |||||
this.props.component.qualifier | |||||
); | |||||
return isPortfolioLike(this.props.component.qualifier); | |||||
}; | }; | ||||
render() { | render() { |
import { formatMeasure } from 'sonar-ui-common/helpers/measures'; | import { formatMeasure } from 'sonar-ui-common/helpers/measures'; | ||||
import { colors } from '../../../app/theme'; | import { colors } from '../../../app/theme'; | ||||
import Measure from '../../../components/measure/Measure'; | import Measure from '../../../components/measure/Measure'; | ||||
import { getProjectUrl } from '../../../helpers/urls'; | |||||
import { getComponentOverviewUrl } from '../../../helpers/urls'; | |||||
import { ComponentQualifier } from '../../../types/component'; | |||||
import { SubComponent } from '../types'; | import { SubComponent } from '../types'; | ||||
interface Props { | interface Props { | ||||
<td> | <td> | ||||
<Link | <Link | ||||
className="link-with-icon" | className="link-with-icon" | ||||
to={getProjectUrl(component.refKey || component.key)}> | |||||
to={getComponentOverviewUrl( | |||||
component.refKey || component.key, | |||||
component.qualifier | |||||
)}> | |||||
<QualifierIcon qualifier={component.qualifier} /> {component.name} | <QualifierIcon qualifier={component.qualifier} /> {component.name} | ||||
</Link> | </Link> | ||||
</td> | </td> | ||||
{component.qualifier === 'TRK' | |||||
{component.qualifier === ComponentQualifier.Project | |||||
? renderCell(component.measures, 'alert_status', 'LEVEL') | ? renderCell(component.measures, 'alert_status', 'LEVEL') | ||||
: renderCell(component.measures, 'releasability_rating', 'RATING')} | : renderCell(component.measures, 'releasability_rating', 'RATING')} | ||||
{renderCell(component.measures, 'reliability_rating', 'RATING')} | {renderCell(component.measures, 'reliability_rating', 'RATING')} |
style={Object {}} | style={Object {}} | ||||
to={ | to={ | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | |||||
"pathname": "/portfolio", | |||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "barbar", | "id": "barbar", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "bazbaz", | "id": "bazbaz", | ||||
}, | }, | ||||
} | } |
import DateTooltipFormatter from 'sonar-ui-common/components/intl/DateTooltipFormatter'; | import DateTooltipFormatter from 'sonar-ui-common/components/intl/DateTooltipFormatter'; | ||||
import { Project } from '../../api/components'; | import { Project } from '../../api/components'; | ||||
import PrivacyBadgeContainer from '../../components/common/PrivacyBadgeContainer'; | import PrivacyBadgeContainer from '../../components/common/PrivacyBadgeContainer'; | ||||
import { getPortfolioUrl, getProjectUrl } from '../../helpers/urls'; | |||||
import { ComponentQualifier } from '../../types/component'; | |||||
import { getComponentOverviewUrl } from '../../helpers/urls'; | |||||
import './ProjectRow.css'; | import './ProjectRow.css'; | ||||
import ProjectRowActions from './ProjectRowActions'; | import ProjectRowActions from './ProjectRowActions'; | ||||
this.props.onProjectCheck(this.props.project, checked); | this.props.onProjectCheck(this.props.project, checked); | ||||
}; | }; | ||||
getComponentUrl(project: Project) { | |||||
return project.qualifier === ComponentQualifier.Portfolio | |||||
? getPortfolioUrl(project.key) | |||||
: getProjectUrl(project.key); | |||||
} | |||||
render() { | render() { | ||||
const { organization, project, selected } = this.props; | const { organization, project, selected } = this.props; | ||||
</td> | </td> | ||||
<td className="nowrap hide-overflow project-row-text-cell"> | <td className="nowrap hide-overflow project-row-text-cell"> | ||||
<Link className="link-with-icon" to={this.getComponentUrl(project)}> | |||||
<Link | |||||
className="link-with-icon" | |||||
to={getComponentOverviewUrl(project.key, project.qualifier)}> | |||||
<QualifierIcon className="little-spacer-right" qualifier={project.qualifier} /> | <QualifierIcon className="little-spacer-right" qualifier={project.qualifier} /> | ||||
<Tooltip overlay={project.name} placement="left"> | <Tooltip overlay={project.name} placement="left"> |
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "project", | "id": "project", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "project", | "id": "project", | ||||
}, | }, | ||||
} | } |
import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; | import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; | ||||
import { translate } from 'sonar-ui-common/helpers/l10n'; | import { translate } from 'sonar-ui-common/helpers/l10n'; | ||||
import { getProfileProjects } from '../../../api/quality-profiles'; | import { getProfileProjects } from '../../../api/quality-profiles'; | ||||
import { getProjectUrl } from '../../../helpers/urls'; | |||||
import { Profile } from '../types'; | import { Profile } from '../types'; | ||||
import ChangeProjectsForm from './ChangeProjectsForm'; | import ChangeProjectsForm from './ChangeProjectsForm'; | ||||
<ul> | <ul> | ||||
{projects.map(project => ( | {projects.map(project => ( | ||||
<li className="spacer-top js-profile-project" data-key={project.key} key={project.key}> | <li className="spacer-top js-profile-project" data-key={project.key} key={project.key}> | ||||
<Link | |||||
className="link-with-icon" | |||||
to={{ pathname: '/dashboard', query: { id: project.key } }}> | |||||
<Link className="link-with-icon" to={getProjectUrl(project.key)}> | |||||
<QualifierIcon qualifier="TRK" /> <span>{project.name}</span> | <QualifierIcon qualifier="TRK" /> <span>{project.name}</span> | ||||
</Link> | </Link> | ||||
</li> | </li> |
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "org.sonarsource.xml:xml", | "id": "org.sonarsource.xml:xml", | ||||
}, | }, | ||||
} | } |
* 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 { ComponentQualifier } from '../../types/component'; | |||||
import { | import { | ||||
getComponentDrilldownUrl, | getComponentDrilldownUrl, | ||||
getComponentIssuesUrl, | getComponentIssuesUrl, | ||||
getComponentOverviewUrl, | |||||
getComponentSecurityHotspotsUrl, | getComponentSecurityHotspotsUrl, | ||||
getQualityGatesUrl, | getQualityGatesUrl, | ||||
getQualityGateUrl | getQualityGateUrl | ||||
}); | }); | ||||
}); | }); | ||||
describe('getComponentOverviewUrl', () => { | |||||
it('should return a portfolio url for a portfolio', () => { | |||||
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Portfolio)).toEqual({ | |||||
pathname: '/portfolio', | |||||
query: { id: SIMPLE_COMPONENT_KEY } | |||||
}); | |||||
}); | |||||
it('should return a portfolio url for a subportfolio', () => { | |||||
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.SubPortfolio)).toEqual({ | |||||
pathname: '/portfolio', | |||||
query: { id: SIMPLE_COMPONENT_KEY } | |||||
}); | |||||
}); | |||||
it('should return a dashboard url for a project', () => { | |||||
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Project)).toEqual({ | |||||
pathname: '/dashboard', | |||||
query: { id: SIMPLE_COMPONENT_KEY } | |||||
}); | |||||
}); | |||||
it('should return a dashboard url for an app', () => { | |||||
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Application)).toEqual({ | |||||
pathname: '/dashboard', | |||||
query: { id: SIMPLE_COMPONENT_KEY } | |||||
}); | |||||
}); | |||||
}); | |||||
describe('#getComponentDrilldownUrl', () => { | describe('#getComponentDrilldownUrl', () => { | ||||
it('should return component drilldown url', () => { | it('should return component drilldown url', () => { | ||||
expect( | expect( |
*/ | */ | ||||
import { getBaseUrl, Location } from 'sonar-ui-common/helpers/urls'; | import { getBaseUrl, Location } from 'sonar-ui-common/helpers/urls'; | ||||
import { getProfilePath } from '../apps/quality-profiles/utils'; | import { getProfilePath } from '../apps/quality-profiles/utils'; | ||||
import { BranchLike } from '../types/branch-like'; | |||||
import { BranchLike, BranchParameters } from '../types/branch-like'; | |||||
import { ComponentQualifier, isPortfolioLike } from '../types/component'; | |||||
import { GraphType } from '../types/project-activity'; | import { GraphType } from '../types/project-activity'; | ||||
import { getBranchLikeQuery, isBranch, isMainBranch, isPullRequest } from './branch-like'; | import { getBranchLikeQuery, isBranch, isMainBranch, isPullRequest } from './branch-like'; | ||||
type Query = Location['query']; | type Query = Location['query']; | ||||
export function getComponentOverviewUrl( | |||||
componentKey: string, | |||||
componentQualifier: ComponentQualifier | string, | |||||
branchParameters?: BranchParameters | |||||
) { | |||||
return isPortfolioLike(componentQualifier) | |||||
? getPortfolioUrl(componentKey) | |||||
: getProjectQueryUrl(componentKey, branchParameters); | |||||
} | |||||
export function getProjectUrl(project: string, branch?: string): Location { | export function getProjectUrl(project: string, branch?: string): Location { | ||||
return { pathname: '/dashboard', query: { id: project, branch } }; | return { pathname: '/dashboard', query: { id: project, branch } }; | ||||
} | } | ||||
export function getProjectQueryUrl(project: string, branchParameters?: BranchParameters): Location { | |||||
return { pathname: '/dashboard', query: { id: project, ...branchParameters } }; | |||||
} | |||||
export function getPortfolioUrl(key: string): Location { | export function getPortfolioUrl(key: string): Location { | ||||
return { pathname: '/portfolio', query: { id: key } }; | return { pathname: '/portfolio', query: { id: key } }; | ||||
} | } |
* 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. | ||||
*/ | */ | ||||
export enum ComponentQualifier { | export enum ComponentQualifier { | ||||
Application = 'APP', | Application = 'APP', | ||||
Directory = 'DIR', | Directory = 'DIR', | ||||
TestFile = 'UTS' | TestFile = 'UTS' | ||||
} | } | ||||
export function isPortfolioLike(componentQualifier?: string | ComponentQualifier) { | |||||
return Boolean( | |||||
componentQualifier && | |||||
[ | |||||
ComponentQualifier.Portfolio.toString(), | |||||
ComponentQualifier.SubPortfolio.toString() | |||||
].includes(componentQualifier) | |||||
); | |||||
} | |||||
export enum Visibility { | export enum Visibility { | ||||
Public = 'public', | Public = 'public', | ||||
Private = 'private' | Private = 'private' |