* 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
import * as React from 'react'; | import * as React from 'react'; | ||||
import * as PropTypes from 'prop-types'; | import * as PropTypes from 'prop-types'; | ||||
import { connect } from 'react-redux'; | 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 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 { differenceInDays, parseDate, toShortNotSoISOString } from '../../helpers/dates'; | ||||
import { EditionKey } from '../../apps/marketplace/utils'; | import { EditionKey } from '../../apps/marketplace/utils'; | ||||
import { getCurrentUser, getAppState } from '../../store/rootReducer'; | 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 { showLicense } from '../../api/marketplace'; | ||||
import { hasMessage } from '../../helpers/l10n'; | import { hasMessage } from '../../helpers/l10n'; | ||||
import { save, get } from '../../helpers/storage'; | import { save, get } from '../../helpers/storage'; | ||||
import { isSonarCloud } from '../../helpers/system'; | |||||
import { skipOnboarding } from '../../api/users'; | |||||
interface StateProps { | interface StateProps { | ||||
canAdmin: boolean; | canAdmin: boolean; | ||||
} | } | ||||
interface DispatchProps { | interface DispatchProps { | ||||
skipOnboarding: () => void; | |||||
skipOnboardingAction: () => void; | |||||
} | } | ||||
interface OwnProps { | interface OwnProps { | ||||
enum ModalKey { | enum ModalKey { | ||||
license, | license, | ||||
onboarding | |||||
onboarding, | |||||
organizationOnboarding, | |||||
projectOnboarding, | |||||
teamOnboarding | |||||
} | } | ||||
interface State { | interface State { | ||||
const LICENSE_PROMPT = 'sonarqube.license.prompt'; | const LICENSE_PROMPT = 'sonarqube.license.prompt'; | ||||
export class StartupModal extends React.PureComponent<Props, State> { | export class StartupModal extends React.PureComponent<Props, State> { | ||||
static contextTypes = { | |||||
router: PropTypes.object.isRequired | |||||
}; | |||||
static childContextTypes = { | static childContextTypes = { | ||||
openOnboardingTutorial: PropTypes.func | |||||
openProjectOnboarding: PropTypes.func | |||||
}; | }; | ||||
state: State = { automatic: false }; | state: State = { automatic: false }; | ||||
getChildContext() { | getChildContext() { | ||||
return { openOnboardingTutorial: this.openOnboarding }; | |||||
return { openProjectOnboarding: this.openProjectOnboarding }; | |||||
} | } | ||||
componentDidMount() { | componentDidMount() { | ||||
closeOnboarding = () => { | closeOnboarding = () => { | ||||
this.setState(state => { | 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 { automatic: false, modal: undefined }; | ||||
} | } | ||||
return undefined; | return undefined; | ||||
}); | }); | ||||
}; | }; | ||||
closeOrganizationOnboarding = ({ key }: Pick<Organization, 'key'>) => { | |||||
this.closeOnboarding(); | |||||
this.context.router.push(`/organizations/${key}`); | |||||
}; | |||||
openOnboarding = () => { | openOnboarding = () => { | ||||
this.setState({ modal: ModalKey.onboarding }); | this.setState({ modal: ModalKey.onboarding }); | ||||
}; | }; | ||||
openOrganizationOnboarding = () => { | |||||
this.setState({ modal: ModalKey.organizationOnboarding }); | |||||
}; | |||||
openProjectOnboarding = () => { | |||||
this.setState({ modal: ModalKey.projectOnboarding }); | |||||
}; | |||||
openTeamOnboarding = () => { | |||||
this.setState({ modal: ModalKey.teamOnboarding }); | |||||
}; | |||||
tryAutoOpenLicense = () => { | tryAutoOpenLicense = () => { | ||||
const { canAdmin, currentEdition, currentUser } = this.props; | const { canAdmin, currentEdition, currentUser } = this.props; | ||||
const hasLicenseManager = hasMessage('license.prompt.title'); | const hasLicenseManager = hasMessage('license.prompt.title'); | ||||
const { currentUser, location } = this.props; | const { currentUser, location } = this.props; | ||||
if (currentUser.showOnboardingTutorial && !location.pathname.startsWith('documentation')) { | if (currentUser.showOnboardingTutorial && !location.pathname.startsWith('documentation')) { | ||||
this.setState({ automatic: true }); | this.setState({ automatic: true }); | ||||
this.openOnboarding(); | |||||
if (isSonarCloud()) { | |||||
this.openOnboarding(); | |||||
} else { | |||||
this.openProjectOnboarding(); | |||||
} | |||||
} | } | ||||
}; | }; | ||||
{this.props.children} | {this.props.children} | ||||
{modal === ModalKey.license && <LicensePromptModal onClose={this.closeLicense} />} | {modal === ModalKey.license && <LicensePromptModal onClose={this.closeLicense} />} | ||||
{modal === ModalKey.onboarding && ( | {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} /> | |||||
)} | )} | ||||
</> | </> | ||||
); | ); | ||||
currentUser: getCurrentUser(state) | currentUser: getCurrentUser(state) | ||||
}); | }); | ||||
const mapDispatchToProps: DispatchProps = { skipOnboarding }; | |||||
const mapDispatchToProps: DispatchProps = { skipOnboardingAction }; | |||||
export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)( | export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)( | ||||
StartupModal | StartupModal |
async function shouldNotHaveModals(wrapper: ShallowWrapper) { | async function shouldNotHaveModals(wrapper: ShallowWrapper) { | ||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); | ||||
expect(wrapper.find('LicensePromptModal').exists()).toBeFalsy(); | expect(wrapper.find('LicensePromptModal').exists()).toBeFalsy(); | ||||
expect(wrapper.find('OnboardingModal').exists()).toBeFalsy(); | |||||
expect(wrapper.find('ProjectOnboardingModal').exists()).toBeFalsy(); | |||||
} | } | ||||
async function shouldDisplayOnboarding(wrapper: ShallowWrapper) { | async function shouldDisplayOnboarding(wrapper: ShallowWrapper) { | ||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); | ||||
expect(wrapper.find('OnboardingModal').exists()).toBeTruthy(); | |||||
expect(wrapper.find('ProjectOnboardingModal').exists()).toBeTruthy(); | |||||
} | } | ||||
async function shouldDisplayLicense(wrapper: ShallowWrapper) { | async function shouldDisplayLicense(wrapper: ShallowWrapper) { | ||||
currentEdition={EditionKey.enterprise} | currentEdition={EditionKey.enterprise} | ||||
currentUser={LOGGED_IN_USER} | currentUser={LOGGED_IN_USER} | ||||
location={{ pathname: 'foo/bar' }} | location={{ pathname: 'foo/bar' }} | ||||
skipOnboarding={jest.fn()} | |||||
skipOnboardingAction={jest.fn()} | |||||
{...props}> | {...props}> | ||||
<div /> | <div /> | ||||
</StartupModal> | |||||
</StartupModal>, | |||||
{ context: { router: { push: jest.fn() } } } | |||||
); | ); | ||||
} | } |
export default class EmbedDocsPopup extends React.PureComponent<Props> { | export default class EmbedDocsPopup extends React.PureComponent<Props> { | ||||
static contextTypes = { | static contextTypes = { | ||||
openOnboardingTutorial: PropTypes.func | |||||
openProjectOnboarding: PropTypes.func | |||||
}; | }; | ||||
onAnalyzeProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | onAnalyzeProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | ||||
event.preventDefault(); | event.preventDefault(); | ||||
event.currentTarget.blur(); | event.currentTarget.blur(); | ||||
this.context.openOnboardingTutorial(); | |||||
this.context.openProjectOnboarding(); | |||||
}; | }; | ||||
renderTitle(text: string) { | renderTitle(text: string) { |
type Props = StateProps & OwnProps; | type Props = StateProps & OwnProps; | ||||
class GlobalNav extends React.PureComponent<Props> { | class GlobalNav extends React.PureComponent<Props> { | ||||
static contextTypes = { openOnboardingTutorial: PropTypes.func }; | |||||
static contextTypes = { openProjectOnboarding: PropTypes.func }; | |||||
render() { | render() { | ||||
return ( | return ( | ||||
<Search appState={this.props.appState} currentUser={this.props.currentUser} /> | <Search appState={this.props.appState} currentUser={this.props.currentUser} /> | ||||
{isLoggedIn(this.props.currentUser) && | {isLoggedIn(this.props.currentUser) && | ||||
isSonarCloud() && ( | isSonarCloud() && ( | ||||
<GlobalNavPlus openOnboardingTutorial={this.context.openOnboardingTutorial} /> | |||||
<GlobalNavPlus openProjectOnboarding={this.context.openProjectOnboarding} /> | |||||
)} | )} | ||||
<GlobalNavUserContainer {...this.props} /> | <GlobalNavUserContainer {...this.props} /> | ||||
</ul> | </ul> |
import { translate } from '../../../../helpers/l10n'; | import { translate } from '../../../../helpers/l10n'; | ||||
interface Props { | interface Props { | ||||
openOnboardingTutorial: () => void; | |||||
openProjectOnboarding: () => void; | |||||
} | } | ||||
interface State { | interface State { | ||||
handleNewProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | handleNewProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | ||||
event.preventDefault(); | event.preventDefault(); | ||||
this.props.openOnboardingTutorial(); | |||||
this.props.openProjectOnboarding(); | |||||
}; | }; | ||||
openCreateOrganizationForm = () => this.setState({ createOrganization: true }); | openCreateOrganizationForm = () => this.setState({ createOrganization: true }); |
import { click } from '../../../../../helpers/testUtils'; | import { click } from '../../../../../helpers/testUtils'; | ||||
it('render', () => { | it('render', () => { | ||||
const wrapper = shallow(<GlobalNavPlus openOnboardingTutorial={jest.fn()} />); | |||||
const wrapper = shallow(<GlobalNavPlus openProjectOnboarding={jest.fn()} />); | |||||
expect(wrapper.is('Dropdown')).toBe(true); | expect(wrapper.is('Dropdown')).toBe(true); | ||||
expect(wrapper.find('Dropdown')).toMatchSnapshot(); | expect(wrapper.find('Dropdown')).toMatchSnapshot(); | ||||
}); | }); | ||||
it('opens onboarding', () => { | it('opens onboarding', () => { | ||||
const openOnboardingTutorial = jest.fn(); | |||||
const openProjectOnboarding = jest.fn(); | |||||
const wrapper = shallow( | const wrapper = shallow( | ||||
shallow(<GlobalNavPlus openOnboardingTutorial={openOnboardingTutorial} />) | |||||
shallow(<GlobalNavPlus openProjectOnboarding={openProjectOnboarding} />) | |||||
.find('Dropdown') | .find('Dropdown') | ||||
.prop('overlay') | .prop('overlay') | ||||
); | ); | ||||
click(wrapper.find('.js-new-project')); | click(wrapper.find('.js-new-project')); | ||||
expect(openOnboardingTutorial).toBeCalled(); | |||||
expect(openProjectOnboarding).toBeCalled(); | |||||
}); | }); |
<Route | <Route | ||||
path="onboarding" | path="onboarding" | ||||
component={lazyLoad(() => | component={lazyLoad(() => | ||||
import('../../apps/tutorials/onboarding/OnboardingPage') | |||||
import('../../apps/tutorials/projectOnboarding/ProjectOnboardingPage') | |||||
)} | )} | ||||
/> | /> | ||||
<Route path="organizations" childRoutes={organizationsRoutes} /> | <Route path="organizations" childRoutes={organizationsRoutes} /> |
export class NoFavoriteProjects extends React.PureComponent<StateProps> { | export class NoFavoriteProjects extends React.PureComponent<StateProps> { | ||||
static contextTypes = { | static contextTypes = { | ||||
openOnboardingTutorial: PropTypes.func | |||||
openProjectOnboarding: PropTypes.func | |||||
}; | }; | ||||
onAnalyzeProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | onAnalyzeProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | ||||
event.preventDefault(); | event.preventDefault(); | ||||
event.currentTarget.blur(); | event.currentTarget.blur(); | ||||
this.context.openOnboardingTutorial(); | |||||
this.context.openProjectOnboarding(); | |||||
}; | }; | ||||
render() { | render() { |
/* | |||||
* 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); |
/* | |||||
* 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(); | |||||
}); |
// 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> | |||||
`; |
/* | |||||
* 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> | |||||
); | |||||
} | |||||
} |
/* | |||||
* 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); |
// 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> | |||||
`; |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import Step from './Step'; | import Step from './Step'; | ||||
import LanguageStep from './LanguageStep'; | |||||
/*:: import type { Result } from './LanguageStep'; */ | |||||
import LanguageStep, { Result } from './LanguageStep'; | |||||
import JavaMaven from './commands/JavaMaven'; | import JavaMaven from './commands/JavaMaven'; | ||||
import JavaGradle from './commands/JavaGradle'; | import JavaGradle from './commands/JavaGradle'; | ||||
import DotNet from './commands/DotNet'; | import DotNet from './commands/DotNet'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { getHostUrl } from '../../../helpers/urls'; | 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 }); | this.setState({ result }); | ||||
const projectKey = result && result.language !== 'java' ? result.projectKey : undefined; | const projectKey = result && result.language !== 'java' ? result.projectKey : undefined; | ||||
this.props.onFinish(projectKey); | this.props.onFinish(projectKey); | ||||
); | ); | ||||
}; | }; | ||||
renderFormattedCommand = (...lines /*: Array<string> */) => ( | |||||
renderFormattedCommand = (...lines: Array<string>) => ( | |||||
// keep this "useless" concatentation for the readability reason | // keep this "useless" concatentation for the readability reason | ||||
// eslint-disable-next-line no-useless-concat | // eslint-disable-next-line no-useless-concat | ||||
<pre>{lines.join(' ' + '\\' + '\n' + ' ')}</pre> | <pre>{lines.join(' ' + '\\' + '\n' + ' ')}</pre> | ||||
} | } | ||||
}; | }; | ||||
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 = () => { | renderCommandForDotNet = () => { | ||||
const { token } = this.props; | |||||
const { result } = this.state; | |||||
if (!result || !result.projectKey || !token) { | |||||
return null; | |||||
} | |||||
return ( | return ( | ||||
<DotNet | <DotNet | ||||
host={getHostUrl()} | host={getHostUrl()} | ||||
organization={this.props.organization} | organization={this.props.organization} | ||||
// $FlowFixMe | |||||
projectKey={this.state.result.projectKey} | |||||
token={this.props.token} | |||||
projectKey={result.projectKey} | |||||
token={token} | |||||
/> | /> | ||||
); | ); | ||||
}; | }; | ||||
renderCommandForMSVC = () => { | renderCommandForMSVC = () => { | ||||
const { token } = this.props; | |||||
const { result } = this.state; | |||||
if (!result || !result.projectKey || !token) { | |||||
return null; | |||||
} | |||||
return ( | return ( | ||||
<Msvc | <Msvc | ||||
host={getHostUrl()} | host={getHostUrl()} | ||||
organization={this.props.organization} | 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; | renderResult = () => null; | ||||
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import NewProjectForm from './NewProjectForm'; | import NewProjectForm from './NewProjectForm'; | ||||
import RadioToggle from '../../../components/controls/RadioToggle'; | import RadioToggle from '../../../components/controls/RadioToggle'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { isSonarCloud } from '../../../helpers/system'; | 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 = () => { | isConfigured = () => { | ||||
const { language, javaBuild, cFamilyCompiler, os, projectKey } = this.state; | const { language, javaBuild, cFamilyCompiler, os, projectKey } = this.state; | ||||
} | } | ||||
}; | }; | ||||
handleLanguageChange = (language /*: string */) => { | |||||
handleLanguageChange = (language: string) => { | |||||
this.setState({ language }, this.handleChange); | this.setState({ language }, this.handleChange); | ||||
}; | }; | ||||
handleJavaBuildChange = (javaBuild /*: string */) => { | |||||
handleJavaBuildChange = (javaBuild: string) => { | |||||
this.setState({ javaBuild }, this.handleChange); | this.setState({ javaBuild }, this.handleChange); | ||||
}; | }; | ||||
handleCFamilyCompilerChange = (cFamilyCompiler /*: string */) => { | |||||
handleCFamilyCompilerChange = (cFamilyCompiler: string) => { | |||||
this.setState({ cFamilyCompiler }, this.handleChange); | this.setState({ cFamilyCompiler }, this.handleChange); | ||||
}; | }; | ||||
handleOSChange = (os /*: string */) => { | |||||
handleOSChange = (os: string) => { | |||||
this.setState({ os }, this.handleChange); | this.setState({ os }, this.handleChange); | ||||
}; | }; | ||||
handleProjectKeyDone = (projectKey /*: string */) => { | |||||
handleProjectKeyDone = (projectKey: string) => { | |||||
this.setState({ projectKey }, this.handleChange); | this.setState({ projectKey }, this.handleChange); | ||||
}; | }; | ||||
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { debounce } from 'lodash'; | import { debounce } from 'lodash'; | ||||
import { | import { | ||||
createOrganization, | createOrganization, | ||||
import { DeleteButton, SubmitButton } from '../../../components/ui/buttons'; | import { DeleteButton, SubmitButton } from '../../../components/ui/buttons'; | ||||
import { translate } from '../../../helpers/l10n'; | 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); | super(props); | ||||
this.state = { | this.state = { | ||||
done: props.organization != null, | done: props.organization != null, | ||||
} | } | ||||
}; | }; | ||||
validateOrganization = (organization /*: string */) => { | |||||
validateOrganization = (organization: string) => { | |||||
getOrganization(organization).then(response => { | getOrganization(organization).then(response => { | ||||
if (this.mounted) { | if (this.mounted) { | ||||
this.setState({ unique: response == null }); | this.setState({ unique: response == null }); | ||||
}); | }); | ||||
}; | }; | ||||
sanitizeOrganization = (organization /*: string */) => | |||||
sanitizeOrganization = (organization: string) => | |||||
organization | organization | ||||
.toLowerCase() | .toLowerCase() | ||||
.replace(/[^a-z0-9-]/, '') | .replace(/[^a-z0-9-]/, '') | ||||
.replace(/^-/, ''); | .replace(/^-/, ''); | ||||
handleOrganizationChange = (event /*: { target: HTMLInputElement } */) => { | |||||
handleOrganizationChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||||
const organization = this.sanitizeOrganization(event.target.value); | const organization = this.sanitizeOrganization(event.target.value); | ||||
this.setState({ organization }); | this.setState({ organization }); | ||||
this.validateOrganization(organization); | this.validateOrganization(organization); | ||||
}; | }; | ||||
handleOrganizationCreate = (event /*: Event */) => { | |||||
handleOrganizationCreate = (event: React.FormEvent<HTMLFormElement>) => { | |||||
event.preventDefault(); | event.preventDefault(); | ||||
const { organization } = this.state; | const { organization } = this.state; | ||||
if (organization) { | if (organization) { |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { createProject, deleteProject } from '../../../api/components'; | import { createProject, deleteProject } from '../../../api/components'; | ||||
import { DeleteButton, SubmitButton } from '../../../components/ui/buttons'; | import { DeleteButton, SubmitButton } from '../../../components/ui/buttons'; | ||||
import { translate } from '../../../helpers/l10n'; | 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); | super(props); | ||||
this.state = { | this.state = { | ||||
done: props.projectKey != null, | done: props.projectKey != null, | ||||
} | } | ||||
}; | }; | ||||
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) }); | this.setState({ projectKey: this.sanitizeProjectKey(event.target.value) }); | ||||
}; | }; | ||||
handleProjectCreate = (event /*: Event */) => { | |||||
handleProjectCreate = (event: React.FormEvent<HTMLFormElement>) => { | |||||
event.preventDefault(); | event.preventDefault(); | ||||
const { projectKey } = this.state; | const { projectKey } = this.state; | ||||
const data /*: { [string]: string } */ = { | |||||
const data: { | |||||
name: string; | |||||
project: string; | |||||
organization?: string; | |||||
} = { | |||||
name: projectKey, | name: projectKey, | ||||
project: projectKey | project: projectKey | ||||
}; | }; |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import classNames from 'classnames'; | |||||
import * as React from 'react'; | |||||
import * as classNames from 'classnames'; | |||||
import { sortBy } from 'lodash'; | import { sortBy } from 'lodash'; | ||||
import Step from './Step'; | import Step from './Step'; | ||||
import NewOrganizationForm from './NewOrganizationForm'; | import NewOrganizationForm from './NewOrganizationForm'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { Button } from '../../../components/ui/buttons'; | 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, | loading: true, | ||||
existingOrganizations: [], | existingOrganizations: [], | ||||
selection: 'personal' | selection: 'personal' | ||||
} | } | ||||
}; | }; | ||||
handlePersonalClick = (event /*: Event */) => { | |||||
handlePersonalClick = (event: React.MouseEvent<HTMLAnchorElement>) => { | |||||
event.preventDefault(); | event.preventDefault(); | ||||
this.setState({ selection: 'personal' }); | this.setState({ selection: 'personal' }); | ||||
}; | }; | ||||
handleExistingClick = (event /*: Event */) => { | |||||
handleExistingClick = (event: React.MouseEvent<HTMLAnchorElement>) => { | |||||
event.preventDefault(); | event.preventDefault(); | ||||
this.setState({ selection: 'existing' }); | this.setState({ selection: 'existing' }); | ||||
}; | }; | ||||
handleNewClick = (event /*: Event */) => { | |||||
handleNewClick = (event: React.MouseEvent<HTMLAnchorElement>) => { | |||||
event.preventDefault(); | event.preventDefault(); | ||||
this.setState({ selection: 'new' }); | this.setState({ selection: 'new' }); | ||||
}; | }; | ||||
handleOrganizationCreate = (newOrganization /*: string */) => { | |||||
handleOrganizationCreate = (newOrganization: string) => { | |||||
this.setState({ newOrganization }); | this.setState({ newOrganization }); | ||||
}; | }; | ||||
this.setState({ newOrganization: undefined }); | this.setState({ newOrganization: undefined }); | ||||
}; | }; | ||||
handleExistingOrganizationSelect = ({ value } /*: { value: string } */) => { | |||||
handleExistingOrganizationSelect = ({ value }: { value: string }) => { | |||||
this.setState({ existingOrganization: value }); | this.setState({ existingOrganization: value }); | ||||
}; | }; | ||||
/* | |||||
* 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); |
onFinish: () => void; | 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 ( | return ( | ||||
<Modal contentLabel={translate('tutorials.onboarding')} large={true}> | <Modal contentLabel={translate('tutorials.onboarding')} large={true}> | ||||
<OnboardingContainer {...props} /> | |||||
<ProjectOnboarding {...props} /> | |||||
</Modal> | </Modal> | ||||
); | ); | ||||
} | } |
import * as React from 'react'; | import * as React from 'react'; | ||||
import * as PropTypes from 'prop-types'; | import * as PropTypes from 'prop-types'; | ||||
import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||
import OnboardingModal from './OnboardingModal'; | |||||
import ProjectOnboardingModal from './ProjectOnboardingModal'; | |||||
import { skipOnboarding } from '../../../store/users/actions'; | import { skipOnboarding } from '../../../store/users/actions'; | ||||
interface DispatchProps { | interface DispatchProps { | ||||
skipOnboarding: () => void; | skipOnboarding: () => void; | ||||
} | } | ||||
export class OnboardingPage extends React.PureComponent<DispatchProps> { | |||||
export class ProjectOnboardingPage extends React.PureComponent<DispatchProps> { | |||||
static contextTypes = { | static contextTypes = { | ||||
router: PropTypes.object.isRequired | router: PropTypes.object.isRequired | ||||
}; | }; | ||||
}; | }; | ||||
render() { | render() { | ||||
return <OnboardingModal onFinish={this.onSkipOnboardingTutorial} />; | |||||
return <ProjectOnboardingModal onFinish={this.onSkipOnboardingTutorial} />; | |||||
} | } | ||||
} | } | ||||
const mapDispatchToProps: DispatchProps = { skipOnboarding }; | const mapDispatchToProps: DispatchProps = { skipOnboarding }; | ||||
export default connect<{}, DispatchProps, {}>(null, mapDispatchToProps)(OnboardingPage); | |||||
export default connect<{}, DispatchProps>(null, mapDispatchToProps)(ProjectOnboardingPage); |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import * as classNames from 'classnames'; | |||||
import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon'; | import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon'; | ||||
import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon'; | import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon'; | ||||
import { getTasksForComponent } from '../../../api/ce'; | import { getTasksForComponent } from '../../../api/ce'; | ||||
const INTERVAL = 5000; | const INTERVAL = 5000; | ||||
const TIMEOUT = 10 * 60 * 1000; // 10 min | 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() { | componentDidMount() { | ||||
this.mounted = true; | this.mounted = true; | ||||
this.watch(); | this.watch(); | ||||
this.timeout = setTimeout(this.props.onTimeout, TIMEOUT); | |||||
this.timeout = window.setTimeout(this.props.onTimeout, TIMEOUT); | |||||
} | } | ||||
componentWillUnmount() { | componentWillUnmount() { | ||||
this.mounted = false; | this.mounted = false; | ||||
} | } | ||||
watch = () => (this.interval = setTimeout(this.checkProject, INTERVAL)); | |||||
watch = () => (this.interval = window.setTimeout(this.checkProject, INTERVAL)); | |||||
checkProject = () => { | checkProject = () => { | ||||
const { projectKey } = this.props; | const { projectKey } = this.props; | ||||
render() { | render() { | ||||
const { inQueue, status } = this.state; | const { inQueue, status } = this.state; | ||||
const className = 'pull-left note'; | |||||
if (status === STATUSES.SUCCESS) { | if (status === STATUSES.SUCCESS) { | ||||
return ( | return ( | ||||
<div className="big-spacer-top note text-center"> | |||||
<div className={classNames(className, 'display-inline-flex-center')}> | |||||
<AlertSuccessIcon className="spacer-right" /> | <AlertSuccessIcon className="spacer-right" /> | ||||
{translate('onboarding.project_watcher.finished')} | {translate('onboarding.project_watcher.finished')} | ||||
</div> | </div> | ||||
if (inQueue || status === STATUSES.PENDING || status === STATUSES.IN_PROGRESS) { | if (inQueue || status === STATUSES.PENDING || status === STATUSES.IN_PROGRESS) { | ||||
return ( | return ( | ||||
<div className="big-spacer-top note text-center"> | |||||
<div className={className}> | |||||
<i className="spinner spacer-right" /> | <i className="spinner spacer-right" /> | ||||
{translate('onboarding.project_watcher.in_progress')} | {translate('onboarding.project_watcher.in_progress')} | ||||
</div> | </div> | ||||
if (status != null) { | if (status != null) { | ||||
return ( | return ( | ||||
<div className="big-spacer-top note text-center"> | |||||
<div className={classNames(className, 'display-inline-flex-center')}> | |||||
<AlertErrorIcon className="spacer-right" /> | <AlertErrorIcon className="spacer-right" /> | ||||
{translate('onboarding.project_watcher.failed')} | {translate('onboarding.project_watcher.failed')} | ||||
</div> | </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>; | |||||
} | } | ||||
} | } |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @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', { | const className = classNames('boxed-group', 'onboarding-step', { | ||||
'is-open': props.open, | 'is-open': props.open, | ||||
'is-finished': props.finished | 'is-finished': props.finished | ||||
const clickable = !props.open && props.finished; | const clickable = !props.open && props.finished; | ||||
const handleClick = (event /*: Event */) => { | |||||
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => { | |||||
event.preventDefault(); | event.preventDefault(); | ||||
props.onOpen(); | props.onOpen(); | ||||
}; | }; |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import classNames from 'classnames'; | |||||
import * as React from 'react'; | |||||
import * as classNames from 'classnames'; | |||||
import Step from './Step'; | import Step from './Step'; | ||||
import { getTokens, generateToken, revokeToken } from '../../../api/user-tokens'; | import { getTokens, generateToken, revokeToken } from '../../../api/user-tokens'; | ||||
import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon'; | import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon'; | ||||
import { DeleteButton, SubmitButton, Button } from '../../../components/ui/buttons'; | import { DeleteButton, SubmitButton, Button } from '../../../components/ui/buttons'; | ||||
import { translate } from '../../../helpers/l10n'; | 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, | canUseExisting: false, | ||||
existingToken: '', | existingToken: '', | ||||
loading: false, | loading: false, | ||||
); | ); | ||||
}; | }; | ||||
handleTokenNameChange = (event /*: { target: HTMLInputElement } */) => { | |||||
handleTokenNameChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||||
this.setState({ tokenName: event.target.value }); | this.setState({ tokenName: event.target.value }); | ||||
}; | }; | ||||
handleTokenGenerate = (event /*: Event */) => { | |||||
handleTokenGenerate = (event: React.FormEvent<HTMLFormElement>) => { | |||||
event.preventDefault(); | event.preventDefault(); | ||||
const { tokenName } = this.state; | const { tokenName } = this.state; | ||||
if (tokenName) { | if (tokenName) { | ||||
this.setState({ loading: true }); | 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); | |||||
} | } | ||||
}; | }; | ||||
const { tokenName } = this.state; | const { tokenName } = this.state; | ||||
if (tokenName) { | if (tokenName) { | ||||
this.setState({ loading: true }); | 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); | |||||
} | } | ||||
}; | }; | ||||
} | } | ||||
}; | }; | ||||
handleGenerateClick = (event /*: Event */) => { | |||||
handleGenerateClick = (event: React.MouseEvent<HTMLAnchorElement>) => { | |||||
event.preventDefault(); | event.preventDefault(); | ||||
this.setState({ selection: 'generate' }); | this.setState({ selection: 'generate' }); | ||||
}; | }; | ||||
handleUseExistingClick = (event /*: Event */) => { | |||||
handleUseExistingClick = (event: React.MouseEvent<HTMLAnchorElement>) => { | |||||
event.preventDefault(); | event.preventDefault(); | ||||
this.setState({ selection: 'use-existing' }); | this.setState({ selection: 'use-existing' }); | ||||
}; | }; | ||||
handleExisingTokenChange = (event /*: { currentTarget: HTMLInputElement } */) => { | |||||
handleExisingTokenChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||||
this.setState({ existingToken: event.currentTarget.value }); | this.setState({ existingToken: event.currentTarget.value }); | ||||
}; | }; | ||||
stopLoading = () => { | |||||
if (this.mounted) { | |||||
this.setState({ loading: false }); | |||||
} | |||||
}; | |||||
renderGenerateOption = () => ( | renderGenerateOption = () => ( | ||||
<div> | <div> | ||||
{this.state.canUseExisting ? ( | {this.state.canUseExisting ? ( | ||||
'is-checked': this.state.selection === 'generate' | 'is-checked': this.state.selection === 'generate' | ||||
})} | })} | ||||
/> | /> | ||||
{translate('onboading.token.generate_token')} | |||||
{translate('onboarding.token.generate_token')} | |||||
</a> | </a> | ||||
) : ( | ) : ( | ||||
translate('onboading.token.generate_token') | |||||
translate('onboarding.token.generate_token') | |||||
)} | )} | ||||
{this.state.selection === 'generate' && ( | {this.state.selection === 'generate' && ( | ||||
<div className="big-spacer-top"> | <div className="big-spacer-top"> | ||||
autoFocus={true} | autoFocus={true} | ||||
className="input-large spacer-right text-middle" | className="input-large spacer-right text-middle" | ||||
onChange={this.handleTokenNameChange} | onChange={this.handleTokenNameChange} | ||||
placeholder={translate('onboading.token.generate_token.placeholder')} | |||||
placeholder={translate('onboarding.token.generate_token.placeholder')} | |||||
required={true} | required={true} | ||||
type="text" | type="text" | ||||
value={this.state.tokenName || ''} | value={this.state.tokenName || ''} |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import LanguageStep from '../LanguageStep'; | import LanguageStep from '../LanguageStep'; | ||||
import { isSonarCloud } from '../../../../helpers/system'; | import { isSonarCloud } from '../../../../helpers/system'; | ||||
jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() })); | jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() })); | ||||
beforeEach(() => { | beforeEach(() => { | ||||
isSonarCloud.mockImplementation(() => false); | |||||
(isSonarCloud as jest.Mock<any>).mockImplementation(() => false); | |||||
}); | }); | ||||
it('selects java', () => { | it('selects java', () => { | ||||
const onDone = jest.fn(); | const onDone = jest.fn(); | ||||
const wrapper = shallow(<LanguageStep onDone={onDone} onReset={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(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
wrapper | |||||
(wrapper | |||||
.find('RadioToggle') | .find('RadioToggle') | ||||
.at(1) | .at(1) | ||||
.prop('onCheck')('maven'); | |||||
.prop('onCheck') as Function)('maven'); | |||||
wrapper.update(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
expect(onDone).lastCalledWith({ language: 'java', javaBuild: 'maven' }); | expect(onDone).lastCalledWith({ language: 'java', javaBuild: 'maven' }); | ||||
wrapper | |||||
(wrapper | |||||
.find('RadioToggle') | .find('RadioToggle') | ||||
.at(1) | .at(1) | ||||
.prop('onCheck')('gradle'); | |||||
.prop('onCheck') as Function)('gradle'); | |||||
wrapper.update(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
expect(onDone).lastCalledWith({ language: 'java', javaBuild: 'gradle' }); | expect(onDone).lastCalledWith({ language: 'java', javaBuild: 'gradle' }); | ||||
const onDone = jest.fn(); | const onDone = jest.fn(); | ||||
const wrapper = shallow(<LanguageStep onDone={onDone} onReset={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(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); | 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' }); | expect(onDone).lastCalledWith({ language: 'dotnet', projectKey: 'project-foo' }); | ||||
}); | }); | ||||
it('selects c-family', () => { | it('selects c-family', () => { | ||||
isSonarCloud.mockImplementation(() => true); | |||||
(isSonarCloud as jest.Mock<any>).mockImplementation(() => true); | |||||
const onDone = jest.fn(); | const onDone = jest.fn(); | ||||
const wrapper = shallow(<LanguageStep onDone={onDone} onReset={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(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
wrapper | |||||
(wrapper | |||||
.find('RadioToggle') | .find('RadioToggle') | ||||
.at(1) | .at(1) | ||||
.prop('onCheck')('msvc'); | |||||
.prop('onCheck') as Function)('msvc'); | |||||
wrapper.update(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
wrapper.find('NewProjectForm').prop('onDone')('project-foo'); | |||||
(wrapper.find('NewProjectForm').prop('onDone') as Function)('project-foo'); | |||||
expect(onDone).lastCalledWith({ | expect(onDone).lastCalledWith({ | ||||
language: 'c-family', | language: 'c-family', | ||||
cFamilyCompiler: 'msvc', | cFamilyCompiler: 'msvc', | ||||
projectKey: 'project-foo' | projectKey: 'project-foo' | ||||
}); | }); | ||||
wrapper | |||||
(wrapper | |||||
.find('RadioToggle') | .find('RadioToggle') | ||||
.at(1) | .at(1) | ||||
.prop('onCheck')('clang-gcc'); | |||||
.prop('onCheck') as Function)('clang-gcc'); | |||||
wrapper.update(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
wrapper | |||||
(wrapper | |||||
.find('RadioToggle') | .find('RadioToggle') | ||||
.at(2) | .at(2) | ||||
.prop('onCheck')('linux'); | |||||
.prop('onCheck') as Function)('linux'); | |||||
wrapper.update(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
wrapper.find('NewProjectForm').prop('onDone')('project-foo'); | |||||
(wrapper.find('NewProjectForm').prop('onDone') as Function)('project-foo'); | |||||
expect(onDone).lastCalledWith({ | expect(onDone).lastCalledWith({ | ||||
language: 'c-family', | language: 'c-family', | ||||
cFamilyCompiler: 'clang-gcc', | cFamilyCompiler: 'clang-gcc', | ||||
const onDone = jest.fn(); | const onDone = jest.fn(); | ||||
const wrapper = shallow(<LanguageStep onDone={onDone} onReset={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(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
wrapper | |||||
(wrapper | |||||
.find('RadioToggle') | .find('RadioToggle') | ||||
.at(1) | .at(1) | ||||
.prop('onCheck')('mac'); | |||||
.prop('onCheck') as Function)('mac'); | |||||
wrapper.update(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); | 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' }); | expect(onDone).lastCalledWith({ language: 'other', os: 'mac', projectKey: 'project-foo' }); | ||||
}); | }); |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { mount } from 'enzyme'; | import { mount } from 'enzyme'; | ||||
import NewOrganizationForm from '../NewOrganizationForm'; | import NewOrganizationForm from '../NewOrganizationForm'; | ||||
import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils'; | import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils'; | ||||
const wrapper = mount(<NewOrganizationForm onDelete={onDelete} onDone={jest.fn()} />); | const wrapper = mount(<NewOrganizationForm onDelete={onDelete} onDone={jest.fn()} />); | ||||
wrapper.setState({ done: true, loading: false, organization: 'foo' }); | wrapper.setState({ done: true, loading: false, organization: 'foo' }); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
wrapper.find('DeleteButton').prop('onClick')(); | |||||
(wrapper.find('DeleteButton').prop('onClick') as Function)(); | |||||
wrapper.update(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); // spinner | expect(wrapper).toMatchSnapshot(); // spinner | ||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { mount } from 'enzyme'; | import { mount } from 'enzyme'; | ||||
import NewProjectForm from '../NewProjectForm'; | import NewProjectForm from '../NewProjectForm'; | ||||
import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils'; | import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils'; | ||||
const wrapper = mount(<NewProjectForm onDelete={onDelete} onDone={jest.fn()} />); | const wrapper = mount(<NewProjectForm onDelete={onDelete} onDone={jest.fn()} />); | ||||
wrapper.setState({ done: true, loading: false, projectKey: 'foo' }); | wrapper.setState({ done: true, loading: false, projectKey: 'foo' }); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
wrapper.find('DeleteButton').prop('onClick')(); | |||||
(wrapper.find('DeleteButton').prop('onClick') as Function)(); | |||||
wrapper.update(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); // spinner | expect(wrapper).toMatchSnapshot(); // spinner | ||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { mount } from 'enzyme'; | import { mount } from 'enzyme'; | ||||
import OrganizationStep from '../OrganizationStep'; | import OrganizationStep from '../OrganizationStep'; | ||||
import { click, waitAndUpdate } from '../../../../helpers/testUtils'; | import { click, waitAndUpdate } from '../../../../helpers/testUtils'; | ||||
const currentUser = { isLoggedIn: true, login: 'user' }; | const currentUser = { isLoggedIn: true, login: 'user' }; | ||||
beforeEach(() => { | beforeEach(() => { | ||||
getOrganizations.mockClear(); | |||||
(getOrganizations as jest.Mock<any>).mockClear(); | |||||
}); | }); | ||||
// FIXME | // FIXME | ||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); | ||||
click(wrapper.find('.js-existing')); | click(wrapper.find('.js-existing')); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
wrapper | |||||
(wrapper | |||||
.find('Select') | .find('Select') | ||||
.first() | .first() | ||||
.prop('onChange')({ value: 'another' }); | |||||
.prop('onChange') as Function)({ value: 'another' }); | |||||
wrapper.update(); | wrapper.update(); | ||||
click(wrapper.find('[className="js-continue"]')); | click(wrapper.find('[className="js-continue"]')); | ||||
expect(onContinue).toBeCalledWith('another'); | expect(onContinue).toBeCalledWith('another'); | ||||
); | ); | ||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); | ||||
click(wrapper.find('.js-new')); | click(wrapper.find('.js-new')); | ||||
wrapper.find('NewOrganizationForm').prop('onDone')('new'); | |||||
(wrapper.find('NewOrganizationForm').prop('onDone') as Function)('new'); | |||||
wrapper.update(); | wrapper.update(); | ||||
click(wrapper.find('[className="js-continue"]')); | click(wrapper.find('[className="js-continue"]')); | ||||
expect(onContinue).toBeCalledWith('new'); | expect(onContinue).toBeCalledWith('new'); |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow, mount } from 'enzyme'; | import { shallow, mount } from 'enzyme'; | ||||
import Onboarding from '../Onboarding'; | |||||
import { ProjectOnboarding } from '../ProjectOnboarding'; | |||||
import { click, doAsync } from '../../../../helpers/testUtils'; | import { click, doAsync } from '../../../../helpers/testUtils'; | ||||
import { getInstance, isSonarCloud } from '../../../../helpers/system'; | import { getInstance, isSonarCloud } from '../../../../helpers/system'; | ||||
const currentUser = { login: 'admin', isLoggedIn: true }; | const currentUser = { login: 'admin', isLoggedIn: true }; | ||||
it('guides for on-premise', () => { | 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( | const wrapper = shallow( | ||||
<Onboarding | |||||
className="modal-container" | |||||
<ProjectOnboarding | |||||
currentUser={currentUser} | currentUser={currentUser} | ||||
onFinish={jest.fn()} | onFinish={jest.fn()} | ||||
organizationsEnabled={false} | organizationsEnabled={false} | ||||
); | ); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
// $FlowFixMe | |||||
wrapper.instance().handleTokenDone('abcd1234'); | |||||
(wrapper.instance() as ProjectOnboarding).handleTokenDone('abcd1234'); | |||||
wrapper.update(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
}); | }); | ||||
it('guides for sonarcloud', () => { | 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( | const wrapper = shallow( | ||||
<Onboarding currentUser={currentUser} onFinish={jest.fn()} organizationsEnabled={true} /> | |||||
<ProjectOnboarding currentUser={currentUser} onFinish={jest.fn()} organizationsEnabled={true} /> | |||||
); | ); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
// $FlowFixMe | |||||
wrapper.instance().handleOrganizationDone('my-org'); | |||||
(wrapper.instance() as ProjectOnboarding).handleOrganizationDone('my-org'); | |||||
wrapper.update(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
// $FlowFixMe | |||||
wrapper.instance().handleTokenDone('abcd1234'); | |||||
(wrapper.instance() as ProjectOnboarding).handleTokenDone('abcd1234'); | |||||
wrapper.update(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
}); | }); | ||||
it('finishes', () => { | 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 onFinish = jest.fn(); | ||||
const wrapper = mount( | 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(() => { | return doAsync(() => { | ||||
expect(onFinish).toBeCalled(); | expect(onFinish).toBeCalled(); | ||||
}); | }); |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow, mount } from 'enzyme'; | import { shallow, mount } from 'enzyme'; | ||||
import ProjectWatcher from '../ProjectWatcher'; | import ProjectWatcher from '../ProjectWatcher'; | ||||
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import Step from '../Step'; | import Step from '../Step'; | ||||
import { click } from '../../../../helpers/testUtils'; | import { click } from '../../../../helpers/testUtils'; |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { mount } from 'enzyme'; | import { mount } from 'enzyme'; | ||||
import TokenStep from '../TokenStep'; | import TokenStep from '../TokenStep'; | ||||
import { change, click, submit, waitAndUpdate } from '../../../../helpers/testUtils'; | import { change, click, submit, waitAndUpdate } from '../../../../helpers/testUtils'; | ||||
await new Promise(setImmediate); | await new Promise(setImmediate); | ||||
wrapper.setState({ token: 'abcd1234', tokenName: 'my token' }); | wrapper.setState({ token: 'abcd1234', tokenName: 'my token' }); | ||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
wrapper.find('DeleteButton').prop('onClick')(); | |||||
(wrapper.find('DeleteButton').prop('onClick') as Function)(); | |||||
wrapper.update(); | wrapper.update(); | ||||
expect(wrapper).toMatchSnapshot(); // spinner | expect(wrapper).toMatchSnapshot(); // spinner | ||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); |
// 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> | |||||
`; |
exports[`renders 1`] = ` | exports[`renders 1`] = ` | ||||
<div | <div | ||||
className="big-spacer-top note text-center" | |||||
className="pull-left note" | |||||
> | > | ||||
onboarding.project_watcher.not_started | onboarding.project_watcher.not_started | ||||
</div> | </div> | ||||
exports[`renders 2`] = ` | exports[`renders 2`] = ` | ||||
<div | <div | ||||
className="big-spacer-top note text-center" | |||||
className="pull-left note" | |||||
> | > | ||||
<i | <i | ||||
className="spinner spacer-right" | className="spinner spacer-right" | ||||
exports[`renders 3`] = ` | exports[`renders 3`] = ` | ||||
<div | <div | ||||
className="big-spacer-top note text-center" | |||||
className="pull-left note display-inline-flex-center" | |||||
> | > | ||||
<AlertSuccessIcon | <AlertSuccessIcon | ||||
className="spacer-right" | className="spacer-right" | ||||
exports[`renders 4`] = ` | exports[`renders 4`] = ` | ||||
<div | <div | ||||
className="big-spacer-top note text-center" | |||||
className="pull-left note" | |||||
> | > | ||||
<i | <i | ||||
className="spinner spacer-right" | className="spinner spacer-right" |
<i | <i | ||||
className="icon-radio spacer-right is-checked" | className="icon-radio spacer-right is-checked" | ||||
/> | /> | ||||
onboading.token.generate_token | |||||
onboarding.token.generate_token | |||||
</a> | </a> | ||||
<div | <div | ||||
className="big-spacer-top" | className="big-spacer-top" | ||||
autoFocus={true} | autoFocus={true} | ||||
className="input-large spacer-right text-middle" | className="input-large spacer-right text-middle" | ||||
onChange={[Function]} | onChange={[Function]} | ||||
placeholder="onboading.token.generate_token.placeholder" | |||||
placeholder="onboarding.token.generate_token.placeholder" | |||||
required={true} | required={true} | ||||
type="text" | type="text" | ||||
value="" | value="" | ||||
<i | <i | ||||
className="icon-radio spacer-right is-checked" | className="icon-radio spacer-right is-checked" | ||||
/> | /> | ||||
onboading.token.generate_token | |||||
onboarding.token.generate_token | |||||
</a> | </a> | ||||
<div | <div | ||||
className="big-spacer-top" | className="big-spacer-top" | ||||
autoFocus={true} | autoFocus={true} | ||||
className="input-large spacer-right text-middle" | className="input-large spacer-right text-middle" | ||||
onChange={[Function]} | onChange={[Function]} | ||||
placeholder="onboading.token.generate_token.placeholder" | |||||
placeholder="onboarding.token.generate_token.placeholder" | |||||
required={true} | required={true} | ||||
type="text" | type="text" | ||||
value="my token" | value="my token" | ||||
<i | <i | ||||
className="icon-radio spacer-right is-checked" | className="icon-radio spacer-right is-checked" | ||||
/> | /> | ||||
onboading.token.generate_token | |||||
onboarding.token.generate_token | |||||
</a> | </a> | ||||
<div | <div | ||||
className="big-spacer-top" | className="big-spacer-top" | ||||
autoFocus={true} | autoFocus={true} | ||||
className="input-large spacer-right text-middle" | className="input-large spacer-right text-middle" | ||||
onChange={[Function]} | onChange={[Function]} | ||||
placeholder="onboading.token.generate_token.placeholder" | |||||
placeholder="onboarding.token.generate_token.placeholder" | |||||
required={true} | required={true} | ||||
type="text" | type="text" | ||||
value="" | value="" |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { translate } from '../../../../helpers/l10n'; | 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', | win: 'build-wrapper-win-x86.zip', | ||||
linux: 'build-wrapper-linux-x86.zip', | linux: 'build-wrapper-linux-x86.zip', | ||||
mac: 'build-wrapper-macosx-x86.zip' | mac: 'build-wrapper-macosx-x86.zip' | ||||
}; | }; | ||||
export default function BuildWrapper(props /*: Props */) { | |||||
export default function BuildWrapper(props: Props) { | |||||
return ( | return ( | ||||
<div className={props.className}> | <div className={props.className}> | ||||
<h4 className="spacer-bottom"> | <h4 className="spacer-bottom"> | ||||
<a | <a | ||||
className="button" | className="button" | ||||
download={filenames[props.os]} | download={filenames[props.os]} | ||||
href={window.baseUrl + '/static/cpp/' + filenames[props.os]} | |||||
href={`${getBaseUrl()}/static/cpp/${filenames[props.os]}`} | |||||
target="_blank"> | target="_blank"> | ||||
{translate('download_verb')} | {translate('download_verb')} | ||||
</a> | </a> |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import SQScanner from './SQScanner'; | import SQScanner from './SQScanner'; | ||||
import BuildWrapper from './BuildWrapper'; | import BuildWrapper from './BuildWrapper'; | ||||
import CodeSnippet from '../../../../components/common/CodeSnippet'; | import CodeSnippet from '../../../../components/common/CodeSnippet'; | ||||
import InstanceMessage from '../../../../components/common/InstanceMessage'; | import InstanceMessage from '../../../../components/common/InstanceMessage'; | ||||
import { translate } from '../../../../helpers/l10n'; | 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', | linux: 'build-wrapper-linux-x86-64', | ||||
win: 'build-wrapper-win-x86-64.exe', | win: 'build-wrapper-win-x86-64.exe', | ||||
mac: 'build-wrapper-macosx-x86' | 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 command1 = `${executables[props.os]} --out-dir bw-output make clean all`; | ||||
const command2 = [ | const command2 = [ |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import MSBuildScanner from './MSBuildScanner'; | import MSBuildScanner from './MSBuildScanner'; | ||||
import CodeSnippet from '../../../../components/common/CodeSnippet'; | import CodeSnippet from '../../../../components/common/CodeSnippet'; | ||||
import InstanceMessage from '../../../../components/common/InstanceMessage'; | import InstanceMessage from '../../../../components/common/InstanceMessage'; | ||||
import { translate } from '../../../../helpers/l10n'; | 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 = [ | const command1 = [ | ||||
'SonarQube.Scanner.MSBuild.exe begin', | 'SonarQube.Scanner.MSBuild.exe begin', | ||||
`/k:"${props.projectKey}"`, | `/k:"${props.projectKey}"`, |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import CodeSnippet from '../../../../components/common/CodeSnippet'; | import CodeSnippet from '../../../../components/common/CodeSnippet'; | ||||
import InstanceMessage from '../../../../components/common/InstanceMessage'; | import InstanceMessage from '../../../../components/common/InstanceMessage'; | ||||
import { translate } from '../../../../helpers/l10n'; | 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 config = 'plugins {\n id "org.sonarqube" version "2.6"\n}'; | ||||
const command = [ | const command = [ |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import CodeSnippet from '../../../../components/common/CodeSnippet'; | import CodeSnippet from '../../../../components/common/CodeSnippet'; | ||||
import InstanceMessage from '../../../../components/common/InstanceMessage'; | import InstanceMessage from '../../../../components/common/InstanceMessage'; | ||||
import { translate } from '../../../../helpers/l10n'; | 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 = [ | const command = [ | ||||
'mvn sonar:sonar', | 'mvn sonar:sonar', | ||||
props.organization && `-Dsonar.organization=${props.organization}`, | props.organization && `-Dsonar.organization=${props.organization}`, |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { translate } from '../../../../helpers/l10n'; | 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 ( | return ( | ||||
<div className={props.className}> | <div className={props.className}> | ||||
<h4 className="spacer-bottom">{translate('onboarding.analysis.msbuild.header')}</h4> | <h4 className="spacer-bottom">{translate('onboarding.analysis.msbuild.header')}</h4> | ||||
<a | <a | ||||
className="button" | className="button" | ||||
href="http://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html" | href="http://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html" | ||||
rel="noopener noreferrer" | |||||
target="_blank"> | target="_blank"> | ||||
{translate('download_verb')} | {translate('download_verb')} | ||||
</a> | </a> |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import MSBuildScanner from './MSBuildScanner'; | import MSBuildScanner from './MSBuildScanner'; | ||||
import BuildWrapper from './BuildWrapper'; | import BuildWrapper from './BuildWrapper'; | ||||
import CodeSnippet from '../../../../components/common/CodeSnippet'; | import CodeSnippet from '../../../../components/common/CodeSnippet'; | ||||
import InstanceMessage from '../../../../components/common/InstanceMessage'; | import InstanceMessage from '../../../../components/common/InstanceMessage'; | ||||
import { translate } from '../../../../helpers/l10n'; | 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 = [ | const command1 = [ | ||||
'SonarQube.Scanner.MSBuild.exe begin', | 'SonarQube.Scanner.MSBuild.exe begin', | ||||
`/k:"${props.projectKey}"`, | `/k:"${props.projectKey}"`, |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import SQScanner from './SQScanner'; | import SQScanner from './SQScanner'; | ||||
import CodeSnippet from '../../../../components/common/CodeSnippet'; | import CodeSnippet from '../../../../components/common/CodeSnippet'; | ||||
import InstanceMessage from '../../../../components/common/InstanceMessage'; | import InstanceMessage from '../../../../components/common/InstanceMessage'; | ||||
import { translate } from '../../../../helpers/l10n'; | 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 = [ | const command = [ | ||||
props.os === 'win' ? 'sonar-scanner.bat' : 'sonar-scanner', | props.os === 'win' ? 'sonar-scanner.bat' : 'sonar-scanner', | ||||
`-Dsonar.projectKey=${props.projectKey}`, | `-Dsonar.projectKey=${props.projectKey}`, |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { translate } from '../../../../helpers/l10n'; | 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 ( | return ( | ||||
<div className={props.className}> | <div className={props.className}> | ||||
<h4 className="spacer-bottom"> | <h4 className="spacer-bottom"> | ||||
<a | <a | ||||
className="button" | className="button" | ||||
href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" | href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" | ||||
rel="noopener noreferrer" | |||||
target="_blank"> | target="_blank"> | ||||
{translate('download_verb')} | {translate('download_verb')} | ||||
</a> | </a> |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import BuildWrapper from '../BuildWrapper'; | import BuildWrapper from '../BuildWrapper'; | ||||
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import ClangGCC from '../ClangGCC'; | import ClangGCC from '../ClangGCC'; | ||||
shallow( | shallow( | ||||
<ClangGCC | <ClangGCC | ||||
host="host" | host="host" | ||||
os="linux" | |||||
organization="organization" | organization="organization" | ||||
os="linux" | |||||
projectKey="projectKey" | projectKey="projectKey" | ||||
token="token" | token="token" | ||||
/> | /> |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import DotNet from '../DotNet'; | import DotNet from '../DotNet'; | ||||
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import JavaGradle from '../JavaGradle'; | import JavaGradle from '../JavaGradle'; | ||||
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import JavaMaven from '../JavaMaven'; | import JavaMaven from '../JavaMaven'; | ||||
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import MSBuildScanner from '../MSBuildScanner'; | import MSBuildScanner from '../MSBuildScanner'; | ||||
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import Msvc from '../Msvc'; | import Msvc from '../Msvc'; | ||||
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import Other from '../Other'; | import Other from '../Other'; | ||||
shallow( | shallow( | ||||
<Other | <Other | ||||
host="host" | host="host" | ||||
os="linux" | |||||
organization="organization" | organization="organization" | ||||
os="linux" | |||||
projectKey="projectKey" | projectKey="projectKey" | ||||
token="token" | token="token" | ||||
/> | /> |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import SQScanner from '../SQScanner'; | import SQScanner from '../SQScanner'; | ||||
<a | <a | ||||
className="button" | className="button" | ||||
href="http://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html" | href="http://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html" | ||||
rel="noopener noreferrer" | |||||
target="_blank" | target="_blank" | ||||
> | > | ||||
download_verb | download_verb |
<a | <a | ||||
className="button" | className="button" | ||||
href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" | href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" | ||||
rel="noopener noreferrer" | |||||
target="_blank" | target="_blank" | ||||
> | > | ||||
download_verb | download_verb | ||||
<a | <a | ||||
className="button" | className="button" | ||||
href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" | href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" | ||||
rel="noopener noreferrer" | |||||
target="_blank" | target="_blank" | ||||
> | > | ||||
download_verb | download_verb | ||||
<a | <a | ||||
className="button" | className="button" | ||||
href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" | href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" | ||||
rel="noopener noreferrer" | |||||
target="_blank" | target="_blank" | ||||
> | > | ||||
download_verb | download_verb |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
.onboarding { | |||||
min-height: calc(70vh - 60px); | |||||
} | |||||
.onboarding-step { | .onboarding-step { | ||||
position: relative; | position: relative; | ||||
outline: none; | 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; | |||||
} | } |
/* | |||||
* 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> | |||||
); | |||||
} | |||||
} |
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import * as React from 'react'; | 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(); | |||||
}); |
// 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> | |||||
`; |
window.dispatchEvent(dispatchedEvent); | window.dispatchEvent(dispatchedEvent); | ||||
} | } | ||||
export function submit(element: ShallowWrapper): void { | |||||
export function submit(element: ShallowWrapper | ReactWrapper): void { | |||||
element.simulate('submit', { | element.simulate('submit', { | ||||
preventDefault() {} | preventDefault() {} | ||||
}); | }); | ||||
} | } | ||||
export function change(element: ShallowWrapper, value: string, event = {}): void { | |||||
export function change(element: ShallowWrapper | ReactWrapper, value: string, event = {}): void { | |||||
element.simulate('change', { | element.simulate('change', { | ||||
target: { value }, | target: { value }, | ||||
currentTarget: { value }, | currentTarget: { value }, |
and_worse=and worse | and_worse=and worse | ||||
are_you_sure=Are you sure? | are_you_sure=Are you sure? | ||||
as_explained_here=as explained here | |||||
assigned_to=Assigned to | assigned_to=Assigned to | ||||
bulk_change=Bulk Change | bulk_change=Bulk Change | ||||
bulleted_point=Bulleted point | bulleted_point=Bulleted point | ||||
tutorials.onboarding=Analyze a new project | tutorials.onboarding=Analyze a new project | ||||
tutorials.skip=Skip this tutorial | tutorials.skip=Skip this tutorial | ||||
tutorials.finish=Finish 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 | |||||
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ | ||||
# ONBOARDING | # 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.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.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.generate=Generate | ||||
onboarding.token.placeholder=Enter a name for your token | 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=Use existing token | ||||
onboarding.token.use_existing_token.placeholder=Enter your existing token | onboarding.token.use_existing_token.placeholder=Enter your existing token | ||||
onboarding.token.invalid_format=The token you have entered has invalid format. | onboarding.token.invalid_format=The token you have entered has invalid format. |