@@ -0,0 +1,58 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { Alert } from '../../components/ui/Alert'; | |||
import { translate } from '../../helpers/l10n'; | |||
import { BranchLike } from '../../types/branch-like'; | |||
import { isApplication } from '../../types/component'; | |||
export interface NonAdminPagesContainerProps { | |||
children: JSX.Element; | |||
branchLike?: BranchLike; | |||
branchLikes: BranchLike[]; | |||
component: T.Component; | |||
isInProgress?: boolean; | |||
isPending?: boolean; | |||
onBranchesChange: () => void; | |||
onComponentChange: (changes: {}) => void; | |||
} | |||
export default function NonAdminPagesContainer(props: NonAdminPagesContainerProps) { | |||
const { children, component } = props; | |||
/* | |||
* Catch Applications for which the user does not have access to all child projects | |||
* and prevent displaying whatever page was requested. | |||
* This doesn't apply to admin pages (those are not within this container) | |||
*/ | |||
if (isApplication(component.qualifier) && !component.canBrowseAllChildProjects) { | |||
return ( | |||
<div className="page page-limited display-flex-justify-center"> | |||
<Alert className="max-width-60 huge-spacer-top" display="block" variant="error"> | |||
<p>{translate('application.cannot_access_all_child_projects1')}</p> | |||
<br /> | |||
<p>{translate('application.cannot_access_all_child_projects2')}</p> | |||
</Alert> | |||
</div> | |||
); | |||
} | |||
return React.cloneElement(children, props); | |||
} |
@@ -0,0 +1,69 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { mockComponent } from '../../../helpers/mocks/component'; | |||
import { ComponentQualifier } from '../../../types/component'; | |||
import NonAdminPagesContainer, { NonAdminPagesContainerProps } from '../NonAdminPagesContainer'; | |||
function Child() { | |||
return <div />; | |||
} | |||
it('should render correctly', () => { | |||
expect( | |||
shallowRender() | |||
.find(Child) | |||
.exists() | |||
).toBe(true); | |||
expect( | |||
shallowRender({ | |||
component: mockComponent({ | |||
qualifier: ComponentQualifier.Application, | |||
canBrowseAllChildProjects: true | |||
}) | |||
}) | |||
.find(Child) | |||
.exists() | |||
).toBe(true); | |||
const wrapper = shallowRender({ | |||
component: mockComponent({ | |||
qualifier: ComponentQualifier.Application | |||
}) | |||
}); | |||
expect(wrapper.find(Child).exists()).toBe(false); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
function shallowRender(props: Partial<NonAdminPagesContainerProps> = {}) { | |||
return shallow<NonAdminPagesContainerProps>( | |||
<NonAdminPagesContainer | |||
branchLikes={[]} | |||
component={mockComponent()} | |||
onBranchesChange={jest.fn()} | |||
onComponentChange={jest.fn()} | |||
{...props}> | |||
<Child /> | |||
</NonAdminPagesContainer> | |||
); | |||
} |
@@ -0,0 +1,21 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="page page-limited display-flex-justify-center" | |||
> | |||
<Alert | |||
className="max-width-60 huge-spacer-top" | |||
display="block" | |||
variant="error" | |||
> | |||
<p> | |||
application.cannot_access_all_child_projects1 | |||
</p> | |||
<br /> | |||
<p> | |||
application.cannot_access_all_child_projects2 | |||
</p> | |||
</Alert> | |||
</div> | |||
`; |
@@ -67,6 +67,7 @@ import App from '../components/App'; | |||
import GlobalContainer from '../components/GlobalContainer'; | |||
import { PageContext } from '../components/indexation/PageUnavailableDueToIndexation'; | |||
import MigrationContainer from '../components/MigrationContainer'; | |||
import NonAdminPagesContainer from '../components/NonAdminPagesContainer'; | |||
import getStore from './getStore'; | |||
/* | |||
@@ -156,46 +157,51 @@ function renderRedirects() { | |||
function renderComponentRoutes() { | |||
return ( | |||
<Route component={lazyLoadComponent(() => import('../components/ComponentContainer'))}> | |||
<RouteWithChildRoutes path="code" childRoutes={codeRoutes} /> | |||
<RouteWithChildRoutes path="component_measures" childRoutes={componentMeasuresRoutes} /> | |||
<RouteWithChildRoutes path="dashboard" childRoutes={overviewRoutes} /> | |||
<RouteWithChildRoutes path="portfolio" childRoutes={portfolioRoutes} /> | |||
<RouteWithChildRoutes path="project/activity" childRoutes={projectActivityRoutes} /> | |||
<Route | |||
path="project/extension/:pluginKey/:extensionKey" | |||
component={lazyLoadComponent(() => import('../components/extensions/ProjectPageExtension'))} | |||
/> | |||
<Route | |||
path="project/issues" | |||
component={Issues} | |||
onEnter={({ location: { query } }, replace) => { | |||
if (query.types) { | |||
if (query.types === 'SECURITY_HOTSPOT') { | |||
replace({ | |||
pathname: '/security_hotspots', | |||
query: { ...pick(query, ['id', 'branch', 'pullRequest']), assignedToMe: false } | |||
}); | |||
} else { | |||
query.types = query.types | |||
.split(',') | |||
.filter((type: string) => type !== 'SECURITY_HOTSPOT') | |||
.join(','); | |||
{/* This container is a catch-all for all non-admin pages */} | |||
<Route component={NonAdminPagesContainer}> | |||
<RouteWithChildRoutes path="code" childRoutes={codeRoutes} /> | |||
<RouteWithChildRoutes path="component_measures" childRoutes={componentMeasuresRoutes} /> | |||
<RouteWithChildRoutes path="dashboard" childRoutes={overviewRoutes} /> | |||
<RouteWithChildRoutes path="portfolio" childRoutes={portfolioRoutes} /> | |||
<RouteWithChildRoutes path="project/activity" childRoutes={projectActivityRoutes} /> | |||
<Route | |||
path="project/extension/:pluginKey/:extensionKey" | |||
component={lazyLoadComponent(() => | |||
import('../components/extensions/ProjectPageExtension') | |||
)} | |||
/> | |||
<Route | |||
path="project/issues" | |||
component={Issues} | |||
onEnter={({ location: { query } }, replace) => { | |||
if (query.types) { | |||
if (query.types === 'SECURITY_HOTSPOT') { | |||
replace({ | |||
pathname: '/security_hotspots', | |||
query: { ...pick(query, ['id', 'branch', 'pullRequest']), assignedToMe: false } | |||
}); | |||
} else { | |||
query.types = query.types | |||
.split(',') | |||
.filter((type: string) => type !== 'SECURITY_HOTSPOT') | |||
.join(','); | |||
} | |||
} | |||
} | |||
}} | |||
/> | |||
<Route | |||
path="security_hotspots" | |||
component={lazyLoadComponent(() => | |||
import('../../apps/security-hotspots/SecurityHotspotsApp') | |||
)} | |||
/> | |||
<RouteWithChildRoutes path="project/quality_gate" childRoutes={projectQualityGateRoutes} /> | |||
<RouteWithChildRoutes | |||
path="project/quality_profiles" | |||
childRoutes={projectQualityProfilesRoutes} | |||
/> | |||
<RouteWithChildRoutes path="tutorials" childRoutes={tutorialsRoutes} /> | |||
}} | |||
/> | |||
<Route | |||
path="security_hotspots" | |||
component={lazyLoadComponent(() => | |||
import('../../apps/security-hotspots/SecurityHotspotsApp') | |||
)} | |||
/> | |||
<RouteWithChildRoutes path="project/quality_gate" childRoutes={projectQualityGateRoutes} /> | |||
<RouteWithChildRoutes | |||
path="project/quality_profiles" | |||
childRoutes={projectQualityProfilesRoutes} | |||
/> | |||
<RouteWithChildRoutes path="tutorials" childRoutes={tutorialsRoutes} /> | |||
</Route> | |||
<Route component={lazyLoadComponent(() => import('../components/ProjectAdminContainer'))}> | |||
<Route | |||
path="project/admin/extension/:pluginKey/:extensionKey" |
@@ -3979,6 +3979,14 @@ branch_like_navigation.only_one_branch.documentation=Branches documentation | |||
branch_like_navigation.only_one_branch.pr_analysis=Pull Request analysis | |||
branch_like_navigation.tutorial_for_ci=Show me how to set up my CI | |||
#------------------------------------------------------------------------------ | |||
# | |||
# APPLICATIONS | |||
# | |||
#------------------------------------------------------------------------------ | |||
application.cannot_access_all_child_projects1=You must have access to all the projects within this Application in order to browse it. | |||
application.cannot_access_all_child_projects2=Please contact your project administrator. | |||
#------------------------------------------------------------------------------ | |||
# | |||
# PORTFOLIOS |