* SONARCLOUD-64 Move Onboarding to ProjectOnboarding * SONARCLOUD-64 Migrate project onboarding to TS * SONARCLOUD-64 Update ProjectOnboarding style * SONARCLOUD-64 Add main onboarding pagetags/7.5
@@ -20,16 +20,21 @@ | |||
import * as React from 'react'; | |||
import * as PropTypes from 'prop-types'; | |||
import { connect } from 'react-redux'; | |||
import OnboardingModal from '../../apps/tutorials/onboarding/OnboardingModal'; | |||
import Onboarding from '../../apps/tutorials/Onboarding'; | |||
import CreateOrganizationForm from '../../apps/account/organizations/CreateOrganizationForm'; | |||
import LicensePromptModal from '../../apps/marketplace/components/LicensePromptModal'; | |||
import { CurrentUser, isLoggedIn } from '../types'; | |||
import ProjectOnboardingModal from '../../apps/tutorials/projectOnboarding/ProjectOnboardingModal'; | |||
import TeamOnboardingModal from '../../apps/tutorials/teamOnboarding/TeamOnboardingModal'; | |||
import { CurrentUser, isLoggedIn, Organization } from '../types'; | |||
import { differenceInDays, parseDate, toShortNotSoISOString } from '../../helpers/dates'; | |||
import { EditionKey } from '../../apps/marketplace/utils'; | |||
import { getCurrentUser, getAppState } from '../../store/rootReducer'; | |||
import { skipOnboarding } from '../../store/users/actions'; | |||
import { skipOnboarding as skipOnboardingAction } from '../../store/users/actions'; | |||
import { showLicense } from '../../api/marketplace'; | |||
import { hasMessage } from '../../helpers/l10n'; | |||
import { save, get } from '../../helpers/storage'; | |||
import { isSonarCloud } from '../../helpers/system'; | |||
import { skipOnboarding } from '../../api/users'; | |||
interface StateProps { | |||
canAdmin: boolean; | |||
@@ -38,7 +43,7 @@ interface StateProps { | |||
} | |||
interface DispatchProps { | |||
skipOnboarding: () => void; | |||
skipOnboardingAction: () => void; | |||
} | |||
interface OwnProps { | |||
@@ -50,7 +55,10 @@ type Props = StateProps & DispatchProps & OwnProps; | |||
enum ModalKey { | |||
license, | |||
onboarding | |||
onboarding, | |||
organizationOnboarding, | |||
projectOnboarding, | |||
teamOnboarding | |||
} | |||
interface State { | |||
@@ -61,14 +69,18 @@ interface State { | |||
const LICENSE_PROMPT = 'sonarqube.license.prompt'; | |||
export class StartupModal extends React.PureComponent<Props, State> { | |||
static contextTypes = { | |||
router: PropTypes.object.isRequired | |||
}; | |||
static childContextTypes = { | |||
openOnboardingTutorial: PropTypes.func | |||
openProjectOnboarding: PropTypes.func | |||
}; | |||
state: State = { automatic: false }; | |||
getChildContext() { | |||
return { openOnboardingTutorial: this.openOnboarding }; | |||
return { openProjectOnboarding: this.openProjectOnboarding }; | |||
} | |||
componentDidMount() { | |||
@@ -77,8 +89,9 @@ export class StartupModal extends React.PureComponent<Props, State> { | |||
closeOnboarding = () => { | |||
this.setState(state => { | |||
if (state.modal === ModalKey.onboarding) { | |||
this.props.skipOnboarding(); | |||
if (state.modal !== ModalKey.license) { | |||
skipOnboarding(); | |||
this.props.skipOnboardingAction(); | |||
return { automatic: false, modal: undefined }; | |||
} | |||
return undefined; | |||
@@ -94,10 +107,27 @@ export class StartupModal extends React.PureComponent<Props, State> { | |||
}); | |||
}; | |||
closeOrganizationOnboarding = ({ key }: Pick<Organization, 'key'>) => { | |||
this.closeOnboarding(); | |||
this.context.router.push(`/organizations/${key}`); | |||
}; | |||
openOnboarding = () => { | |||
this.setState({ modal: ModalKey.onboarding }); | |||
}; | |||
openOrganizationOnboarding = () => { | |||
this.setState({ modal: ModalKey.organizationOnboarding }); | |||
}; | |||
openProjectOnboarding = () => { | |||
this.setState({ modal: ModalKey.projectOnboarding }); | |||
}; | |||
openTeamOnboarding = () => { | |||
this.setState({ modal: ModalKey.teamOnboarding }); | |||
}; | |||
tryAutoOpenLicense = () => { | |||
const { canAdmin, currentEdition, currentUser } = this.props; | |||
const hasLicenseManager = hasMessage('license.prompt.title'); | |||
@@ -124,7 +154,11 @@ export class StartupModal extends React.PureComponent<Props, State> { | |||
const { currentUser, location } = this.props; | |||
if (currentUser.showOnboardingTutorial && !location.pathname.startsWith('documentation')) { | |||
this.setState({ automatic: true }); | |||
this.openOnboarding(); | |||
if (isSonarCloud()) { | |||
this.openOnboarding(); | |||
} else { | |||
this.openProjectOnboarding(); | |||
} | |||
} | |||
}; | |||
@@ -135,7 +169,24 @@ export class StartupModal extends React.PureComponent<Props, State> { | |||
{this.props.children} | |||
{modal === ModalKey.license && <LicensePromptModal onClose={this.closeLicense} />} | |||
{modal === ModalKey.onboarding && ( | |||
<OnboardingModal automatic={automatic} onFinish={this.closeOnboarding} /> | |||
<Onboarding | |||
onFinish={this.closeOnboarding} | |||
onOpenOrganizationOnboarding={this.openOrganizationOnboarding} | |||
onOpenProjectOnboarding={this.openProjectOnboarding} | |||
onOpenTeamOnboarding={this.openTeamOnboarding} | |||
/> | |||
)} | |||
{modal === ModalKey.projectOnboarding && ( | |||
<ProjectOnboardingModal automatic={automatic} onFinish={this.closeOnboarding} /> | |||
)} | |||
{modal === ModalKey.organizationOnboarding && ( | |||
<CreateOrganizationForm | |||
onClose={this.closeOnboarding} | |||
onCreate={this.closeOrganizationOnboarding} | |||
/> | |||
)} | |||
{modal === ModalKey.teamOnboarding && ( | |||
<TeamOnboardingModal onFinish={this.closeOnboarding} /> | |||
)} | |||
</> | |||
); | |||
@@ -148,7 +199,7 @@ const mapStateToProps = (state: any): StateProps => ({ | |||
currentUser: getCurrentUser(state) | |||
}); | |||
const mapDispatchToProps: DispatchProps = { skipOnboarding }; | |||
const mapDispatchToProps: DispatchProps = { skipOnboardingAction }; | |||
export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)( | |||
StartupModal |
@@ -119,12 +119,12 @@ it('should render onboarding modal', async () => { | |||
async function shouldNotHaveModals(wrapper: ShallowWrapper) { | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.find('LicensePromptModal').exists()).toBeFalsy(); | |||
expect(wrapper.find('OnboardingModal').exists()).toBeFalsy(); | |||
expect(wrapper.find('ProjectOnboardingModal').exists()).toBeFalsy(); | |||
} | |||
async function shouldDisplayOnboarding(wrapper: ShallowWrapper) { | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.find('OnboardingModal').exists()).toBeTruthy(); | |||
expect(wrapper.find('ProjectOnboardingModal').exists()).toBeTruthy(); | |||
} | |||
async function shouldDisplayLicense(wrapper: ShallowWrapper) { | |||
@@ -139,9 +139,10 @@ function getWrapper(props = {}) { | |||
currentEdition={EditionKey.enterprise} | |||
currentUser={LOGGED_IN_USER} | |||
location={{ pathname: 'foo/bar' }} | |||
skipOnboarding={jest.fn()} | |||
skipOnboardingAction={jest.fn()} | |||
{...props}> | |||
<div /> | |||
</StartupModal> | |||
</StartupModal>, | |||
{ context: { router: { push: jest.fn() } } } | |||
); | |||
} |
@@ -36,13 +36,13 @@ interface Props { | |||
export default class EmbedDocsPopup extends React.PureComponent<Props> { | |||
static contextTypes = { | |||
openOnboardingTutorial: PropTypes.func | |||
openProjectOnboarding: PropTypes.func | |||
}; | |||
onAnalyzeProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
event.currentTarget.blur(); | |||
this.context.openOnboardingTutorial(); | |||
this.context.openProjectOnboarding(); | |||
}; | |||
renderTitle(text: string) { |
@@ -50,7 +50,7 @@ interface OwnProps { | |||
type Props = StateProps & OwnProps; | |||
class GlobalNav extends React.PureComponent<Props> { | |||
static contextTypes = { openOnboardingTutorial: PropTypes.func }; | |||
static contextTypes = { openProjectOnboarding: PropTypes.func }; | |||
render() { | |||
return ( | |||
@@ -69,7 +69,7 @@ class GlobalNav extends React.PureComponent<Props> { | |||
<Search appState={this.props.appState} currentUser={this.props.currentUser} /> | |||
{isLoggedIn(this.props.currentUser) && | |||
isSonarCloud() && ( | |||
<GlobalNavPlus openOnboardingTutorial={this.context.openOnboardingTutorial} /> | |||
<GlobalNavPlus openProjectOnboarding={this.context.openProjectOnboarding} /> | |||
)} | |||
<GlobalNavUserContainer {...this.props} /> | |||
</ul> |
@@ -25,7 +25,7 @@ import Dropdown from '../../../../components/controls/Dropdown'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
interface Props { | |||
openOnboardingTutorial: () => void; | |||
openProjectOnboarding: () => void; | |||
} | |||
interface State { | |||
@@ -44,7 +44,7 @@ export default class GlobalNavPlus extends React.PureComponent<Props, State> { | |||
handleNewProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
this.props.openOnboardingTutorial(); | |||
this.props.openProjectOnboarding(); | |||
}; | |||
openCreateOrganizationForm = () => this.setState({ createOrganization: true }); |
@@ -23,18 +23,18 @@ import GlobalNavPlus from '../GlobalNavPlus'; | |||
import { click } from '../../../../../helpers/testUtils'; | |||
it('render', () => { | |||
const wrapper = shallow(<GlobalNavPlus openOnboardingTutorial={jest.fn()} />); | |||
const wrapper = shallow(<GlobalNavPlus openProjectOnboarding={jest.fn()} />); | |||
expect(wrapper.is('Dropdown')).toBe(true); | |||
expect(wrapper.find('Dropdown')).toMatchSnapshot(); | |||
}); | |||
it('opens onboarding', () => { | |||
const openOnboardingTutorial = jest.fn(); | |||
const openProjectOnboarding = jest.fn(); | |||
const wrapper = shallow( | |||
shallow(<GlobalNavPlus openOnboardingTutorial={openOnboardingTutorial} />) | |||
shallow(<GlobalNavPlus openProjectOnboarding={openProjectOnboarding} />) | |||
.find('Dropdown') | |||
.prop('overlay') | |||
); | |||
click(wrapper.find('.js-new-project')); | |||
expect(openOnboardingTutorial).toBeCalled(); | |||
expect(openProjectOnboarding).toBeCalled(); | |||
}); |
@@ -172,7 +172,7 @@ const startReactApp = () => { | |||
<Route | |||
path="onboarding" | |||
component={lazyLoad(() => | |||
import('../../apps/tutorials/onboarding/OnboardingPage') | |||
import('../../apps/tutorials/projectOnboarding/ProjectOnboardingPage') | |||
)} | |||
/> | |||
<Route path="organizations" childRoutes={organizationsRoutes} /> |
@@ -36,13 +36,13 @@ interface StateProps { | |||
export class NoFavoriteProjects extends React.PureComponent<StateProps> { | |||
static contextTypes = { | |||
openOnboardingTutorial: PropTypes.func | |||
openProjectOnboarding: PropTypes.func | |||
}; | |||
onAnalyzeProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
event.currentTarget.blur(); | |||
this.context.openOnboardingTutorial(); | |||
this.context.openProjectOnboarding(); | |||
}; | |||
render() { |
@@ -0,0 +1,102 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { connect } from 'react-redux'; | |||
import handleRequiredAuthentication from '../../app/utils/handleRequiredAuthentication'; | |||
import Modal from '../../components/controls/Modal'; | |||
import { ResetButtonLink, Button } from '../../components/ui/buttons'; | |||
import { translate } from '../../helpers/l10n'; | |||
import { CurrentUser, isLoggedIn } from '../../app/types'; | |||
import { getCurrentUser } from '../../store/rootReducer'; | |||
import './styles.css'; | |||
interface OwnProps { | |||
onFinish: () => void; | |||
onOpenOrganizationOnboarding: () => void; | |||
onOpenProjectOnboarding: () => void; | |||
onOpenTeamOnboarding: () => void; | |||
} | |||
interface StateProps { | |||
currentUser: CurrentUser; | |||
} | |||
type Props = OwnProps & StateProps; | |||
export class Onboarding extends React.PureComponent<Props> { | |||
componentDidMount() { | |||
if (!isLoggedIn(this.props.currentUser)) { | |||
handleRequiredAuthentication(); | |||
} | |||
} | |||
render() { | |||
if (!isLoggedIn(this.props.currentUser)) { | |||
return null; | |||
} | |||
const header = translate('onboarding.header'); | |||
return ( | |||
<Modal | |||
contentLabel={header} | |||
medium={true} | |||
onRequestClose={this.props.onFinish} | |||
shouldCloseOnOverlayClick={false}> | |||
<header className="modal-head"> | |||
<h2>{header}</h2> | |||
</header> | |||
<div className="modal-body"> | |||
<p className="spacer-top big-spacer-bottom"> | |||
{translate('onboarding.header.description')} | |||
</p> | |||
<ul className="onboarding-choices"> | |||
<li className="text-center"> | |||
<p className="big-spacer-bottom">{translate('onboarding.analyze_public_code')}</p> | |||
<Button onClick={this.props.onOpenProjectOnboarding}> | |||
{translate('onboarding.analyze_public_code.button')} | |||
</Button> | |||
</li> | |||
<li className="text-center"> | |||
<p className="big-spacer-bottom">{translate('onboarding.analyze_private_code')}</p> | |||
<Button onClick={this.props.onOpenOrganizationOnboarding}> | |||
{translate('onboarding.analyze_private_code.button')} | |||
</Button> | |||
</li> | |||
<li className="text-center"> | |||
<p className="big-spacer-bottom"> | |||
{translate('onboarding.contribute_existing_project')} | |||
</p> | |||
<Button onClick={this.props.onOpenTeamOnboarding}> | |||
{translate('onboarding.contribute_existing_project.button')} | |||
</Button> | |||
</li> | |||
</ul> | |||
</div> | |||
<footer className="modal-foot"> | |||
<ResetButtonLink onClick={this.props.onFinish}>{translate('close')}</ResetButtonLink> | |||
</footer> | |||
</Modal> | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state: any): StateProps => ({ currentUser: getCurrentUser(state) }); | |||
export default connect<StateProps, {}, OwnProps>(mapStateToProps)(Onboarding); |
@@ -0,0 +1,61 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { shallow } from 'enzyme'; | |||
import { Onboarding } from '../Onboarding'; | |||
import { click } from '../../../helpers/testUtils'; | |||
it('renders correctly', () => { | |||
expect( | |||
shallow( | |||
<Onboarding | |||
currentUser={{ isLoggedIn: true }} | |||
onFinish={jest.fn()} | |||
onOpenOrganizationOnboarding={jest.fn()} | |||
onOpenProjectOnboarding={jest.fn()} | |||
onOpenTeamOnboarding={jest.fn()} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should correctly open the different tutorials', () => { | |||
const onFinish = jest.fn(); | |||
const onOpenOrganizationOnboarding = jest.fn(); | |||
const onOpenProjectOnboarding = jest.fn(); | |||
const onOpenTeamOnboarding = jest.fn(); | |||
const wrapper = shallow( | |||
<Onboarding | |||
currentUser={{ isLoggedIn: true }} | |||
onFinish={onFinish} | |||
onOpenOrganizationOnboarding={onOpenOrganizationOnboarding} | |||
onOpenProjectOnboarding={onOpenProjectOnboarding} | |||
onOpenTeamOnboarding={onOpenTeamOnboarding} | |||
/> | |||
); | |||
click(wrapper.find('ResetButtonLink')); | |||
expect(onFinish).toHaveBeenCalled(); | |||
wrapper.find('Button').forEach(button => click(button)); | |||
expect(onOpenOrganizationOnboarding).toHaveBeenCalled(); | |||
expect(onOpenProjectOnboarding).toHaveBeenCalled(); | |||
expect(onOpenTeamOnboarding).toHaveBeenCalled(); | |||
}); |
@@ -0,0 +1,82 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders correctly 1`] = ` | |||
<Modal | |||
contentLabel="onboarding.header" | |||
medium={true} | |||
onRequestClose={[MockFunction]} | |||
shouldCloseOnOverlayClick={false} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
onboarding.header | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body" | |||
> | |||
<p | |||
className="spacer-top big-spacer-bottom" | |||
> | |||
onboarding.header.description | |||
</p> | |||
<ul | |||
className="onboarding-choices" | |||
> | |||
<li | |||
className="text-center" | |||
> | |||
<p | |||
className="big-spacer-bottom" | |||
> | |||
onboarding.analyze_public_code | |||
</p> | |||
<Button | |||
onClick={[MockFunction]} | |||
> | |||
onboarding.analyze_public_code.button | |||
</Button> | |||
</li> | |||
<li | |||
className="text-center" | |||
> | |||
<p | |||
className="big-spacer-bottom" | |||
> | |||
onboarding.analyze_private_code | |||
</p> | |||
<Button | |||
onClick={[MockFunction]} | |||
> | |||
onboarding.analyze_private_code.button | |||
</Button> | |||
</li> | |||
<li | |||
className="text-center" | |||
> | |||
<p | |||
className="big-spacer-bottom" | |||
> | |||
onboarding.contribute_existing_project | |||
</p> | |||
<Button | |||
onClick={[MockFunction]} | |||
> | |||
onboarding.contribute_existing_project.button | |||
</Button> | |||
</li> | |||
</ul> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
close | |||
</ResetButtonLink> | |||
</footer> | |||
</Modal> | |||
`; |
@@ -1,236 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import Helmet from 'react-helmet'; | |||
import TokenStep from './TokenStep'; | |||
import OrganizationStep from './OrganizationStep'; | |||
import AnalysisStep from './AnalysisStep'; | |||
import ProjectWatcher from './ProjectWatcher'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import InstanceMessage from '../../../components/common/InstanceMessage'; | |||
import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; | |||
import { skipOnboarding } from '../../../api/users'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { isSonarCloud } from '../../../helpers/system'; | |||
import './styles.css'; | |||
/*:: | |||
type Props = {| | |||
automatic?:boolean, | |||
className?: string, | |||
currentUser: { login: string, isLoggedIn: boolean }, | |||
onFinish: () => void, | |||
organizationsEnabled: boolean | |||
|}; | |||
*/ | |||
/*:: | |||
type State = { | |||
finished: boolean, | |||
organization?: string, | |||
projectKey?: string, | |||
skipping: boolean, | |||
step: string, | |||
token?: string | |||
}; | |||
*/ | |||
export default class Onboarding extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
/*:: state: State; */ | |||
static contextTypes = { | |||
router: PropTypes.object | |||
}; | |||
constructor(props /*: Props */) { | |||
super(props); | |||
this.state = { | |||
finished: false, | |||
skipping: false, | |||
step: props.organizationsEnabled ? 'organization' : 'token' | |||
}; | |||
} | |||
componentDidMount() { | |||
this.mounted = true; | |||
// useCapture = true to receive the event before inputs | |||
window.addEventListener('keydown', this.onKeyDown, true); | |||
if (!this.props.currentUser.isLoggedIn) { | |||
handleRequiredAuthentication(); | |||
} | |||
} | |||
componentWillUnmount() { | |||
window.removeEventListener('keydown', this.onKeyDown, true); | |||
this.mounted = false; | |||
} | |||
onKeyDown = (event /*: KeyboardEvent */) => { | |||
// ESC key | |||
if (event.keyCode === 27) { | |||
this.finishOnboarding(); | |||
} | |||
}; | |||
finishOnboarding = () => { | |||
this.setState({ skipping: true }); | |||
skipOnboarding().then( | |||
() => { | |||
if (this.mounted) { | |||
this.props.onFinish(); | |||
if (this.state.projectKey) { | |||
this.context.router.push(getProjectUrl(this.state.projectKey)); | |||
} | |||
} | |||
}, | |||
() => { | |||
if (this.mounted) { | |||
this.setState({ skipping: false }); | |||
} | |||
} | |||
); | |||
}; | |||
handleTimeout = () => { | |||
// unset `projectKey` to display a generic "Finish this tutorial" button | |||
this.setState({ projectKey: undefined }); | |||
}; | |||
handleTokenDone = (token /*: string */) => { | |||
this.setState({ step: 'analysis', token }); | |||
}; | |||
handleOrganizationDone = (organization /*: string */) => { | |||
this.setState({ organization, step: 'token' }); | |||
}; | |||
handleTokenOpen = () => this.setState({ step: 'token' }); | |||
handleOrganizationOpen = () => this.setState({ step: 'organization' }); | |||
handleSkipClick = (event /*: Event */) => { | |||
event.preventDefault(); | |||
this.finishOnboarding(); | |||
}; | |||
handleFinish = (projectKey /*: string | void */) => this.setState({ finished: true, projectKey }); | |||
handleReset = () => this.setState({ finished: false, projectKey: undefined }); | |||
render() { | |||
if (!this.props.currentUser.isLoggedIn) { | |||
return null; | |||
} | |||
const { automatic, organizationsEnabled } = this.props; | |||
const { step, token } = this.state; | |||
let stepNumber = 1; | |||
return ( | |||
<div className={this.props.className}> | |||
<InstanceMessage message={translate('onboarding.header')}> | |||
{transformedMessage => <Helmet title={transformedMessage} titleTemplate="%s" />} | |||
</InstanceMessage> | |||
<div className="page page-limited onboarding"> | |||
<header className="page-header"> | |||
<h1 className="page-title"> | |||
<InstanceMessage message={translate('onboarding.header')} /> | |||
</h1> | |||
<div className="page-actions"> | |||
<DeferredSpinner loading={this.state.skipping}> | |||
<a className="js-skip text-muted" href="#" onClick={this.handleSkipClick}> | |||
{automatic ? translate('tutorials.skip') : translate('close')} | |||
</a> | |||
</DeferredSpinner> | |||
<p className="note"> | |||
{translate( | |||
isSonarCloud() | |||
? 'tutorials.find_it_back_in_plus' | |||
: 'tutorials.find_it_back_in_help' | |||
)} | |||
</p> | |||
</div> | |||
<div className="page-description"> | |||
{translateWithParameters( | |||
'onboarding.header.description', | |||
organizationsEnabled ? 3 : 2 | |||
)} | |||
</div> | |||
</header> | |||
{organizationsEnabled && ( | |||
<OrganizationStep | |||
currentUser={this.props.currentUser} | |||
finished={this.state.organization != null} | |||
onContinue={this.handleOrganizationDone} | |||
onOpen={this.handleOrganizationOpen} | |||
open={step === 'organization'} | |||
stepNumber={stepNumber++} | |||
/> | |||
)} | |||
<TokenStep | |||
currentUser={this.props.currentUser} | |||
finished={this.state.token != null} | |||
onContinue={this.handleTokenDone} | |||
onOpen={this.handleTokenOpen} | |||
open={step === 'token'} | |||
stepNumber={stepNumber++} | |||
/> | |||
<AnalysisStep | |||
onFinish={this.handleFinish} | |||
onReset={this.handleReset} | |||
open={step === 'analysis'} | |||
organization={this.state.organization} | |||
stepNumber={stepNumber} | |||
token={token} | |||
/> | |||
{this.state.finished && | |||
!this.state.skipping && | |||
(this.state.projectKey ? ( | |||
<ProjectWatcher | |||
onFinish={this.finishOnboarding} | |||
onTimeout={this.handleTimeout} | |||
projectKey={this.state.projectKey} | |||
/> | |||
) : ( | |||
<footer className="text-right"> | |||
<a className="button" href="#" onClick={this.handleSkipClick}> | |||
{translate('tutorials.finish')} | |||
</a> | |||
</footer> | |||
))} | |||
</div> | |||
</div> | |||
); | |||
} | |||
} |
@@ -1,33 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
// @flow | |||
import { connect } from 'react-redux'; | |||
import Onboarding from './Onboarding'; | |||
import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer'; | |||
const mapStateToProps = state => { | |||
return { | |||
className: 'modal-container', | |||
currentUser: getCurrentUser(state), | |||
organizationsEnabled: areThereCustomOrganizations(state) | |||
}; | |||
}; | |||
export default connect(mapStateToProps)(Onboarding); |
@@ -1,388 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`guides for on-premise 1`] = ` | |||
<div | |||
className="modal-container" | |||
> | |||
<InstanceMessage | |||
message="onboarding.header" | |||
/> | |||
<div | |||
className="page page-limited onboarding" | |||
> | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
<InstanceMessage | |||
message="onboarding.header" | |||
/> | |||
</h1> | |||
<div | |||
className="page-actions" | |||
> | |||
<DeferredSpinner | |||
loading={false} | |||
timeout={100} | |||
> | |||
<a | |||
className="js-skip text-muted" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
close | |||
</a> | |||
</DeferredSpinner> | |||
<p | |||
className="note" | |||
> | |||
tutorials.find_it_back_in_help | |||
</p> | |||
</div> | |||
<div | |||
className="page-description" | |||
> | |||
onboarding.header.description.2 | |||
</div> | |||
</header> | |||
<TokenStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={false} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={true} | |||
stepNumber={1} | |||
/> | |||
<AnalysisStep | |||
onFinish={[Function]} | |||
onReset={[Function]} | |||
open={false} | |||
stepNumber={2} | |||
/> | |||
</div> | |||
</div> | |||
`; | |||
exports[`guides for on-premise 2`] = ` | |||
<div | |||
className="modal-container" | |||
> | |||
<InstanceMessage | |||
message="onboarding.header" | |||
/> | |||
<div | |||
className="page page-limited onboarding" | |||
> | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
<InstanceMessage | |||
message="onboarding.header" | |||
/> | |||
</h1> | |||
<div | |||
className="page-actions" | |||
> | |||
<DeferredSpinner | |||
loading={false} | |||
timeout={100} | |||
> | |||
<a | |||
className="js-skip text-muted" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
close | |||
</a> | |||
</DeferredSpinner> | |||
<p | |||
className="note" | |||
> | |||
tutorials.find_it_back_in_help | |||
</p> | |||
</div> | |||
<div | |||
className="page-description" | |||
> | |||
onboarding.header.description.2 | |||
</div> | |||
</header> | |||
<TokenStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={true} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={false} | |||
stepNumber={1} | |||
/> | |||
<AnalysisStep | |||
onFinish={[Function]} | |||
onReset={[Function]} | |||
open={true} | |||
stepNumber={2} | |||
token="abcd1234" | |||
/> | |||
</div> | |||
</div> | |||
`; | |||
exports[`guides for sonarcloud 1`] = ` | |||
<div> | |||
<InstanceMessage | |||
message="onboarding.header" | |||
/> | |||
<div | |||
className="page page-limited onboarding" | |||
> | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
<InstanceMessage | |||
message="onboarding.header" | |||
/> | |||
</h1> | |||
<div | |||
className="page-actions" | |||
> | |||
<DeferredSpinner | |||
loading={false} | |||
timeout={100} | |||
> | |||
<a | |||
className="js-skip text-muted" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
close | |||
</a> | |||
</DeferredSpinner> | |||
<p | |||
className="note" | |||
> | |||
tutorials.find_it_back_in_plus | |||
</p> | |||
</div> | |||
<div | |||
className="page-description" | |||
> | |||
onboarding.header.description.3 | |||
</div> | |||
</header> | |||
<OrganizationStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={false} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={true} | |||
stepNumber={1} | |||
/> | |||
<TokenStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={false} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={false} | |||
stepNumber={2} | |||
/> | |||
<AnalysisStep | |||
onFinish={[Function]} | |||
onReset={[Function]} | |||
open={false} | |||
stepNumber={3} | |||
/> | |||
</div> | |||
</div> | |||
`; | |||
exports[`guides for sonarcloud 2`] = ` | |||
<div> | |||
<InstanceMessage | |||
message="onboarding.header" | |||
/> | |||
<div | |||
className="page page-limited onboarding" | |||
> | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
<InstanceMessage | |||
message="onboarding.header" | |||
/> | |||
</h1> | |||
<div | |||
className="page-actions" | |||
> | |||
<DeferredSpinner | |||
loading={false} | |||
timeout={100} | |||
> | |||
<a | |||
className="js-skip text-muted" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
close | |||
</a> | |||
</DeferredSpinner> | |||
<p | |||
className="note" | |||
> | |||
tutorials.find_it_back_in_plus | |||
</p> | |||
</div> | |||
<div | |||
className="page-description" | |||
> | |||
onboarding.header.description.3 | |||
</div> | |||
</header> | |||
<OrganizationStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={true} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={false} | |||
stepNumber={1} | |||
/> | |||
<TokenStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={false} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={true} | |||
stepNumber={2} | |||
/> | |||
<AnalysisStep | |||
onFinish={[Function]} | |||
onReset={[Function]} | |||
open={false} | |||
organization="my-org" | |||
stepNumber={3} | |||
/> | |||
</div> | |||
</div> | |||
`; | |||
exports[`guides for sonarcloud 3`] = ` | |||
<div> | |||
<InstanceMessage | |||
message="onboarding.header" | |||
/> | |||
<div | |||
className="page page-limited onboarding" | |||
> | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
<InstanceMessage | |||
message="onboarding.header" | |||
/> | |||
</h1> | |||
<div | |||
className="page-actions" | |||
> | |||
<DeferredSpinner | |||
loading={false} | |||
timeout={100} | |||
> | |||
<a | |||
className="js-skip text-muted" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
close | |||
</a> | |||
</DeferredSpinner> | |||
<p | |||
className="note" | |||
> | |||
tutorials.find_it_back_in_plus | |||
</p> | |||
</div> | |||
<div | |||
className="page-description" | |||
> | |||
onboarding.header.description.3 | |||
</div> | |||
</header> | |||
<OrganizationStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={true} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={false} | |||
stepNumber={1} | |||
/> | |||
<TokenStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={true} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={false} | |||
stepNumber={2} | |||
/> | |||
<AnalysisStep | |||
onFinish={[Function]} | |||
onReset={[Function]} | |||
open={true} | |||
organization="my-org" | |||
stepNumber={3} | |||
token="abcd1234" | |||
/> | |||
</div> | |||
</div> | |||
`; |
@@ -17,11 +17,9 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import Step from './Step'; | |||
import LanguageStep from './LanguageStep'; | |||
/*:: import type { Result } from './LanguageStep'; */ | |||
import LanguageStep, { Result } from './LanguageStep'; | |||
import JavaMaven from './commands/JavaMaven'; | |||
import JavaGradle from './commands/JavaGradle'; | |||
import DotNet from './commands/DotNet'; | |||
@@ -31,28 +29,23 @@ import Other from './commands/Other'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getHostUrl } from '../../../helpers/urls'; | |||
/*:: | |||
type Props = {| | |||
onFinish: (projectKey?: string) => void, | |||
onReset: () => void, | |||
open: boolean, | |||
organization?: string, | |||
stepNumber: number, | |||
token: string | |||
|}; | |||
*/ | |||
/*:: | |||
type State = { | |||
result?: Result | |||
}; | |||
*/ | |||
export default class AnalysisStep extends React.PureComponent { | |||
/*:: props: Props; */ | |||
state /*: State */ = {}; | |||
handleLanguageSelect = (result /*: Result | void */) => { | |||
interface Props { | |||
onFinish: (projectKey?: string) => void; | |||
onReset: () => void; | |||
open: boolean; | |||
organization?: string; | |||
stepNumber: number; | |||
token?: string; | |||
} | |||
interface State { | |||
result?: Result; | |||
} | |||
export default class AnalysisStep extends React.PureComponent<Props, State> { | |||
state: State = {}; | |||
handleLanguageSelect = (result?: Result) => { | |||
this.setState({ result }); | |||
const projectKey = result && result.language !== 'java' ? result.projectKey : undefined; | |||
this.props.onFinish(projectKey); | |||
@@ -80,7 +73,7 @@ export default class AnalysisStep extends React.PureComponent { | |||
); | |||
}; | |||
renderFormattedCommand = (...lines /*: Array<string> */) => ( | |||
renderFormattedCommand = (...lines: Array<string>) => ( | |||
// keep this "useless" concatentation for the readability reason | |||
// eslint-disable-next-line no-useless-concat | |||
<pre>{lines.join(' ' + '\\' + '\n' + ' ')}</pre> | |||
@@ -108,69 +101,87 @@ export default class AnalysisStep extends React.PureComponent { | |||
} | |||
}; | |||
renderCommandForMaven = () => ( | |||
<JavaMaven | |||
host={getHostUrl()} | |||
organization={this.props.organization} | |||
token={this.props.token} | |||
/> | |||
); | |||
renderCommandForMaven = () => { | |||
const { token } = this.props; | |||
if (!token) { | |||
return null; | |||
} | |||
return <JavaMaven host={getHostUrl()} organization={this.props.organization} token={token} />; | |||
}; | |||
renderCommandForGradle = () => ( | |||
<JavaGradle | |||
host={getHostUrl()} | |||
organization={this.props.organization} | |||
token={this.props.token} | |||
/> | |||
); | |||
renderCommandForGradle = () => { | |||
const { token } = this.props; | |||
if (!token) { | |||
return null; | |||
} | |||
return <JavaGradle host={getHostUrl()} organization={this.props.organization} token={token} />; | |||
}; | |||
renderCommandForDotNet = () => { | |||
const { token } = this.props; | |||
const { result } = this.state; | |||
if (!result || !result.projectKey || !token) { | |||
return null; | |||
} | |||
return ( | |||
<DotNet | |||
host={getHostUrl()} | |||
organization={this.props.organization} | |||
// $FlowFixMe | |||
projectKey={this.state.result.projectKey} | |||
token={this.props.token} | |||
projectKey={result.projectKey} | |||
token={token} | |||
/> | |||
); | |||
}; | |||
renderCommandForMSVC = () => { | |||
const { token } = this.props; | |||
const { result } = this.state; | |||
if (!result || !result.projectKey || !token) { | |||
return null; | |||
} | |||
return ( | |||
<Msvc | |||
host={getHostUrl()} | |||
organization={this.props.organization} | |||
// $FlowFixMe | |||
projectKey={this.state.result.projectKey} | |||
token={this.props.token} | |||
projectKey={result.projectKey} | |||
token={token} | |||
/> | |||
); | |||
}; | |||
renderCommandForClangGCC = () => ( | |||
<ClangGCC | |||
host={getHostUrl()} | |||
organization={this.props.organization} | |||
// $FlowFixMe | |||
os={this.state.result.os} | |||
// $FlowFixMe | |||
projectKey={this.state.result.projectKey} | |||
token={this.props.token} | |||
/> | |||
); | |||
renderCommandForClangGCC = () => { | |||
const { token } = this.props; | |||
const { result } = this.state; | |||
if (!result || !result.projectKey || !result.os || !token) { | |||
return null; | |||
} | |||
return ( | |||
<ClangGCC | |||
host={getHostUrl()} | |||
organization={this.props.organization} | |||
os={result.os} | |||
projectKey={result.projectKey} | |||
token={token} | |||
/> | |||
); | |||
}; | |||
renderCommandForOther = () => ( | |||
<Other | |||
host={getHostUrl()} | |||
organization={this.props.organization} | |||
// $FlowFixMe | |||
os={this.state.result.os} | |||
// $FlowFixMe | |||
projectKey={this.state.result.projectKey} | |||
token={this.props.token} | |||
/> | |||
); | |||
renderCommandForOther = () => { | |||
const { token } = this.props; | |||
const { result } = this.state; | |||
if (!result || !result.projectKey || !result.os || !token) { | |||
return null; | |||
} | |||
return ( | |||
<Other | |||
host={getHostUrl()} | |||
organization={this.props.organization} | |||
os={result.os} | |||
projectKey={result.projectKey} | |||
token={token} | |||
/> | |||
); | |||
}; | |||
renderResult = () => null; | |||
@@ -17,38 +17,31 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import NewProjectForm from './NewProjectForm'; | |||
import RadioToggle from '../../../components/controls/RadioToggle'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { isSonarCloud } from '../../../helpers/system'; | |||
/*:: | |||
type Props = {| | |||
onDone: (result: Result) => void, | |||
onReset: () => void, | |||
organization?: string, | |||
|}; | |||
*/ | |||
/*:: | |||
type State = { | |||
language?: string, | |||
javaBuild?: string, | |||
cFamilyCompiler?: string, | |||
os?: string, | |||
projectKey?: string | |||
}; | |||
*/ | |||
export interface Result { | |||
language?: string; | |||
javaBuild?: string; | |||
cFamilyCompiler?: string; | |||
os?: string; | |||
projectKey?: string; | |||
} | |||
/*:: | |||
export type Result = State; */ | |||
interface Props { | |||
onDone: (result: Result) => void; | |||
onReset: () => void; | |||
organization?: string; | |||
} | |||
export default class LanguageStep extends React.PureComponent { | |||
/*:: props: Props; */ | |||
type State = Result; | |||
state /*: State */ = {}; | |||
export default class LanguageStep extends React.PureComponent<Props, State> { | |||
state: State = {}; | |||
isConfigured = () => { | |||
const { language, javaBuild, cFamilyCompiler, os, projectKey } = this.state; | |||
@@ -69,23 +62,23 @@ export default class LanguageStep extends React.PureComponent { | |||
} | |||
}; | |||
handleLanguageChange = (language /*: string */) => { | |||
handleLanguageChange = (language: string) => { | |||
this.setState({ language }, this.handleChange); | |||
}; | |||
handleJavaBuildChange = (javaBuild /*: string */) => { | |||
handleJavaBuildChange = (javaBuild: string) => { | |||
this.setState({ javaBuild }, this.handleChange); | |||
}; | |||
handleCFamilyCompilerChange = (cFamilyCompiler /*: string */) => { | |||
handleCFamilyCompilerChange = (cFamilyCompiler: string) => { | |||
this.setState({ cFamilyCompiler }, this.handleChange); | |||
}; | |||
handleOSChange = (os /*: string */) => { | |||
handleOSChange = (os: string) => { | |||
this.setState({ os }, this.handleChange); | |||
}; | |||
handleProjectKeyDone = (projectKey /*: string */) => { | |||
handleProjectKeyDone = (projectKey: string) => { | |||
this.setState({ projectKey }, this.handleChange); | |||
}; | |||
@@ -17,8 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { debounce } from 'lodash'; | |||
import { | |||
createOrganization, | |||
@@ -29,29 +28,23 @@ import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon' | |||
import { DeleteButton, SubmitButton } from '../../../components/ui/buttons'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = {| | |||
onDelete: () => void, | |||
onDone: (organization: string) => void, | |||
organization?: string | |||
|}; | |||
*/ | |||
interface Props { | |||
onDelete: () => void; | |||
onDone: (organization: string) => void; | |||
organization?: string; | |||
} | |||
/*:: | |||
type State = { | |||
done: boolean, | |||
loading: boolean, | |||
organization: string, | |||
unique: boolean | |||
}; | |||
*/ | |||
interface State { | |||
done: boolean; | |||
loading: boolean; | |||
organization: string; | |||
unique: boolean; | |||
} | |||
export default class NewOrganizationForm extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
/*:: state: State; */ | |||
export default class NewOrganizationForm extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
constructor(props /*: Props */) { | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { | |||
done: props.organization != null, | |||
@@ -76,7 +69,7 @@ export default class NewOrganizationForm extends React.PureComponent { | |||
} | |||
}; | |||
validateOrganization = (organization /*: string */) => { | |||
validateOrganization = (organization: string) => { | |||
getOrganization(organization).then(response => { | |||
if (this.mounted) { | |||
this.setState({ unique: response == null }); | |||
@@ -84,19 +77,19 @@ export default class NewOrganizationForm extends React.PureComponent { | |||
}); | |||
}; | |||
sanitizeOrganization = (organization /*: string */) => | |||
sanitizeOrganization = (organization: string) => | |||
organization | |||
.toLowerCase() | |||
.replace(/[^a-z0-9-]/, '') | |||
.replace(/^-/, ''); | |||
handleOrganizationChange = (event /*: { target: HTMLInputElement } */) => { | |||
handleOrganizationChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
const organization = this.sanitizeOrganization(event.target.value); | |||
this.setState({ organization }); | |||
this.validateOrganization(organization); | |||
}; | |||
handleOrganizationCreate = (event /*: Event */) => { | |||
handleOrganizationCreate = (event: React.FormEvent<HTMLFormElement>) => { | |||
event.preventDefault(); | |||
const { organization } = this.state; | |||
if (organization) { |
@@ -17,35 +17,28 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { createProject, deleteProject } from '../../../api/components'; | |||
import { DeleteButton, SubmitButton } from '../../../components/ui/buttons'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = {| | |||
onDelete: () => void, | |||
onDone: (projectKey: string) => void, | |||
organization?: string, | |||
projectKey?: string | |||
|}; | |||
*/ | |||
interface Props { | |||
onDelete: () => void; | |||
onDone: (projectKey: string) => void; | |||
organization?: string; | |||
projectKey?: string; | |||
} | |||
/*:: | |||
type State = { | |||
done: boolean, | |||
loading: boolean, | |||
projectKey: string | |||
}; | |||
*/ | |||
interface State { | |||
done: boolean; | |||
loading: boolean; | |||
projectKey: string; | |||
} | |||
export default class NewProjectForm extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
/*:: state: State; */ | |||
export default class NewProjectForm extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
constructor(props /*: Props */) { | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { | |||
done: props.projectKey != null, | |||
@@ -68,16 +61,20 @@ export default class NewProjectForm extends React.PureComponent { | |||
} | |||
}; | |||
sanitizeProjectKey = (projectKey /*: string */) => projectKey.replace(/[^-_a-zA-Z0-9.:]/, ''); | |||
sanitizeProjectKey = (projectKey: string) => projectKey.replace(/[^-_a-zA-Z0-9.:]/, ''); | |||
handleProjectKeyChange = (event /*: { target: HTMLInputElement } */) => { | |||
handleProjectKeyChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
this.setState({ projectKey: this.sanitizeProjectKey(event.target.value) }); | |||
}; | |||
handleProjectCreate = (event /*: Event */) => { | |||
handleProjectCreate = (event: React.FormEvent<HTMLFormElement>) => { | |||
event.preventDefault(); | |||
const { projectKey } = this.state; | |||
const data /*: { [string]: string } */ = { | |||
const data: { | |||
name: string; | |||
project: string; | |||
organization?: string; | |||
} = { | |||
name: projectKey, | |||
project: projectKey | |||
}; |
@@ -17,9 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import { sortBy } from 'lodash'; | |||
import Step from './Step'; | |||
import NewOrganizationForm from './NewOrganizationForm'; | |||
@@ -30,32 +29,27 @@ import Select from '../../../components/controls/Select'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { Button } from '../../../components/ui/buttons'; | |||
/*:: | |||
type Props = {| | |||
currentUser: { login: string, isLoggedIn: boolean }, | |||
finished: boolean, | |||
onOpen: () => void, | |||
onContinue: (organization: string) => void, | |||
open: boolean, | |||
stepNumber: number | |||
|}; | |||
*/ | |||
interface Props { | |||
currentUser: { login: string; isLoggedIn: boolean }; | |||
finished: boolean; | |||
onOpen: () => void; | |||
onContinue: (organization: string) => void; | |||
open: boolean; | |||
stepNumber: number; | |||
} | |||
/*:: | |||
type State = { | |||
loading: boolean, | |||
newOrganization?: string, | |||
existingOrganization?: string, | |||
existingOrganizations: Array<string>, | |||
personalOrganization?: string, | |||
selection: 'personal' | 'existing' | 'new' | |||
}; | |||
*/ | |||
interface State { | |||
loading: boolean; | |||
newOrganization?: string; | |||
existingOrganization?: string; | |||
existingOrganizations: Array<string>; | |||
personalOrganization?: string; | |||
selection: 'personal' | 'existing' | 'new'; | |||
} | |||
export default class OrganizationStep extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
state /*: State */ = { | |||
export default class OrganizationStep extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { | |||
loading: true, | |||
existingOrganizations: [], | |||
selection: 'personal' | |||
@@ -112,22 +106,22 @@ export default class OrganizationStep extends React.PureComponent { | |||
} | |||
}; | |||
handlePersonalClick = (event /*: Event */) => { | |||
handlePersonalClick = (event: React.MouseEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
this.setState({ selection: 'personal' }); | |||
}; | |||
handleExistingClick = (event /*: Event */) => { | |||
handleExistingClick = (event: React.MouseEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
this.setState({ selection: 'existing' }); | |||
}; | |||
handleNewClick = (event /*: Event */) => { | |||
handleNewClick = (event: React.MouseEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
this.setState({ selection: 'new' }); | |||
}; | |||
handleOrganizationCreate = (newOrganization /*: string */) => { | |||
handleOrganizationCreate = (newOrganization: string) => { | |||
this.setState({ newOrganization }); | |||
}; | |||
@@ -135,7 +129,7 @@ export default class OrganizationStep extends React.PureComponent { | |||
this.setState({ newOrganization: undefined }); | |||
}; | |||
handleExistingOrganizationSelect = ({ value } /*: { value: string } */) => { | |||
handleExistingOrganizationSelect = ({ value }: { value: string }) => { | |||
this.setState({ existingOrganization: value }); | |||
}; | |||
@@ -0,0 +1,206 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 * as PropTypes from 'prop-types'; | |||
import Helmet from 'react-helmet'; | |||
import { connect } from 'react-redux'; | |||
import TokenStep from './TokenStep'; | |||
import OrganizationStep from './OrganizationStep'; | |||
import AnalysisStep from './AnalysisStep'; | |||
import ProjectWatcher from './ProjectWatcher'; | |||
import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; | |||
import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer'; | |||
import { CurrentUser, isLoggedIn } from '../../../app/types'; | |||
import { ResetButtonLink } from '../../../components/ui/buttons'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { isSonarCloud } from '../../../helpers/system'; | |||
import '../styles.css'; | |||
interface OwnProps { | |||
automatic?: boolean; | |||
onFinish: () => void; | |||
} | |||
interface StateProps { | |||
currentUser: CurrentUser; | |||
organizationsEnabled: boolean; | |||
} | |||
type Props = OwnProps & StateProps; | |||
interface State { | |||
finished: boolean; | |||
organization?: string; | |||
projectKey?: string; | |||
step: string; | |||
token?: string; | |||
} | |||
export class ProjectOnboarding extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
static contextTypes = { | |||
router: PropTypes.object | |||
}; | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { | |||
finished: false, | |||
step: props.organizationsEnabled ? 'organization' : 'token' | |||
}; | |||
} | |||
componentDidMount() { | |||
this.mounted = true; | |||
// useCapture = true to receive the event before inputs | |||
window.addEventListener('keydown', this.onKeyDown, true); | |||
if (!isLoggedIn(this.props.currentUser)) { | |||
handleRequiredAuthentication(); | |||
} | |||
} | |||
componentWillUnmount() { | |||
window.removeEventListener('keydown', this.onKeyDown, true); | |||
this.mounted = false; | |||
} | |||
onKeyDown = (event: KeyboardEvent) => { | |||
if (event.key === 'Escape') { | |||
this.finishOnboarding(); | |||
} | |||
}; | |||
finishOnboarding = () => { | |||
this.props.onFinish(); | |||
if (this.state.projectKey) { | |||
this.context.router.push(getProjectUrl(this.state.projectKey)); | |||
} | |||
}; | |||
handleTimeout = () => { | |||
// unset `projectKey` to display a generic "Finish this tutorial" button | |||
this.setState({ projectKey: undefined }); | |||
}; | |||
handleTokenDone = (token: string) => { | |||
this.setState({ step: 'analysis', token }); | |||
}; | |||
handleOrganizationDone = (organization: string) => { | |||
this.setState({ organization, step: 'token' }); | |||
}; | |||
handleTokenOpen = () => this.setState({ step: 'token' }); | |||
handleOrganizationOpen = () => this.setState({ step: 'organization' }); | |||
handleFinish = (projectKey?: string) => this.setState({ finished: true, projectKey }); | |||
handleReset = () => this.setState({ finished: false, projectKey: undefined }); | |||
render() { | |||
const { automatic, currentUser, organizationsEnabled } = this.props; | |||
if (!isLoggedIn(currentUser)) { | |||
return null; | |||
} | |||
const { finished, projectKey, step, token } = this.state; | |||
const header = translate('onboarding.project.header'); | |||
let stepNumber = 1; | |||
return ( | |||
<> | |||
<Helmet title={header} titleTemplate="%s" /> | |||
<header className="modal-head"> | |||
<h2>{header}</h2> | |||
</header> | |||
<div className="modal-body modal-container"> | |||
<p className="spacer-top big-spacer-bottom"> | |||
{translateWithParameters( | |||
'onboarding.project.header.description', | |||
organizationsEnabled ? 3 : 2 | |||
)} | |||
</p> | |||
{organizationsEnabled && ( | |||
<OrganizationStep | |||
currentUser={currentUser} | |||
finished={this.state.organization != null} | |||
onContinue={this.handleOrganizationDone} | |||
onOpen={this.handleOrganizationOpen} | |||
open={step === 'organization'} | |||
stepNumber={stepNumber++} | |||
/> | |||
)} | |||
<TokenStep | |||
currentUser={currentUser} | |||
finished={this.state.token != null} | |||
onContinue={this.handleTokenDone} | |||
onOpen={this.handleTokenOpen} | |||
open={step === 'token'} | |||
stepNumber={stepNumber++} | |||
/> | |||
<AnalysisStep | |||
onFinish={this.handleFinish} | |||
onReset={this.handleReset} | |||
open={step === 'analysis'} | |||
organization={this.state.organization} | |||
stepNumber={stepNumber} | |||
token={token} | |||
/> | |||
</div> | |||
<footer className="modal-foot"> | |||
<ResetButtonLink className="js-skip" onClick={this.finishOnboarding}> | |||
{(finished && translate('tutorials.finish')) || | |||
(automatic ? translate('tutorials.skip') : translate('close'))} | |||
</ResetButtonLink> | |||
{finished && projectKey ? ( | |||
<ProjectWatcher | |||
onFinish={this.finishOnboarding} | |||
onTimeout={this.handleTimeout} | |||
projectKey={projectKey} | |||
/> | |||
) : ( | |||
<span className="pull-left note"> | |||
{translate( | |||
isSonarCloud() | |||
? 'tutorials.find_tutorial_back_in_plus' | |||
: 'tutorials.find_tutorial_back_in_help' | |||
)} | |||
</span> | |||
)} | |||
</footer> | |||
</> | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state: any): StateProps => { | |||
return { | |||
currentUser: getCurrentUser(state), | |||
organizationsEnabled: areThereCustomOrganizations(state) | |||
}; | |||
}; | |||
export default connect<StateProps, {}, OwnProps>(mapStateToProps)(ProjectOnboarding); |
@@ -27,12 +27,12 @@ interface Props { | |||
onFinish: () => void; | |||
} | |||
const OnboardingContainer = lazyLoad(() => import('./OnboardingContainer')); | |||
const ProjectOnboarding = lazyLoad(() => import('./ProjectOnboarding')); | |||
export default function OnboardingModal(props: Props) { | |||
export default function ProjectOnboardingModal(props: Props) { | |||
return ( | |||
<Modal contentLabel={translate('tutorials.onboarding')} large={true}> | |||
<OnboardingContainer {...props} /> | |||
<ProjectOnboarding {...props} /> | |||
</Modal> | |||
); | |||
} |
@@ -20,14 +20,14 @@ | |||
import * as React from 'react'; | |||
import * as PropTypes from 'prop-types'; | |||
import { connect } from 'react-redux'; | |||
import OnboardingModal from './OnboardingModal'; | |||
import ProjectOnboardingModal from './ProjectOnboardingModal'; | |||
import { skipOnboarding } from '../../../store/users/actions'; | |||
interface DispatchProps { | |||
skipOnboarding: () => void; | |||
} | |||
export class OnboardingPage extends React.PureComponent<DispatchProps> { | |||
export class ProjectOnboardingPage extends React.PureComponent<DispatchProps> { | |||
static contextTypes = { | |||
router: PropTypes.object.isRequired | |||
}; | |||
@@ -38,10 +38,10 @@ export class OnboardingPage extends React.PureComponent<DispatchProps> { | |||
}; | |||
render() { | |||
return <OnboardingModal onFinish={this.onSkipOnboardingTutorial} />; | |||
return <ProjectOnboardingModal onFinish={this.onSkipOnboardingTutorial} />; | |||
} | |||
} | |||
const mapDispatchToProps: DispatchProps = { skipOnboarding }; | |||
export default connect<{}, DispatchProps, {}>(null, mapDispatchToProps)(OnboardingPage); | |||
export default connect<{}, DispatchProps>(null, mapDispatchToProps)(ProjectOnboardingPage); |
@@ -17,8 +17,9 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon'; | |||
import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon'; | |||
import { getTasksForComponent } from '../../../api/ce'; | |||
@@ -28,35 +29,27 @@ import { translate } from '../../../helpers/l10n'; | |||
const INTERVAL = 5000; | |||
const TIMEOUT = 10 * 60 * 1000; // 10 min | |||
/*:: | |||
type Props = { | |||
onFinish: () => void, | |||
onTimeout: () => void, | |||
projectKey: string | |||
}; | |||
*/ | |||
interface Props { | |||
onFinish: () => void; | |||
onTimeout: () => void; | |||
projectKey: string; | |||
} | |||
/*:: | |||
type State = { | |||
inQueue: boolean, | |||
status: ?string | |||
}; | |||
*/ | |||
interface State { | |||
inQueue: boolean; | |||
status?: string; | |||
} | |||
export default class ProjectWatcher extends React.PureComponent { | |||
/*:: interval: number; */ | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
/*:: timeout: number; */ | |||
state /*: State */ = { | |||
inQueue: false, | |||
status: null | |||
}; | |||
export default class ProjectWatcher extends React.PureComponent<Props, State> { | |||
interval?: number; | |||
timeout?: number; | |||
mounted = false; | |||
state: State = { inQueue: false }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.watch(); | |||
this.timeout = setTimeout(this.props.onTimeout, TIMEOUT); | |||
this.timeout = window.setTimeout(this.props.onTimeout, TIMEOUT); | |||
} | |||
componentWillUnmount() { | |||
@@ -65,7 +58,7 @@ export default class ProjectWatcher extends React.PureComponent { | |||
this.mounted = false; | |||
} | |||
watch = () => (this.interval = setTimeout(this.checkProject, INTERVAL)); | |||
watch = () => (this.interval = window.setTimeout(this.checkProject, INTERVAL)); | |||
checkProject = () => { | |||
const { projectKey } = this.props; | |||
@@ -93,10 +86,11 @@ export default class ProjectWatcher extends React.PureComponent { | |||
render() { | |||
const { inQueue, status } = this.state; | |||
const className = 'pull-left note'; | |||
if (status === STATUSES.SUCCESS) { | |||
return ( | |||
<div className="big-spacer-top note text-center"> | |||
<div className={classNames(className, 'display-inline-flex-center')}> | |||
<AlertSuccessIcon className="spacer-right" /> | |||
{translate('onboarding.project_watcher.finished')} | |||
</div> | |||
@@ -105,7 +99,7 @@ export default class ProjectWatcher extends React.PureComponent { | |||
if (inQueue || status === STATUSES.PENDING || status === STATUSES.IN_PROGRESS) { | |||
return ( | |||
<div className="big-spacer-top note text-center"> | |||
<div className={className}> | |||
<i className="spinner spacer-right" /> | |||
{translate('onboarding.project_watcher.in_progress')} | |||
</div> | |||
@@ -114,17 +108,13 @@ export default class ProjectWatcher extends React.PureComponent { | |||
if (status != null) { | |||
return ( | |||
<div className="big-spacer-top note text-center"> | |||
<div className={classNames(className, 'display-inline-flex-center')}> | |||
<AlertErrorIcon className="spacer-right" /> | |||
{translate('onboarding.project_watcher.failed')} | |||
</div> | |||
); | |||
} | |||
return ( | |||
<div className="big-spacer-top note text-center"> | |||
{translate('onboarding.project_watcher.not_started')} | |||
</div> | |||
); | |||
return <div className={className}>{translate('onboarding.project_watcher.not_started')}</div>; | |||
} | |||
} |
@@ -17,23 +17,21 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import classNames from 'classnames'; | |||
/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/no-noninteractive-tabindex */ | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
/*:: | |||
type Props = {| | |||
finished: boolean, | |||
onOpen: () => void, | |||
open: boolean, | |||
renderForm: () => React.Element<*>, | |||
renderResult: () => ?React.Element<*>, | |||
stepNumber: number, | |||
stepTitle: React.Element<*> | string | |||
|}; | |||
*/ | |||
interface Props { | |||
finished: boolean; | |||
onOpen: () => void; | |||
open: boolean; | |||
renderForm: () => React.ReactNode; | |||
renderResult: () => React.ReactNode; | |||
stepNumber: number; | |||
stepTitle: React.ReactNode; | |||
} | |||
export default function Step(props /*: Props */) { | |||
export default function Step(props: Props) { | |||
const className = classNames('boxed-group', 'onboarding-step', { | |||
'is-open': props.open, | |||
'is-finished': props.finished | |||
@@ -41,7 +39,7 @@ export default function Step(props /*: Props */) { | |||
const clickable = !props.open && props.finished; | |||
const handleClick = (event /*: Event */) => { | |||
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => { | |||
event.preventDefault(); | |||
props.onOpen(); | |||
}; |
@@ -17,9 +17,9 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import Step from './Step'; | |||
import { getTokens, generateToken, revokeToken } from '../../../api/user-tokens'; | |||
import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon'; | |||
@@ -27,32 +27,28 @@ import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessI | |||
import { DeleteButton, SubmitButton, Button } from '../../../components/ui/buttons'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = {| | |||
currentUser: { login: string }, | |||
finished: boolean, | |||
open: boolean, | |||
onContinue: (token: string) => void, | |||
onOpen: () => void, | |||
stepNumber: number | |||
|}; | |||
*/ | |||
interface Props { | |||
currentUser: { login: string }; | |||
finished: boolean; | |||
open: boolean; | |||
onContinue: (token: string) => void; | |||
onOpen: () => void; | |||
stepNumber: number; | |||
} | |||
/*:: | |||
type State = { | |||
canUseExisting: boolean, | |||
existingToken: string, | |||
loading: boolean, | |||
selection: string, | |||
tokenName?: string, | |||
token?: string | |||
}; | |||
*/ | |||
interface State { | |||
canUseExisting: boolean; | |||
existingToken: string; | |||
loading: boolean; | |||
selection: string; | |||
tokenName?: string; | |||
token?: string; | |||
} | |||
export default class TokenStep extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
state /*: State */ = { | |||
export default class TokenStep extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { | |||
canUseExisting: false, | |||
existingToken: '', | |||
loading: false, | |||
@@ -87,27 +83,20 @@ export default class TokenStep extends React.PureComponent { | |||
); | |||
}; | |||
handleTokenNameChange = (event /*: { target: HTMLInputElement } */) => { | |||
handleTokenNameChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
this.setState({ tokenName: event.target.value }); | |||
}; | |||
handleTokenGenerate = (event /*: Event */) => { | |||
handleTokenGenerate = (event: React.FormEvent<HTMLFormElement>) => { | |||
event.preventDefault(); | |||
const { tokenName } = this.state; | |||
if (tokenName) { | |||
this.setState({ loading: true }); | |||
generateToken({ name: tokenName }).then( | |||
({ token }) => { | |||
if (this.mounted) { | |||
this.setState({ loading: false, token }); | |||
} | |||
}, | |||
() => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
generateToken({ name: tokenName }).then(({ token }) => { | |||
if (this.mounted) { | |||
this.setState({ loading: false, token }); | |||
} | |||
); | |||
}, this.stopLoading); | |||
} | |||
}; | |||
@@ -115,18 +104,11 @@ export default class TokenStep extends React.PureComponent { | |||
const { tokenName } = this.state; | |||
if (tokenName) { | |||
this.setState({ loading: true }); | |||
revokeToken({ name: tokenName }).then( | |||
() => { | |||
if (this.mounted) { | |||
this.setState({ loading: false, token: undefined, tokenName: undefined }); | |||
} | |||
}, | |||
() => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
revokeToken({ name: tokenName }).then(() => { | |||
if (this.mounted) { | |||
this.setState({ loading: false, token: undefined, tokenName: undefined }); | |||
} | |||
); | |||
}, this.stopLoading); | |||
} | |||
}; | |||
@@ -137,20 +119,26 @@ export default class TokenStep extends React.PureComponent { | |||
} | |||
}; | |||
handleGenerateClick = (event /*: Event */) => { | |||
handleGenerateClick = (event: React.MouseEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
this.setState({ selection: 'generate' }); | |||
}; | |||
handleUseExistingClick = (event /*: Event */) => { | |||
handleUseExistingClick = (event: React.MouseEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
this.setState({ selection: 'use-existing' }); | |||
}; | |||
handleExisingTokenChange = (event /*: { currentTarget: HTMLInputElement } */) => { | |||
handleExisingTokenChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
this.setState({ existingToken: event.currentTarget.value }); | |||
}; | |||
stopLoading = () => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
}; | |||
renderGenerateOption = () => ( | |||
<div> | |||
{this.state.canUseExisting ? ( | |||
@@ -163,10 +151,10 @@ export default class TokenStep extends React.PureComponent { | |||
'is-checked': this.state.selection === 'generate' | |||
})} | |||
/> | |||
{translate('onboading.token.generate_token')} | |||
{translate('onboarding.token.generate_token')} | |||
</a> | |||
) : ( | |||
translate('onboading.token.generate_token') | |||
translate('onboarding.token.generate_token') | |||
)} | |||
{this.state.selection === 'generate' && ( | |||
<div className="big-spacer-top"> | |||
@@ -175,7 +163,7 @@ export default class TokenStep extends React.PureComponent { | |||
autoFocus={true} | |||
className="input-large spacer-right text-middle" | |||
onChange={this.handleTokenNameChange} | |||
placeholder={translate('onboading.token.generate_token.placeholder')} | |||
placeholder={translate('onboarding.token.generate_token.placeholder')} | |||
required={true} | |||
type="text" | |||
value={this.state.tokenName || ''} |
@@ -17,8 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import LanguageStep from '../LanguageStep'; | |||
import { isSonarCloud } from '../../../../helpers/system'; | |||
@@ -26,29 +25,29 @@ import { isSonarCloud } from '../../../../helpers/system'; | |||
jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() })); | |||
beforeEach(() => { | |||
isSonarCloud.mockImplementation(() => false); | |||
(isSonarCloud as jest.Mock<any>).mockImplementation(() => false); | |||
}); | |||
it('selects java', () => { | |||
const onDone = jest.fn(); | |||
const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />); | |||
wrapper.find('RadioToggle').prop('onCheck')('java'); | |||
(wrapper.find('RadioToggle').prop('onCheck') as Function)('java'); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper | |||
(wrapper | |||
.find('RadioToggle') | |||
.at(1) | |||
.prop('onCheck')('maven'); | |||
.prop('onCheck') as Function)('maven'); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(onDone).lastCalledWith({ language: 'java', javaBuild: 'maven' }); | |||
wrapper | |||
(wrapper | |||
.find('RadioToggle') | |||
.at(1) | |||
.prop('onCheck')('gradle'); | |||
.prop('onCheck') as Function)('gradle'); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(onDone).lastCalledWith({ language: 'java', javaBuild: 'gradle' }); | |||
@@ -58,52 +57,52 @@ it('selects c#', () => { | |||
const onDone = jest.fn(); | |||
const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />); | |||
wrapper.find('RadioToggle').prop('onCheck')('dotnet'); | |||
(wrapper.find('RadioToggle').prop('onCheck') as Function)('dotnet'); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.find('NewProjectForm').prop('onDone')('project-foo'); | |||
(wrapper.find('NewProjectForm').prop('onDone') as Function)('project-foo'); | |||
expect(onDone).lastCalledWith({ language: 'dotnet', projectKey: 'project-foo' }); | |||
}); | |||
it('selects c-family', () => { | |||
isSonarCloud.mockImplementation(() => true); | |||
(isSonarCloud as jest.Mock<any>).mockImplementation(() => true); | |||
const onDone = jest.fn(); | |||
const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />); | |||
wrapper.find('RadioToggle').prop('onCheck')('c-family'); | |||
(wrapper.find('RadioToggle').prop('onCheck') as Function)('c-family'); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper | |||
(wrapper | |||
.find('RadioToggle') | |||
.at(1) | |||
.prop('onCheck')('msvc'); | |||
.prop('onCheck') as Function)('msvc'); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.find('NewProjectForm').prop('onDone')('project-foo'); | |||
(wrapper.find('NewProjectForm').prop('onDone') as Function)('project-foo'); | |||
expect(onDone).lastCalledWith({ | |||
language: 'c-family', | |||
cFamilyCompiler: 'msvc', | |||
projectKey: 'project-foo' | |||
}); | |||
wrapper | |||
(wrapper | |||
.find('RadioToggle') | |||
.at(1) | |||
.prop('onCheck')('clang-gcc'); | |||
.prop('onCheck') as Function)('clang-gcc'); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper | |||
(wrapper | |||
.find('RadioToggle') | |||
.at(2) | |||
.prop('onCheck')('linux'); | |||
.prop('onCheck') as Function)('linux'); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.find('NewProjectForm').prop('onDone')('project-foo'); | |||
(wrapper.find('NewProjectForm').prop('onDone') as Function)('project-foo'); | |||
expect(onDone).lastCalledWith({ | |||
language: 'c-family', | |||
cFamilyCompiler: 'clang-gcc', | |||
@@ -116,17 +115,17 @@ it('selects other', () => { | |||
const onDone = jest.fn(); | |||
const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />); | |||
wrapper.find('RadioToggle').prop('onCheck')('other'); | |||
(wrapper.find('RadioToggle').prop('onCheck') as Function)('other'); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper | |||
(wrapper | |||
.find('RadioToggle') | |||
.at(1) | |||
.prop('onCheck')('mac'); | |||
.prop('onCheck') as Function)('mac'); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.find('NewProjectForm').prop('onDone')('project-foo'); | |||
(wrapper.find('NewProjectForm').prop('onDone') as Function)('project-foo'); | |||
expect(onDone).lastCalledWith({ language: 'other', os: 'mac', projectKey: 'project-foo' }); | |||
}); |
@@ -17,8 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { mount } from 'enzyme'; | |||
import NewOrganizationForm from '../NewOrganizationForm'; | |||
import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils'; | |||
@@ -48,7 +47,7 @@ it('deletes organization', async () => { | |||
const wrapper = mount(<NewOrganizationForm onDelete={onDelete} onDone={jest.fn()} />); | |||
wrapper.setState({ done: true, loading: false, organization: 'foo' }); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.find('DeleteButton').prop('onClick')(); | |||
(wrapper.find('DeleteButton').prop('onClick') as Function)(); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); // spinner | |||
await waitAndUpdate(wrapper); |
@@ -17,8 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { mount } from 'enzyme'; | |||
import NewProjectForm from '../NewProjectForm'; | |||
import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils'; | |||
@@ -47,7 +46,7 @@ it('deletes project', async () => { | |||
const wrapper = mount(<NewProjectForm onDelete={onDelete} onDone={jest.fn()} />); | |||
wrapper.setState({ done: true, loading: false, projectKey: 'foo' }); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.find('DeleteButton').prop('onClick')(); | |||
(wrapper.find('DeleteButton').prop('onClick') as Function)(); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); // spinner | |||
await waitAndUpdate(wrapper); |
@@ -17,8 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { mount } from 'enzyme'; | |||
import OrganizationStep from '../OrganizationStep'; | |||
import { click, waitAndUpdate } from '../../../../helpers/testUtils'; | |||
@@ -35,7 +34,7 @@ jest.mock('../../../../api/organizations', () => ({ | |||
const currentUser = { isLoggedIn: true, login: 'user' }; | |||
beforeEach(() => { | |||
getOrganizations.mockClear(); | |||
(getOrganizations as jest.Mock<any>).mockClear(); | |||
}); | |||
// FIXME | |||
@@ -73,10 +72,10 @@ it('works with existing organization', async () => { | |||
await waitAndUpdate(wrapper); | |||
click(wrapper.find('.js-existing')); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper | |||
(wrapper | |||
.find('Select') | |||
.first() | |||
.prop('onChange')({ value: 'another' }); | |||
.prop('onChange') as Function)({ value: 'another' }); | |||
wrapper.update(); | |||
click(wrapper.find('[className="js-continue"]')); | |||
expect(onContinue).toBeCalledWith('another'); | |||
@@ -96,7 +95,7 @@ it('works with new organization', async () => { | |||
); | |||
await waitAndUpdate(wrapper); | |||
click(wrapper.find('.js-new')); | |||
wrapper.find('NewOrganizationForm').prop('onDone')('new'); | |||
(wrapper.find('NewOrganizationForm').prop('onDone') as Function)('new'); | |||
wrapper.update(); | |||
click(wrapper.find('[className="js-continue"]')); | |||
expect(onContinue).toBeCalledWith('new'); |
@@ -17,10 +17,9 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow, mount } from 'enzyme'; | |||
import Onboarding from '../Onboarding'; | |||
import { ProjectOnboarding } from '../ProjectOnboarding'; | |||
import { click, doAsync } from '../../../../helpers/testUtils'; | |||
import { getInstance, isSonarCloud } from '../../../../helpers/system'; | |||
@@ -36,11 +35,10 @@ jest.mock('../../../../helpers/system', () => ({ | |||
const currentUser = { login: 'admin', isLoggedIn: true }; | |||
it('guides for on-premise', () => { | |||
getInstance.mockImplementation(() => 'SonarQube'); | |||
isSonarCloud.mockImplementation(() => false); | |||
(getInstance as jest.Mock<any>).mockImplementation(() => 'SonarQube'); | |||
(isSonarCloud as jest.Mock<any>).mockImplementation(() => false); | |||
const wrapper = shallow( | |||
<Onboarding | |||
className="modal-container" | |||
<ProjectOnboarding | |||
currentUser={currentUser} | |||
onFinish={jest.fn()} | |||
organizationsEnabled={false} | |||
@@ -48,39 +46,36 @@ it('guides for on-premise', () => { | |||
); | |||
expect(wrapper).toMatchSnapshot(); | |||
// $FlowFixMe | |||
wrapper.instance().handleTokenDone('abcd1234'); | |||
(wrapper.instance() as ProjectOnboarding).handleTokenDone('abcd1234'); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('guides for sonarcloud', () => { | |||
getInstance.mockImplementation(() => 'SonarCloud'); | |||
isSonarCloud.mockImplementation(() => true); | |||
(getInstance as jest.Mock<any>).mockImplementation(() => 'SonarCloud'); | |||
(isSonarCloud as jest.Mock<any>).mockImplementation(() => true); | |||
const wrapper = shallow( | |||
<Onboarding currentUser={currentUser} onFinish={jest.fn()} organizationsEnabled={true} /> | |||
<ProjectOnboarding currentUser={currentUser} onFinish={jest.fn()} organizationsEnabled={true} /> | |||
); | |||
expect(wrapper).toMatchSnapshot(); | |||
// $FlowFixMe | |||
wrapper.instance().handleOrganizationDone('my-org'); | |||
(wrapper.instance() as ProjectOnboarding).handleOrganizationDone('my-org'); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
// $FlowFixMe | |||
wrapper.instance().handleTokenDone('abcd1234'); | |||
(wrapper.instance() as ProjectOnboarding).handleTokenDone('abcd1234'); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('finishes', () => { | |||
getInstance.mockImplementation(() => 'SonarQube'); | |||
isSonarCloud.mockImplementation(() => false); | |||
(getInstance as jest.Mock<any>).mockImplementation(() => 'SonarQube'); | |||
(isSonarCloud as jest.Mock<any>).mockImplementation(() => false); | |||
const onFinish = jest.fn(); | |||
const wrapper = mount( | |||
<Onboarding currentUser={currentUser} onFinish={onFinish} organizationsEnabled={false} /> | |||
<ProjectOnboarding currentUser={currentUser} onFinish={onFinish} organizationsEnabled={false} /> | |||
); | |||
click(wrapper.find('.js-skip')); | |||
click(wrapper.find('ResetButtonLink')); | |||
return doAsync(() => { | |||
expect(onFinish).toBeCalled(); | |||
}); |
@@ -17,8 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow, mount } from 'enzyme'; | |||
import ProjectWatcher from '../ProjectWatcher'; | |||
@@ -17,8 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import Step from '../Step'; | |||
import { click } from '../../../../helpers/testUtils'; |
@@ -17,8 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { mount } from 'enzyme'; | |||
import TokenStep from '../TokenStep'; | |||
import { change, click, submit, waitAndUpdate } from '../../../../helpers/testUtils'; | |||
@@ -67,7 +66,7 @@ it('revokes token', async () => { | |||
await new Promise(setImmediate); | |||
wrapper.setState({ token: 'abcd1234', tokenName: 'my token' }); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.find('DeleteButton').prop('onClick')(); | |||
(wrapper.find('DeleteButton').prop('onClick') as Function)(); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); // spinner | |||
await waitAndUpdate(wrapper); |
@@ -0,0 +1,349 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`guides for on-premise 1`] = ` | |||
<React.Fragment> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="onboarding.project.header" | |||
titleTemplate="%s" | |||
/> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
onboarding.project.header | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body modal-container" | |||
> | |||
<p | |||
className="spacer-top big-spacer-bottom" | |||
> | |||
onboarding.project.header.description.2 | |||
</p> | |||
<TokenStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={false} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={true} | |||
stepNumber={1} | |||
/> | |||
<AnalysisStep | |||
onFinish={[Function]} | |||
onReset={[Function]} | |||
open={false} | |||
stepNumber={2} | |||
/> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<ResetButtonLink | |||
className="js-skip" | |||
onClick={[Function]} | |||
> | |||
close | |||
</ResetButtonLink> | |||
<span | |||
className="pull-left note" | |||
> | |||
tutorials.find_tutorial_back_in_help | |||
</span> | |||
</footer> | |||
</React.Fragment> | |||
`; | |||
exports[`guides for on-premise 2`] = ` | |||
<React.Fragment> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="onboarding.project.header" | |||
titleTemplate="%s" | |||
/> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
onboarding.project.header | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body modal-container" | |||
> | |||
<p | |||
className="spacer-top big-spacer-bottom" | |||
> | |||
onboarding.project.header.description.2 | |||
</p> | |||
<TokenStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={true} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={false} | |||
stepNumber={1} | |||
/> | |||
<AnalysisStep | |||
onFinish={[Function]} | |||
onReset={[Function]} | |||
open={true} | |||
stepNumber={2} | |||
token="abcd1234" | |||
/> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<ResetButtonLink | |||
className="js-skip" | |||
onClick={[Function]} | |||
> | |||
close | |||
</ResetButtonLink> | |||
<span | |||
className="pull-left note" | |||
> | |||
tutorials.find_tutorial_back_in_help | |||
</span> | |||
</footer> | |||
</React.Fragment> | |||
`; | |||
exports[`guides for sonarcloud 1`] = ` | |||
<React.Fragment> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="onboarding.project.header" | |||
titleTemplate="%s" | |||
/> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
onboarding.project.header | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body modal-container" | |||
> | |||
<p | |||
className="spacer-top big-spacer-bottom" | |||
> | |||
onboarding.project.header.description.3 | |||
</p> | |||
<OrganizationStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={false} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={true} | |||
stepNumber={1} | |||
/> | |||
<TokenStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={false} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={false} | |||
stepNumber={2} | |||
/> | |||
<AnalysisStep | |||
onFinish={[Function]} | |||
onReset={[Function]} | |||
open={false} | |||
stepNumber={3} | |||
/> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<ResetButtonLink | |||
className="js-skip" | |||
onClick={[Function]} | |||
> | |||
close | |||
</ResetButtonLink> | |||
<span | |||
className="pull-left note" | |||
> | |||
tutorials.find_tutorial_back_in_plus | |||
</span> | |||
</footer> | |||
</React.Fragment> | |||
`; | |||
exports[`guides for sonarcloud 2`] = ` | |||
<React.Fragment> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="onboarding.project.header" | |||
titleTemplate="%s" | |||
/> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
onboarding.project.header | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body modal-container" | |||
> | |||
<p | |||
className="spacer-top big-spacer-bottom" | |||
> | |||
onboarding.project.header.description.3 | |||
</p> | |||
<OrganizationStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={true} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={false} | |||
stepNumber={1} | |||
/> | |||
<TokenStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={false} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={true} | |||
stepNumber={2} | |||
/> | |||
<AnalysisStep | |||
onFinish={[Function]} | |||
onReset={[Function]} | |||
open={false} | |||
organization="my-org" | |||
stepNumber={3} | |||
/> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<ResetButtonLink | |||
className="js-skip" | |||
onClick={[Function]} | |||
> | |||
close | |||
</ResetButtonLink> | |||
<span | |||
className="pull-left note" | |||
> | |||
tutorials.find_tutorial_back_in_plus | |||
</span> | |||
</footer> | |||
</React.Fragment> | |||
`; | |||
exports[`guides for sonarcloud 3`] = ` | |||
<React.Fragment> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="onboarding.project.header" | |||
titleTemplate="%s" | |||
/> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
onboarding.project.header | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body modal-container" | |||
> | |||
<p | |||
className="spacer-top big-spacer-bottom" | |||
> | |||
onboarding.project.header.description.3 | |||
</p> | |||
<OrganizationStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={true} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={false} | |||
stepNumber={1} | |||
/> | |||
<TokenStep | |||
currentUser={ | |||
Object { | |||
"isLoggedIn": true, | |||
"login": "admin", | |||
} | |||
} | |||
finished={true} | |||
onContinue={[Function]} | |||
onOpen={[Function]} | |||
open={false} | |||
stepNumber={2} | |||
/> | |||
<AnalysisStep | |||
onFinish={[Function]} | |||
onReset={[Function]} | |||
open={true} | |||
organization="my-org" | |||
stepNumber={3} | |||
token="abcd1234" | |||
/> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<ResetButtonLink | |||
className="js-skip" | |||
onClick={[Function]} | |||
> | |||
close | |||
</ResetButtonLink> | |||
<span | |||
className="pull-left note" | |||
> | |||
tutorials.find_tutorial_back_in_plus | |||
</span> | |||
</footer> | |||
</React.Fragment> | |||
`; |
@@ -2,7 +2,7 @@ | |||
exports[`renders 1`] = ` | |||
<div | |||
className="big-spacer-top note text-center" | |||
className="pull-left note" | |||
> | |||
onboarding.project_watcher.not_started | |||
</div> | |||
@@ -10,7 +10,7 @@ exports[`renders 1`] = ` | |||
exports[`renders 2`] = ` | |||
<div | |||
className="big-spacer-top note text-center" | |||
className="pull-left note" | |||
> | |||
<i | |||
className="spinner spacer-right" | |||
@@ -21,7 +21,7 @@ exports[`renders 2`] = ` | |||
exports[`renders 3`] = ` | |||
<div | |||
className="big-spacer-top note text-center" | |||
className="pull-left note display-inline-flex-center" | |||
> | |||
<AlertSuccessIcon | |||
className="spacer-right" | |||
@@ -32,7 +32,7 @@ exports[`renders 3`] = ` | |||
exports[`renders 4`] = ` | |||
<div | |||
className="big-spacer-top note text-center" | |||
className="pull-left note" | |||
> | |||
<i | |||
className="spinner spacer-right" |
@@ -50,7 +50,7 @@ exports[`generates token 1`] = ` | |||
<i | |||
className="icon-radio spacer-right is-checked" | |||
/> | |||
onboading.token.generate_token | |||
onboarding.token.generate_token | |||
</a> | |||
<div | |||
className="big-spacer-top" | |||
@@ -62,7 +62,7 @@ exports[`generates token 1`] = ` | |||
autoFocus={true} | |||
className="input-large spacer-right text-middle" | |||
onChange={[Function]} | |||
placeholder="onboading.token.generate_token.placeholder" | |||
placeholder="onboarding.token.generate_token.placeholder" | |||
required={true} | |||
type="text" | |||
value="" | |||
@@ -166,7 +166,7 @@ exports[`generates token 2`] = ` | |||
<i | |||
className="icon-radio spacer-right is-checked" | |||
/> | |||
onboading.token.generate_token | |||
onboarding.token.generate_token | |||
</a> | |||
<div | |||
className="big-spacer-top" | |||
@@ -178,7 +178,7 @@ exports[`generates token 2`] = ` | |||
autoFocus={true} | |||
className="input-large spacer-right text-middle" | |||
onChange={[Function]} | |||
placeholder="onboading.token.generate_token.placeholder" | |||
placeholder="onboarding.token.generate_token.placeholder" | |||
required={true} | |||
type="text" | |||
value="my token" | |||
@@ -582,7 +582,7 @@ exports[`revokes token 3`] = ` | |||
<i | |||
className="icon-radio spacer-right is-checked" | |||
/> | |||
onboading.token.generate_token | |||
onboarding.token.generate_token | |||
</a> | |||
<div | |||
className="big-spacer-top" | |||
@@ -594,7 +594,7 @@ exports[`revokes token 3`] = ` | |||
autoFocus={true} | |||
className="input-large spacer-right text-middle" | |||
onChange={[Function]} | |||
placeholder="onboading.token.generate_token.placeholder" | |||
placeholder="onboarding.token.generate_token.placeholder" | |||
required={true} | |||
type="text" | |||
value="" |
@@ -17,24 +17,22 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { getBaseUrl } from '../../../../helpers/urls'; | |||
/*:: | |||
type Props = { | |||
className?: string, | |||
os: string | |||
}; | |||
*/ | |||
interface Props { | |||
className?: string; | |||
os: string; | |||
} | |||
const filenames = { | |||
const filenames: { [key: string]: string } = { | |||
win: 'build-wrapper-win-x86.zip', | |||
linux: 'build-wrapper-linux-x86.zip', | |||
mac: 'build-wrapper-macosx-x86.zip' | |||
}; | |||
export default function BuildWrapper(props /*: Props */) { | |||
export default function BuildWrapper(props: Props) { | |||
return ( | |||
<div className={props.className}> | |||
<h4 className="spacer-bottom"> | |||
@@ -50,7 +48,7 @@ export default function BuildWrapper(props /*: Props */) { | |||
<a | |||
className="button" | |||
download={filenames[props.os]} | |||
href={window.baseUrl + '/static/cpp/' + filenames[props.os]} | |||
href={`${getBaseUrl()}/static/cpp/${filenames[props.os]}`} | |||
target="_blank"> | |||
{translate('download_verb')} | |||
</a> |
@@ -17,31 +17,28 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import SQScanner from './SQScanner'; | |||
import BuildWrapper from './BuildWrapper'; | |||
import CodeSnippet from '../../../../components/common/CodeSnippet'; | |||
import InstanceMessage from '../../../../components/common/InstanceMessage'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
host: string, | |||
os: string, | |||
organization?: string, | |||
projectKey: string, | |||
token: string | |||
}; | |||
*/ | |||
interface Props { | |||
host: string; | |||
os: string; | |||
organization?: string; | |||
projectKey: string; | |||
token: string; | |||
} | |||
const executables = { | |||
const executables: { [key: string]: string } = { | |||
linux: 'build-wrapper-linux-x86-64', | |||
win: 'build-wrapper-win-x86-64.exe', | |||
mac: 'build-wrapper-macosx-x86' | |||
}; | |||
export default function ClangGCC(props /*: Props */) { | |||
export default function ClangGCC(props: Props) { | |||
const command1 = `${executables[props.os]} --out-dir bw-output make clean all`; | |||
const command2 = [ |
@@ -17,23 +17,20 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import MSBuildScanner from './MSBuildScanner'; | |||
import CodeSnippet from '../../../../components/common/CodeSnippet'; | |||
import InstanceMessage from '../../../../components/common/InstanceMessage'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
/*:: | |||
type Props = {| | |||
host: string, | |||
organization?: string, | |||
projectKey: string, | |||
token: string | |||
|}; | |||
*/ | |||
interface Props { | |||
host: string; | |||
organization?: string; | |||
projectKey: string; | |||
token: string; | |||
} | |||
export default function DotNet(props /*: Props */) { | |||
export default function DotNet(props: Props) { | |||
const command1 = [ | |||
'SonarQube.Scanner.MSBuild.exe begin', | |||
`/k:"${props.projectKey}"`, |
@@ -17,21 +17,18 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import CodeSnippet from '../../../../components/common/CodeSnippet'; | |||
import InstanceMessage from '../../../../components/common/InstanceMessage'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
/*:: | |||
type Props = {| | |||
host: string, | |||
organization?: string, | |||
token: string | |||
|}; | |||
*/ | |||
interface Props { | |||
host: string; | |||
organization?: string; | |||
token: string; | |||
} | |||
export default function JavaGradle(props /*: Props */) { | |||
export default function JavaGradle(props: Props) { | |||
const config = 'plugins {\n id "org.sonarqube" version "2.6"\n}'; | |||
const command = [ |
@@ -17,21 +17,18 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import CodeSnippet from '../../../../components/common/CodeSnippet'; | |||
import InstanceMessage from '../../../../components/common/InstanceMessage'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
/*:: | |||
type Props = {| | |||
host: string, | |||
organization?: string, | |||
token: string | |||
|}; | |||
*/ | |||
interface Props { | |||
host: string; | |||
organization?: string; | |||
token: string; | |||
} | |||
export default function JavaMaven(props /*: Props */) { | |||
export default function JavaMaven(props: Props) { | |||
const command = [ | |||
'mvn sonar:sonar', | |||
props.organization && `-Dsonar.organization=${props.organization}`, |
@@ -17,17 +17,15 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
className?: string | |||
}; | |||
*/ | |||
interface Props { | |||
className?: string; | |||
} | |||
export default function MSBuildScanner(props /*: Props */) { | |||
export default function MSBuildScanner(props: Props) { | |||
return ( | |||
<div className={props.className}> | |||
<h4 className="spacer-bottom">{translate('onboarding.analysis.msbuild.header')}</h4> | |||
@@ -39,6 +37,7 @@ export default function MSBuildScanner(props /*: Props */) { | |||
<a | |||
className="button" | |||
href="http://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html" | |||
rel="noopener noreferrer" | |||
target="_blank"> | |||
{translate('download_verb')} | |||
</a> |
@@ -17,24 +17,21 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import MSBuildScanner from './MSBuildScanner'; | |||
import BuildWrapper from './BuildWrapper'; | |||
import CodeSnippet from '../../../../components/common/CodeSnippet'; | |||
import InstanceMessage from '../../../../components/common/InstanceMessage'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
/*:: | |||
type Props = {| | |||
host: string, | |||
organization?: string, | |||
projectKey: string, | |||
token: string | |||
|}; | |||
*/ | |||
interface Props { | |||
host: string; | |||
organization?: string; | |||
projectKey: string; | |||
token: string; | |||
} | |||
export default function Msvc(props /*: Props */) { | |||
export default function Msvc(props: Props) { | |||
const command1 = [ | |||
'SonarQube.Scanner.MSBuild.exe begin', | |||
`/k:"${props.projectKey}"`, |
@@ -17,24 +17,21 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import SQScanner from './SQScanner'; | |||
import CodeSnippet from '../../../../components/common/CodeSnippet'; | |||
import InstanceMessage from '../../../../components/common/InstanceMessage'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
/*:: | |||
type Props = {| | |||
host: string, | |||
organization?: string, | |||
os: string, | |||
projectKey: string, | |||
token: string | |||
|}; | |||
*/ | |||
interface Props { | |||
host: string; | |||
organization?: string; | |||
os: string; | |||
projectKey: string; | |||
token: string; | |||
} | |||
export default function Other(props /*: Props */) { | |||
export default function Other(props: Props) { | |||
const command = [ | |||
props.os === 'win' ? 'sonar-scanner.bat' : 'sonar-scanner', | |||
`-Dsonar.projectKey=${props.projectKey}`, |
@@ -17,18 +17,16 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
className?: string, | |||
os: string | |||
}; | |||
*/ | |||
interface Props { | |||
className?: string; | |||
os: string; | |||
} | |||
export default function SQScanner(props /*: Props */) { | |||
export default function SQScanner(props: Props) { | |||
return ( | |||
<div className={props.className}> | |||
<h4 className="spacer-bottom"> | |||
@@ -44,6 +42,7 @@ export default function SQScanner(props /*: Props */) { | |||
<a | |||
className="button" | |||
href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" | |||
rel="noopener noreferrer" | |||
target="_blank"> | |||
{translate('download_verb')} | |||
</a> |
@@ -17,8 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import BuildWrapper from '../BuildWrapper'; | |||
@@ -17,8 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import ClangGCC from '../ClangGCC'; | |||
@@ -35,8 +35,8 @@ it('renders correctly', () => { | |||
shallow( | |||
<ClangGCC | |||
host="host" | |||
os="linux" | |||
organization="organization" | |||
os="linux" | |||
projectKey="projectKey" | |||
token="token" | |||
/> |
@@ -17,8 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import DotNet from '../DotNet'; | |||
@@ -17,8 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import JavaGradle from '../JavaGradle'; | |||
@@ -17,8 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import JavaMaven from '../JavaMaven'; | |||
@@ -17,8 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import MSBuildScanner from '../MSBuildScanner'; | |||
@@ -17,8 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import Msvc from '../Msvc'; | |||
@@ -17,8 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import Other from '../Other'; | |||
@@ -35,8 +35,8 @@ it('renders correctly', () => { | |||
shallow( | |||
<Other | |||
host="host" | |||
os="linux" | |||
organization="organization" | |||
os="linux" | |||
projectKey="projectKey" | |||
token="token" | |||
/> |
@@ -17,8 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import SQScanner from '../SQScanner'; | |||
@@ -19,6 +19,7 @@ exports[`renders correctly 1`] = ` | |||
<a | |||
className="button" | |||
href="http://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
download_verb |
@@ -19,6 +19,7 @@ exports[`renders correctly 1`] = ` | |||
<a | |||
className="button" | |||
href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
download_verb | |||
@@ -46,6 +47,7 @@ exports[`renders correctly 2`] = ` | |||
<a | |||
className="button" | |||
href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
download_verb | |||
@@ -73,6 +75,7 @@ exports[`renders correctly 3`] = ` | |||
<a | |||
className="button" | |||
href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
download_verb |
@@ -17,9 +17,6 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
.onboarding { | |||
min-height: calc(70vh - 60px); | |||
} | |||
.onboarding-step { | |||
position: relative; | |||
@@ -58,12 +55,8 @@ | |||
outline: none; | |||
} | |||
.onboarding .page-actions { | |||
text-align: right; | |||
margin-bottom: 0; | |||
} | |||
.onboarding .page-actions p { | |||
line-height: 16px; | |||
margin-top: 6px; | |||
.onboarding-choices { | |||
display: flex; | |||
justify-content: space-around; | |||
padding: 24px 0 44px; | |||
} |
@@ -0,0 +1,68 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { FormattedMessage } from 'react-intl'; | |||
import { Link } from 'react-router'; | |||
import Modal from '../../../components/controls/Modal'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { ResetButtonLink } from '../../../components/ui/buttons'; | |||
interface Props { | |||
onFinish: () => void; | |||
} | |||
export default class TeamOnboardingModal extends React.PureComponent<Props> { | |||
render() { | |||
const header = translate('onboarding.team.header'); | |||
return ( | |||
<Modal | |||
contentLabel={header} | |||
medium={true} | |||
onRequestClose={this.props.onFinish} | |||
shouldCloseOnOverlayClick={false}> | |||
<header className="modal-head"> | |||
<h2>{header}</h2> | |||
</header> | |||
<div className="modal-body"> | |||
<div className="alert alert-info modal-alert"> | |||
{translate('onboarding.team.work_in_progress')} | |||
</div> | |||
<p className="spacer-top big-spacer-bottom">{translate('onboarding.team.first_step')}</p> | |||
<p className="spacer-top big-spacer-bottom"> | |||
<FormattedMessage | |||
defaultMessage={translate('onboarding.team.how_to_join')} | |||
id="onboarding.team.how_to_join" | |||
values={{ | |||
link: ( | |||
<Link onClick={this.props.onFinish} to="/documentation/organizations/manage-team"> | |||
{translate('as_explained_here')} | |||
</Link> | |||
) | |||
}} | |||
/> | |||
</p> | |||
</div> | |||
<footer className="modal-foot"> | |||
<ResetButtonLink onClick={this.props.onFinish}>{translate('close')}</ResetButtonLink> | |||
</footer> | |||
</Modal> | |||
); | |||
} | |||
} |
@@ -18,9 +18,9 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import TeamOnboardingModal from '../TeamOnboardingModal'; | |||
export interface Props { | |||
onFinish: () => void; | |||
} | |||
export default class OnboardingModal extends React.PureComponent<Props> {} | |||
it('renders correctly', () => { | |||
expect(shallow(<TeamOnboardingModal onFinish={jest.fn()} />)).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,61 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders correctly 1`] = ` | |||
<Modal | |||
contentLabel="onboarding.team.header" | |||
medium={true} | |||
onRequestClose={[MockFunction]} | |||
shouldCloseOnOverlayClick={false} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
onboarding.team.header | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body" | |||
> | |||
<div | |||
className="alert alert-info modal-alert" | |||
> | |||
onboarding.team.work_in_progress | |||
</div> | |||
<p | |||
className="spacer-top big-spacer-bottom" | |||
> | |||
onboarding.team.first_step | |||
</p> | |||
<p | |||
className="spacer-top big-spacer-bottom" | |||
> | |||
<FormattedMessage | |||
defaultMessage="onboarding.team.how_to_join" | |||
id="onboarding.team.how_to_join" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onClick={[MockFunction]} | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/documentation/organizations/manage-team" | |||
> | |||
as_explained_here | |||
</Link>, | |||
} | |||
} | |||
/> | |||
</p> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
close | |||
</ResetButtonLink> | |||
</footer> | |||
</Modal> | |||
`; |
@@ -44,13 +44,13 @@ export function clickOutside(event = {}): void { | |||
window.dispatchEvent(dispatchedEvent); | |||
} | |||
export function submit(element: ShallowWrapper): void { | |||
export function submit(element: ShallowWrapper | ReactWrapper): void { | |||
element.simulate('submit', { | |||
preventDefault() {} | |||
}); | |||
} | |||
export function change(element: ShallowWrapper, value: string, event = {}): void { | |||
export function change(element: ShallowWrapper | ReactWrapper, value: string, event = {}): void { | |||
element.simulate('change', { | |||
target: { value }, | |||
currentTarget: { value }, |
@@ -202,6 +202,7 @@ no=No | |||
and_worse=and worse | |||
are_you_sure=Are you sure? | |||
as_explained_here=as explained here | |||
assigned_to=Assigned to | |||
bulk_change=Bulk Change | |||
bulleted_point=Bulleted point | |||
@@ -921,8 +922,8 @@ shortcuts.section.rules.deactivate=deactivate selected rule | |||
tutorials.onboarding=Analyze a new project | |||
tutorials.skip=Skip this tutorial | |||
tutorials.finish=Finish this tutorial | |||
tutorials.find_it_back_in_help=Find it back anytime in the Help section | |||
tutorials.find_it_back_in_plus=Find it back anytime in the "+" menu | |||
tutorials.find_tutorial_back_in_help=Find this tutorial back anytime in the Help section | |||
tutorials.find_tutorial_back_in_plus=Find this tutorial back anytime in the "+" menu | |||
#------------------------------------------------------------------------------ | |||
@@ -2589,15 +2590,30 @@ footer.web_api=Web API | |||
# ONBOARDING | |||
# | |||
#------------------------------------------------------------------------------ | |||
onboarding.header=Welcome to {instance}! | |||
onboarding.header.description=Want to quickly analyze a first project? Follow these {0} easy steps. | |||
onboarding.header=Welcome to SonarCloud! | |||
onboarding.header.description=Let us help you get started. What do you want to do? | |||
onboarding.project.header=Analyze a project | |||
onboarding.project.header.description=Want to quickly analyze a first project? Follow these {0} easy steps. | |||
onboarding.team.header=Join a team | |||
onboarding.team.first_step=Well congrats, the first step is done! | |||
onboarding.team.how_to_join=To join a team, the only thing you need to do is to be a user registered on Sonarcloud. The administrator of the Sonarcloud organization you wish to join has to add you to his organization's members {link}. Ask him to do so! | |||
onboarding.team.work_in_progress=We are currently working on a better way to join a team or invite people to yours. | |||
onboarding.analyze_public_code=I want to analyze public code | |||
onboarding.analyze_public_code.button=Analyze a project | |||
onboarding.analyze_private_code=I want to analyze private code | |||
onboarding.analyze_private_code.button=Setup a new organization | |||
onboarding.contribute_existing_project=I want to contribute to an existing project | |||
onboarding.contribute_existing_project.button=Join a team | |||
onboarding.token.header=Provide a token | |||
onboarding.token.text=The token is used to identify you when an analysis is performed. If it has been compromised, you can revoke it at any point of time in your user account. | |||
onboarding.token.generate=Generate | |||
onboarding.token.placeholder=Enter a name for your token | |||
onboading.token.generate_token=Generate a token | |||
onboading.token.generate_token.placeholder=Enter a name for your token | |||
onboarding.token.generate_token=Generate a token | |||
onboarding.token.generate_token.placeholder=Enter a name for your token | |||
onboarding.token.use_existing_token=Use existing token | |||
onboarding.token.use_existing_token.placeholder=Enter your existing token | |||
onboarding.token.invalid_format=The token you have entered has invalid format. |