@@ -106,6 +106,11 @@ export function CurrentBranchLike(props: CurrentBranchLikeProps) { | |||
{ | |||
href: '/documentation/analysis/pull-request/', | |||
label: translate('branch_like_navigation.only_one_branch.pr_analysis') | |||
}, | |||
{ | |||
href: `/tutorials?id=${component.key}`, | |||
label: translate('branch_like_navigation.tutorial_for_ci'), | |||
inPlace: true | |||
} | |||
]} | |||
title={translate('branch_like_navigation.only_one_branch.title')}> |
@@ -199,6 +199,11 @@ exports[`CurrentBranchLikeRenderer should render correctly for project when ther | |||
"href": "/documentation/analysis/pull-request/", | |||
"label": "branch_like_navigation.only_one_branch.pr_analysis", | |||
}, | |||
Object { | |||
"href": "/tutorials?id=my-project", | |||
"inPlace": true, | |||
"label": "branch_like_navigation.tutorial_for_ci", | |||
}, | |||
] | |||
} | |||
title="branch_like_navigation.only_one_branch.title" |
@@ -59,6 +59,7 @@ import qualityProfilesRoutes from '../../apps/quality-profiles/routes'; | |||
import sessionsRoutes from '../../apps/sessions/routes'; | |||
import settingsRoutes from '../../apps/settings/routes'; | |||
import systemRoutes from '../../apps/system/routes'; | |||
import tutorialsRoutes from '../../apps/tutorials/routes'; | |||
import usersRoutes from '../../apps/users/routes'; | |||
import webAPIRoutes from '../../apps/web-api/routes'; | |||
import webhooksRoutes from '../../apps/webhooks/routes'; | |||
@@ -189,6 +190,7 @@ function renderComponentRoutes() { | |||
path="project/quality_profiles" | |||
childRoutes={projectQualityProfilesRoutes} | |||
/> | |||
<RouteWithChildRoutes path="tutorials" childRoutes={tutorialsRoutes} /> | |||
<Route component={lazyLoadComponent(() => import('../components/ProjectAdminContainer'))}> | |||
<RouteWithChildRoutes path="custom_measures" childRoutes={customMeasuresRoutes} /> | |||
<Route |
@@ -0,0 +1,46 @@ | |||
/* | |||
* 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 handleRequiredAuthentication from 'sonar-ui-common/helpers/handleRequiredAuthentication'; | |||
import { withCurrentUser } from '../../../components/hoc/withCurrentUser'; | |||
import TutorialSelection from '../../../components/tutorials/TutorialSelection'; | |||
import { isLoggedIn } from '../../../helpers/users'; | |||
export interface TutorialsAppProps { | |||
component: T.Component; | |||
currentUser: T.CurrentUser; | |||
} | |||
export function TutorialsApp(props: TutorialsAppProps) { | |||
const { component, currentUser } = props; | |||
if (!isLoggedIn(currentUser)) { | |||
handleRequiredAuthentication(); | |||
return null; | |||
} | |||
return ( | |||
<div className="page page-limited"> | |||
<TutorialSelection component={component} currentUser={currentUser} /> | |||
</div> | |||
); | |||
} | |||
export default withCurrentUser(TutorialsApp); |
@@ -0,0 +1,41 @@ | |||
/* | |||
* 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 handleRequiredAuthentication from 'sonar-ui-common/helpers/handleRequiredAuthentication'; | |||
import { mockComponent, mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks'; | |||
import { TutorialsApp, TutorialsAppProps } from '../TutorialsApp'; | |||
jest.mock('sonar-ui-common/helpers/handleRequiredAuthentication', () => ({ default: jest.fn() })); | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('should redirect if user is not logged in', () => { | |||
shallowRender({ currentUser: mockCurrentUser() }); | |||
expect(handleRequiredAuthentication).toHaveBeenCalled(); | |||
}); | |||
function shallowRender(overrides: Partial<TutorialsAppProps> = {}) { | |||
return shallow( | |||
<TutorialsApp component={mockComponent()} currentUser={mockLoggedInUser()} {...overrides} /> | |||
); | |||
} |
@@ -0,0 +1,41 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="page page-limited" | |||
> | |||
<withRouter(TutorialSelection) | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "TRK", | |||
"qualityGate": Object { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": Array [ | |||
Object { | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": Array [], | |||
} | |||
} | |||
currentUser={ | |||
Object { | |||
"groups": Array [], | |||
"isLoggedIn": true, | |||
"login": "luke", | |||
"name": "Skywalker", | |||
"scmAccounts": Array [], | |||
} | |||
} | |||
/> | |||
</div> | |||
`; |
@@ -0,0 +1,29 @@ | |||
/* | |||
* 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 { lazyLoadComponent } from 'sonar-ui-common/components/lazyLoadComponent'; | |||
const routes = [ | |||
{ | |||
indexRoute: { component: lazyLoadComponent(() => import('./components/TutorialsApp')) } | |||
} | |||
]; | |||
export default routes; |
@@ -18,6 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; | |||
import DetachIcon from 'sonar-ui-common/components/icons/DetachIcon'; | |||
import { isWebUri } from 'valid-url'; | |||
@@ -26,7 +27,7 @@ export interface DocumentationTooltipProps { | |||
children?: React.ReactNode; | |||
className?: string; | |||
content?: React.ReactNode; | |||
links?: Array<{ href: string; label: string }>; | |||
links?: Array<{ href: string; label: string; inPlace?: boolean }>; | |||
title?: string; | |||
} | |||
@@ -50,16 +51,22 @@ export default function DocumentationTooltip(props: DocumentationTooltipProps) { | |||
<> | |||
<hr className="big-spacer-top big-spacer-bottom" /> | |||
{links.map(({ href, label }) => ( | |||
{links.map(({ href, label, inPlace }) => ( | |||
<div className="little-spacer-bottom" key={label}> | |||
<a | |||
className="display-inline-flex-center link-with-icon" | |||
href={href} | |||
rel="noopener noreferrer" | |||
target="_blank"> | |||
{isWebUri(href) && <DetachIcon size={14} className="spacer-right" />} | |||
<span>{label}</span> | |||
</a> | |||
{inPlace ? ( | |||
<Link to={href}> | |||
<span>{label}</span> | |||
</Link> | |||
) : ( | |||
<a | |||
className="display-inline-flex-center link-with-icon" | |||
href={href} | |||
rel="noopener noreferrer" | |||
target="_blank"> | |||
{isWebUri(href) && <DetachIcon size={14} className="spacer-right" />} | |||
<span>{label}</span> | |||
</a> | |||
)} | |||
</div> | |||
))} | |||
</> |
@@ -27,7 +27,8 @@ it('renders correctly', () => { | |||
shallowRender({ | |||
links: [ | |||
{ href: 'http://link.tosome.place', label: 'external link' }, | |||
{ href: '/documentation/guide', label: 'internal link' } | |||
{ href: '/documentation/guide', label: 'internal link' }, | |||
{ href: '/projects', label: 'in place', inPlace: true } | |||
] | |||
}) | |||
).toMatchSnapshot('with links'); |
@@ -105,6 +105,19 @@ exports[`renders correctly: with links 1`] = ` | |||
</span> | |||
</a> | |||
</div> | |||
<div | |||
className="little-spacer-bottom" | |||
> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/projects" | |||
> | |||
<span> | |||
in place | |||
</span> | |||
</Link> | |||
</div> | |||
</React.Fragment> | |||
</div> | |||
} |
@@ -24,7 +24,6 @@ import RenderOptions from '../components/RenderOptions'; | |||
import { BuildTools, ManualTutorialConfig, OSs } from '../types'; | |||
interface Props { | |||
component: T.Component; | |||
config?: ManualTutorialConfig; | |||
onDone: (config: ManualTutorialConfig) => void; | |||
} |
@@ -51,7 +51,7 @@ export default class ProjectAnalysisStep extends React.PureComponent<Props, Stat | |||
return ( | |||
<div className="boxed-group-inner"> | |||
<div className="display-flex-column"> | |||
<BuildToolForm component={this.props.component} onDone={this.handleBuildToolSelect} /> | |||
<BuildToolForm onDone={this.handleBuildToolSelect} /> | |||
{this.state.config && ( | |||
<div className="huge-spacer-top"> |
@@ -19,7 +19,6 @@ | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { mockComponent } from '../../../../helpers/testMocks'; | |||
import { BuildTools, OSs } from '../../types'; | |||
import BuildToolForm from '../BuildToolForm'; | |||
@@ -47,7 +46,5 @@ it('correctly calls the onDone prop', () => { | |||
}); | |||
function shallowRender(props: Partial<BuildToolForm['props']> = {}) { | |||
return shallow<BuildToolForm>( | |||
<BuildToolForm component={mockComponent()} onDone={jest.fn()} {...props} /> | |||
); | |||
return shallow<BuildToolForm>(<BuildToolForm onDone={jest.fn()} {...props} />); | |||
} |
@@ -3594,6 +3594,7 @@ branch_like_navigation.only_one_branch.title=Learn how to analyze branches in So | |||
branch_like_navigation.only_one_branch.content=Quickly setup branch analysis and get separate insights for each of your branches and Pull Requests. | |||
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 | |||
#------------------------------------------------------------------------------ | |||
# |