diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2018-06-15 15:04:21 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-06-21 20:21:29 +0200 |
commit | 4e91bd432aeef4a2e7ff680910907e62007ac801 (patch) | |
tree | c1e8ff11fefd379dcd57ae152c766e99e6ec3e98 | |
parent | 7a8b69cb8e9e6cd0d47b5dc3f5dc4ab9e2368661 (diff) | |
download | sonarqube-4e91bd432aeef4a2e7ff680910907e62007ac801.tar.gz sonarqube-4e91bd432aeef4a2e7ff680910907e62007ac801.zip |
SONARCLOUD-64 New onboarding with 3 cases for SonarCloud (#383)
* SONARCLOUD-64 Move Onboarding to ProjectOnboarding
* SONARCLOUD-64 Migrate project onboarding to TS
* SONARCLOUD-64 Update ProjectOnboarding style
* SONARCLOUD-64 Add main onboarding page
74 files changed, 1460 insertions, 1194 deletions
diff --git a/server/sonar-web/src/main/js/app/components/StartupModal.tsx b/server/sonar-web/src/main/js/app/components/StartupModal.tsx index 101279628d1..914adc7b3ea 100644 --- a/server/sonar-web/src/main/js/app/components/StartupModal.tsx +++ b/server/sonar-web/src/main/js/app/components/StartupModal.tsx @@ -20,16 +20,21 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import OnboardingModal from '../../apps/tutorials/onboarding/OnboardingModal'; +import Onboarding from '../../apps/tutorials/Onboarding'; +import CreateOrganizationForm from '../../apps/account/organizations/CreateOrganizationForm'; import LicensePromptModal from '../../apps/marketplace/components/LicensePromptModal'; -import { CurrentUser, isLoggedIn } from '../types'; +import ProjectOnboardingModal from '../../apps/tutorials/projectOnboarding/ProjectOnboardingModal'; +import TeamOnboardingModal from '../../apps/tutorials/teamOnboarding/TeamOnboardingModal'; +import { CurrentUser, isLoggedIn, Organization } from '../types'; import { differenceInDays, parseDate, toShortNotSoISOString } from '../../helpers/dates'; import { EditionKey } from '../../apps/marketplace/utils'; import { getCurrentUser, getAppState } from '../../store/rootReducer'; -import { skipOnboarding } from '../../store/users/actions'; +import { skipOnboarding as skipOnboardingAction } from '../../store/users/actions'; import { showLicense } from '../../api/marketplace'; import { hasMessage } from '../../helpers/l10n'; import { save, get } from '../../helpers/storage'; +import { isSonarCloud } from '../../helpers/system'; +import { skipOnboarding } from '../../api/users'; interface StateProps { canAdmin: boolean; @@ -38,7 +43,7 @@ interface StateProps { } interface DispatchProps { - skipOnboarding: () => void; + skipOnboardingAction: () => void; } interface OwnProps { @@ -50,7 +55,10 @@ type Props = StateProps & DispatchProps & OwnProps; enum ModalKey { license, - onboarding + onboarding, + organizationOnboarding, + projectOnboarding, + teamOnboarding } interface State { @@ -61,14 +69,18 @@ interface State { const LICENSE_PROMPT = 'sonarqube.license.prompt'; export class StartupModal extends React.PureComponent<Props, State> { + static contextTypes = { + router: PropTypes.object.isRequired + }; + static childContextTypes = { - openOnboardingTutorial: PropTypes.func + openProjectOnboarding: PropTypes.func }; state: State = { automatic: false }; getChildContext() { - return { openOnboardingTutorial: this.openOnboarding }; + return { openProjectOnboarding: this.openProjectOnboarding }; } componentDidMount() { @@ -77,8 +89,9 @@ export class StartupModal extends React.PureComponent<Props, State> { closeOnboarding = () => { this.setState(state => { - if (state.modal === ModalKey.onboarding) { - this.props.skipOnboarding(); + if (state.modal !== ModalKey.license) { + skipOnboarding(); + this.props.skipOnboardingAction(); return { automatic: false, modal: undefined }; } return undefined; @@ -94,10 +107,27 @@ export class StartupModal extends React.PureComponent<Props, State> { }); }; + closeOrganizationOnboarding = ({ key }: Pick<Organization, 'key'>) => { + this.closeOnboarding(); + this.context.router.push(`/organizations/${key}`); + }; + openOnboarding = () => { this.setState({ modal: ModalKey.onboarding }); }; + openOrganizationOnboarding = () => { + this.setState({ modal: ModalKey.organizationOnboarding }); + }; + + openProjectOnboarding = () => { + this.setState({ modal: ModalKey.projectOnboarding }); + }; + + openTeamOnboarding = () => { + this.setState({ modal: ModalKey.teamOnboarding }); + }; + tryAutoOpenLicense = () => { const { canAdmin, currentEdition, currentUser } = this.props; const hasLicenseManager = hasMessage('license.prompt.title'); @@ -124,7 +154,11 @@ export class StartupModal extends React.PureComponent<Props, State> { const { currentUser, location } = this.props; if (currentUser.showOnboardingTutorial && !location.pathname.startsWith('documentation')) { this.setState({ automatic: true }); - this.openOnboarding(); + if (isSonarCloud()) { + this.openOnboarding(); + } else { + this.openProjectOnboarding(); + } } }; @@ -135,7 +169,24 @@ export class StartupModal extends React.PureComponent<Props, State> { {this.props.children} {modal === ModalKey.license && <LicensePromptModal onClose={this.closeLicense} />} {modal === ModalKey.onboarding && ( - <OnboardingModal automatic={automatic} onFinish={this.closeOnboarding} /> + <Onboarding + onFinish={this.closeOnboarding} + onOpenOrganizationOnboarding={this.openOrganizationOnboarding} + onOpenProjectOnboarding={this.openProjectOnboarding} + onOpenTeamOnboarding={this.openTeamOnboarding} + /> + )} + {modal === ModalKey.projectOnboarding && ( + <ProjectOnboardingModal automatic={automatic} onFinish={this.closeOnboarding} /> + )} + {modal === ModalKey.organizationOnboarding && ( + <CreateOrganizationForm + onClose={this.closeOnboarding} + onCreate={this.closeOrganizationOnboarding} + /> + )} + {modal === ModalKey.teamOnboarding && ( + <TeamOnboardingModal onFinish={this.closeOnboarding} /> )} </> ); @@ -148,7 +199,7 @@ const mapStateToProps = (state: any): StateProps => ({ currentUser: getCurrentUser(state) }); -const mapDispatchToProps: DispatchProps = { skipOnboarding }; +const mapDispatchToProps: DispatchProps = { skipOnboardingAction }; export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)( StartupModal diff --git a/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx index d2d48ee7d51..7a1e2bf2d3d 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx @@ -119,12 +119,12 @@ it('should render onboarding modal', async () => { async function shouldNotHaveModals(wrapper: ShallowWrapper) { await waitAndUpdate(wrapper); expect(wrapper.find('LicensePromptModal').exists()).toBeFalsy(); - expect(wrapper.find('OnboardingModal').exists()).toBeFalsy(); + expect(wrapper.find('ProjectOnboardingModal').exists()).toBeFalsy(); } async function shouldDisplayOnboarding(wrapper: ShallowWrapper) { await waitAndUpdate(wrapper); - expect(wrapper.find('OnboardingModal').exists()).toBeTruthy(); + expect(wrapper.find('ProjectOnboardingModal').exists()).toBeTruthy(); } async function shouldDisplayLicense(wrapper: ShallowWrapper) { @@ -139,9 +139,10 @@ function getWrapper(props = {}) { currentEdition={EditionKey.enterprise} currentUser={LOGGED_IN_USER} location={{ pathname: 'foo/bar' }} - skipOnboarding={jest.fn()} + skipOnboardingAction={jest.fn()} {...props}> <div /> - </StartupModal> + </StartupModal>, + { context: { router: { push: jest.fn() } } } ); } diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx index 061a3af556b..5cb06388326 100644 --- a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx @@ -36,13 +36,13 @@ interface Props { export default class EmbedDocsPopup extends React.PureComponent<Props> { static contextTypes = { - openOnboardingTutorial: PropTypes.func + openProjectOnboarding: PropTypes.func }; onAnalyzeProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { event.preventDefault(); event.currentTarget.blur(); - this.context.openOnboardingTutorial(); + this.context.openProjectOnboarding(); }; renderTitle(text: string) { diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx index 6d3cdcece68..0f596735299 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx @@ -50,7 +50,7 @@ interface OwnProps { type Props = StateProps & OwnProps; class GlobalNav extends React.PureComponent<Props> { - static contextTypes = { openOnboardingTutorial: PropTypes.func }; + static contextTypes = { openProjectOnboarding: PropTypes.func }; render() { return ( @@ -69,7 +69,7 @@ class GlobalNav extends React.PureComponent<Props> { <Search appState={this.props.appState} currentUser={this.props.currentUser} /> {isLoggedIn(this.props.currentUser) && isSonarCloud() && ( - <GlobalNavPlus openOnboardingTutorial={this.context.openOnboardingTutorial} /> + <GlobalNavPlus openProjectOnboarding={this.context.openProjectOnboarding} /> )} <GlobalNavUserContainer {...this.props} /> </ul> diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx index d58fb2379ac..412846db466 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx @@ -25,7 +25,7 @@ import Dropdown from '../../../../components/controls/Dropdown'; import { translate } from '../../../../helpers/l10n'; interface Props { - openOnboardingTutorial: () => void; + openProjectOnboarding: () => void; } interface State { @@ -44,7 +44,7 @@ export default class GlobalNavPlus extends React.PureComponent<Props, State> { handleNewProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { event.preventDefault(); - this.props.openOnboardingTutorial(); + this.props.openProjectOnboarding(); }; openCreateOrganizationForm = () => this.setState({ createOrganization: true }); diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavPlus-test.tsx b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavPlus-test.tsx index 3e4110f5193..88aaf4f172d 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavPlus-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavPlus-test.tsx @@ -23,18 +23,18 @@ import GlobalNavPlus from '../GlobalNavPlus'; import { click } from '../../../../../helpers/testUtils'; it('render', () => { - const wrapper = shallow(<GlobalNavPlus openOnboardingTutorial={jest.fn()} />); + const wrapper = shallow(<GlobalNavPlus openProjectOnboarding={jest.fn()} />); expect(wrapper.is('Dropdown')).toBe(true); expect(wrapper.find('Dropdown')).toMatchSnapshot(); }); it('opens onboarding', () => { - const openOnboardingTutorial = jest.fn(); + const openProjectOnboarding = jest.fn(); const wrapper = shallow( - shallow(<GlobalNavPlus openOnboardingTutorial={openOnboardingTutorial} />) + shallow(<GlobalNavPlus openProjectOnboarding={openProjectOnboarding} />) .find('Dropdown') .prop('overlay') ); click(wrapper.find('.js-new-project')); - expect(openOnboardingTutorial).toBeCalled(); + expect(openProjectOnboarding).toBeCalled(); }); diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.js b/server/sonar-web/src/main/js/app/utils/startReactApp.js index b4b79feec06..e092a6c13eb 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.js +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js @@ -172,7 +172,7 @@ const startReactApp = () => { <Route path="onboarding" component={lazyLoad(() => - import('../../apps/tutorials/onboarding/OnboardingPage') + import('../../apps/tutorials/projectOnboarding/ProjectOnboardingPage') )} /> <Route path="organizations" childRoutes={organizationsRoutes} /> diff --git a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx index 6c52d002bb1..4cd7b7d2164 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx @@ -36,13 +36,13 @@ interface StateProps { export class NoFavoriteProjects extends React.PureComponent<StateProps> { static contextTypes = { - openOnboardingTutorial: PropTypes.func + openProjectOnboarding: PropTypes.func }; onAnalyzeProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { event.preventDefault(); event.currentTarget.blur(); - this.context.openOnboardingTutorial(); + this.context.openProjectOnboarding(); }; render() { diff --git a/server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx b/server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx new file mode 100644 index 00000000000..e6a6d887bd3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx @@ -0,0 +1,102 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { connect } from 'react-redux'; +import handleRequiredAuthentication from '../../app/utils/handleRequiredAuthentication'; +import Modal from '../../components/controls/Modal'; +import { ResetButtonLink, Button } from '../../components/ui/buttons'; +import { translate } from '../../helpers/l10n'; +import { CurrentUser, isLoggedIn } from '../../app/types'; +import { getCurrentUser } from '../../store/rootReducer'; +import './styles.css'; + +interface OwnProps { + onFinish: () => void; + onOpenOrganizationOnboarding: () => void; + onOpenProjectOnboarding: () => void; + onOpenTeamOnboarding: () => void; +} + +interface StateProps { + currentUser: CurrentUser; +} + +type Props = OwnProps & StateProps; + +export class Onboarding extends React.PureComponent<Props> { + componentDidMount() { + if (!isLoggedIn(this.props.currentUser)) { + handleRequiredAuthentication(); + } + } + + render() { + if (!isLoggedIn(this.props.currentUser)) { + return null; + } + + const header = translate('onboarding.header'); + return ( + <Modal + contentLabel={header} + medium={true} + onRequestClose={this.props.onFinish} + shouldCloseOnOverlayClick={false}> + <header className="modal-head"> + <h2>{header}</h2> + </header> + <div className="modal-body"> + <p className="spacer-top big-spacer-bottom"> + {translate('onboarding.header.description')} + </p> + <ul className="onboarding-choices"> + <li className="text-center"> + <p className="big-spacer-bottom">{translate('onboarding.analyze_public_code')}</p> + <Button onClick={this.props.onOpenProjectOnboarding}> + {translate('onboarding.analyze_public_code.button')} + </Button> + </li> + <li className="text-center"> + <p className="big-spacer-bottom">{translate('onboarding.analyze_private_code')}</p> + <Button onClick={this.props.onOpenOrganizationOnboarding}> + {translate('onboarding.analyze_private_code.button')} + </Button> + </li> + <li className="text-center"> + <p className="big-spacer-bottom"> + {translate('onboarding.contribute_existing_project')} + </p> + <Button onClick={this.props.onOpenTeamOnboarding}> + {translate('onboarding.contribute_existing_project.button')} + </Button> + </li> + </ul> + </div> + <footer className="modal-foot"> + <ResetButtonLink onClick={this.props.onFinish}>{translate('close')}</ResetButtonLink> + </footer> + </Modal> + ); + } +} + +const mapStateToProps = (state: any): StateProps => ({ currentUser: getCurrentUser(state) }); + +export default connect<StateProps, {}, OwnProps>(mapStateToProps)(Onboarding); diff --git a/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx new file mode 100644 index 00000000000..c9b3e77500e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx @@ -0,0 +1,61 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import { Onboarding } from '../Onboarding'; +import { click } from '../../../helpers/testUtils'; + +it('renders correctly', () => { + expect( + shallow( + <Onboarding + currentUser={{ isLoggedIn: true }} + onFinish={jest.fn()} + onOpenOrganizationOnboarding={jest.fn()} + onOpenProjectOnboarding={jest.fn()} + onOpenTeamOnboarding={jest.fn()} + /> + ) + ).toMatchSnapshot(); +}); + +it('should correctly open the different tutorials', () => { + const onFinish = jest.fn(); + const onOpenOrganizationOnboarding = jest.fn(); + const onOpenProjectOnboarding = jest.fn(); + const onOpenTeamOnboarding = jest.fn(); + const wrapper = shallow( + <Onboarding + currentUser={{ isLoggedIn: true }} + onFinish={onFinish} + onOpenOrganizationOnboarding={onOpenOrganizationOnboarding} + onOpenProjectOnboarding={onOpenProjectOnboarding} + onOpenTeamOnboarding={onOpenTeamOnboarding} + /> + ); + + click(wrapper.find('ResetButtonLink')); + expect(onFinish).toHaveBeenCalled(); + + wrapper.find('Button').forEach(button => click(button)); + expect(onOpenOrganizationOnboarding).toHaveBeenCalled(); + expect(onOpenProjectOnboarding).toHaveBeenCalled(); + expect(onOpenTeamOnboarding).toHaveBeenCalled(); +}); diff --git a/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap new file mode 100644 index 00000000000..e017f778601 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap @@ -0,0 +1,82 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<Modal + contentLabel="onboarding.header" + medium={true} + onRequestClose={[MockFunction]} + shouldCloseOnOverlayClick={false} +> + <header + className="modal-head" + > + <h2> + onboarding.header + </h2> + </header> + <div + className="modal-body" + > + <p + className="spacer-top big-spacer-bottom" + > + onboarding.header.description + </p> + <ul + className="onboarding-choices" + > + <li + className="text-center" + > + <p + className="big-spacer-bottom" + > + onboarding.analyze_public_code + </p> + <Button + onClick={[MockFunction]} + > + onboarding.analyze_public_code.button + </Button> + </li> + <li + className="text-center" + > + <p + className="big-spacer-bottom" + > + onboarding.analyze_private_code + </p> + <Button + onClick={[MockFunction]} + > + onboarding.analyze_private_code.button + </Button> + </li> + <li + className="text-center" + > + <p + className="big-spacer-bottom" + > + onboarding.contribute_existing_project + </p> + <Button + onClick={[MockFunction]} + > + onboarding.contribute_existing_project.button + </Button> + </li> + </ul> + </div> + <footer + className="modal-foot" + > + <ResetButtonLink + onClick={[MockFunction]} + > + close + </ResetButtonLink> + </footer> +</Modal> +`; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js deleted file mode 100644 index afcf049e397..00000000000 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js +++ /dev/null @@ -1,236 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -// @flow -import React from 'react'; -import PropTypes from 'prop-types'; -import Helmet from 'react-helmet'; -import TokenStep from './TokenStep'; -import OrganizationStep from './OrganizationStep'; -import AnalysisStep from './AnalysisStep'; -import ProjectWatcher from './ProjectWatcher'; -import DeferredSpinner from '../../../components/common/DeferredSpinner'; -import InstanceMessage from '../../../components/common/InstanceMessage'; -import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; -import { skipOnboarding } from '../../../api/users'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { getProjectUrl } from '../../../helpers/urls'; -import { isSonarCloud } from '../../../helpers/system'; -import './styles.css'; - -/*:: -type Props = {| - automatic?:boolean, - className?: string, - currentUser: { login: string, isLoggedIn: boolean }, - onFinish: () => void, - organizationsEnabled: boolean -|}; -*/ - -/*:: -type State = { - finished: boolean, - organization?: string, - projectKey?: string, - skipping: boolean, - step: string, - token?: string -}; -*/ - -export default class Onboarding extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: props: Props; */ - /*:: state: State; */ - - static contextTypes = { - router: PropTypes.object - }; - - constructor(props /*: Props */) { - super(props); - this.state = { - finished: false, - skipping: false, - step: props.organizationsEnabled ? 'organization' : 'token' - }; - } - - componentDidMount() { - this.mounted = true; - - // useCapture = true to receive the event before inputs - window.addEventListener('keydown', this.onKeyDown, true); - - if (!this.props.currentUser.isLoggedIn) { - handleRequiredAuthentication(); - } - } - - componentWillUnmount() { - window.removeEventListener('keydown', this.onKeyDown, true); - this.mounted = false; - } - - onKeyDown = (event /*: KeyboardEvent */) => { - // ESC key - if (event.keyCode === 27) { - this.finishOnboarding(); - } - }; - - finishOnboarding = () => { - this.setState({ skipping: true }); - skipOnboarding().then( - () => { - if (this.mounted) { - this.props.onFinish(); - - if (this.state.projectKey) { - this.context.router.push(getProjectUrl(this.state.projectKey)); - } - } - }, - () => { - if (this.mounted) { - this.setState({ skipping: false }); - } - } - ); - }; - - handleTimeout = () => { - // unset `projectKey` to display a generic "Finish this tutorial" button - this.setState({ projectKey: undefined }); - }; - - handleTokenDone = (token /*: string */) => { - this.setState({ step: 'analysis', token }); - }; - - handleOrganizationDone = (organization /*: string */) => { - this.setState({ organization, step: 'token' }); - }; - - handleTokenOpen = () => this.setState({ step: 'token' }); - - handleOrganizationOpen = () => this.setState({ step: 'organization' }); - - handleSkipClick = (event /*: Event */) => { - event.preventDefault(); - this.finishOnboarding(); - }; - - handleFinish = (projectKey /*: string | void */) => this.setState({ finished: true, projectKey }); - - handleReset = () => this.setState({ finished: false, projectKey: undefined }); - - render() { - if (!this.props.currentUser.isLoggedIn) { - return null; - } - - const { automatic, organizationsEnabled } = this.props; - const { step, token } = this.state; - let stepNumber = 1; - - return ( - <div className={this.props.className}> - <InstanceMessage message={translate('onboarding.header')}> - {transformedMessage => <Helmet title={transformedMessage} titleTemplate="%s" />} - </InstanceMessage> - - <div className="page page-limited onboarding"> - <header className="page-header"> - <h1 className="page-title"> - <InstanceMessage message={translate('onboarding.header')} /> - </h1> - <div className="page-actions"> - <DeferredSpinner loading={this.state.skipping}> - <a className="js-skip text-muted" href="#" onClick={this.handleSkipClick}> - {automatic ? translate('tutorials.skip') : translate('close')} - </a> - </DeferredSpinner> - - <p className="note"> - {translate( - isSonarCloud() - ? 'tutorials.find_it_back_in_plus' - : 'tutorials.find_it_back_in_help' - )} - </p> - </div> - <div className="page-description"> - {translateWithParameters( - 'onboarding.header.description', - organizationsEnabled ? 3 : 2 - )} - </div> - </header> - - {organizationsEnabled && ( - <OrganizationStep - currentUser={this.props.currentUser} - finished={this.state.organization != null} - onContinue={this.handleOrganizationDone} - onOpen={this.handleOrganizationOpen} - open={step === 'organization'} - stepNumber={stepNumber++} - /> - )} - - <TokenStep - currentUser={this.props.currentUser} - finished={this.state.token != null} - onContinue={this.handleTokenDone} - onOpen={this.handleTokenOpen} - open={step === 'token'} - stepNumber={stepNumber++} - /> - - <AnalysisStep - onFinish={this.handleFinish} - onReset={this.handleReset} - open={step === 'analysis'} - organization={this.state.organization} - stepNumber={stepNumber} - token={token} - /> - - {this.state.finished && - !this.state.skipping && - (this.state.projectKey ? ( - <ProjectWatcher - onFinish={this.finishOnboarding} - onTimeout={this.handleTimeout} - projectKey={this.state.projectKey} - /> - ) : ( - <footer className="text-right"> - <a className="button" href="#" onClick={this.handleSkipClick}> - {translate('tutorials.finish')} - </a> - </footer> - ))} - </div> - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingContainer.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingContainer.js deleted file mode 100644 index cf27d5dd333..00000000000 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingContainer.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -// @flow -import { connect } from 'react-redux'; -import Onboarding from './Onboarding'; -import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer'; - -const mapStateToProps = state => { - return { - className: 'modal-container', - currentUser: getCurrentUser(state), - organizationsEnabled: areThereCustomOrganizations(state) - }; -}; - -export default connect(mapStateToProps)(Onboarding); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap deleted file mode 100644 index 1635dc70bd5..00000000000 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap +++ /dev/null @@ -1,388 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`guides for on-premise 1`] = ` -<div - className="modal-container" -> - <InstanceMessage - message="onboarding.header" - /> - <div - className="page page-limited onboarding" - > - <header - className="page-header" - > - <h1 - className="page-title" - > - <InstanceMessage - message="onboarding.header" - /> - </h1> - <div - className="page-actions" - > - <DeferredSpinner - loading={false} - timeout={100} - > - <a - className="js-skip text-muted" - href="#" - onClick={[Function]} - > - close - </a> - </DeferredSpinner> - <p - className="note" - > - tutorials.find_it_back_in_help - </p> - </div> - <div - className="page-description" - > - onboarding.header.description.2 - </div> - </header> - <TokenStep - currentUser={ - Object { - "isLoggedIn": true, - "login": "admin", - } - } - finished={false} - onContinue={[Function]} - onOpen={[Function]} - open={true} - stepNumber={1} - /> - <AnalysisStep - onFinish={[Function]} - onReset={[Function]} - open={false} - stepNumber={2} - /> - </div> -</div> -`; - -exports[`guides for on-premise 2`] = ` -<div - className="modal-container" -> - <InstanceMessage - message="onboarding.header" - /> - <div - className="page page-limited onboarding" - > - <header - className="page-header" - > - <h1 - className="page-title" - > - <InstanceMessage - message="onboarding.header" - /> - </h1> - <div - className="page-actions" - > - <DeferredSpinner - loading={false} - timeout={100} - > - <a - className="js-skip text-muted" - href="#" - onClick={[Function]} - > - close - </a> - </DeferredSpinner> - <p - className="note" - > - tutorials.find_it_back_in_help - </p> - </div> - <div - className="page-description" - > - onboarding.header.description.2 - </div> - </header> - <TokenStep - currentUser={ - Object { - "isLoggedIn": true, - "login": "admin", - } - } - finished={true} - onContinue={[Function]} - onOpen={[Function]} - open={false} - stepNumber={1} - /> - <AnalysisStep - onFinish={[Function]} - onReset={[Function]} - open={true} - stepNumber={2} - token="abcd1234" - /> - </div> -</div> -`; - -exports[`guides for sonarcloud 1`] = ` -<div> - <InstanceMessage - message="onboarding.header" - /> - <div - className="page page-limited onboarding" - > - <header - className="page-header" - > - <h1 - className="page-title" - > - <InstanceMessage - message="onboarding.header" - /> - </h1> - <div - className="page-actions" - > - <DeferredSpinner - loading={false} - timeout={100} - > - <a - className="js-skip text-muted" - href="#" - onClick={[Function]} - > - close - </a> - </DeferredSpinner> - <p - className="note" - > - tutorials.find_it_back_in_plus - </p> - </div> - <div - className="page-description" - > - onboarding.header.description.3 - </div> - </header> - <OrganizationStep - currentUser={ - Object { - "isLoggedIn": true, - "login": "admin", - } - } - finished={false} - onContinue={[Function]} - onOpen={[Function]} - open={true} - stepNumber={1} - /> - <TokenStep - currentUser={ - Object { - "isLoggedIn": true, - "login": "admin", - } - } - finished={false} - onContinue={[Function]} - onOpen={[Function]} - open={false} - stepNumber={2} - /> - <AnalysisStep - onFinish={[Function]} - onReset={[Function]} - open={false} - stepNumber={3} - /> - </div> -</div> -`; - -exports[`guides for sonarcloud 2`] = ` -<div> - <InstanceMessage - message="onboarding.header" - /> - <div - className="page page-limited onboarding" - > - <header - className="page-header" - > - <h1 - className="page-title" - > - <InstanceMessage - message="onboarding.header" - /> - </h1> - <div - className="page-actions" - > - <DeferredSpinner - loading={false} - timeout={100} - > - <a - className="js-skip text-muted" - href="#" - onClick={[Function]} - > - close - </a> - </DeferredSpinner> - <p - className="note" - > - tutorials.find_it_back_in_plus - </p> - </div> - <div - className="page-description" - > - onboarding.header.description.3 - </div> - </header> - <OrganizationStep - currentUser={ - Object { - "isLoggedIn": true, - "login": "admin", - } - } - finished={true} - onContinue={[Function]} - onOpen={[Function]} - open={false} - stepNumber={1} - /> - <TokenStep - currentUser={ - Object { - "isLoggedIn": true, - "login": "admin", - } - } - finished={false} - onContinue={[Function]} - onOpen={[Function]} - open={true} - stepNumber={2} - /> - <AnalysisStep - onFinish={[Function]} - onReset={[Function]} - open={false} - organization="my-org" - stepNumber={3} - /> - </div> -</div> -`; - -exports[`guides for sonarcloud 3`] = ` -<div> - <InstanceMessage - message="onboarding.header" - /> - <div - className="page page-limited onboarding" - > - <header - className="page-header" - > - <h1 - className="page-title" - > - <InstanceMessage - message="onboarding.header" - /> - </h1> - <div - className="page-actions" - > - <DeferredSpinner - loading={false} - timeout={100} - > - <a - className="js-skip text-muted" - href="#" - onClick={[Function]} - > - close - </a> - </DeferredSpinner> - <p - className="note" - > - tutorials.find_it_back_in_plus - </p> - </div> - <div - className="page-description" - > - onboarding.header.description.3 - </div> - </header> - <OrganizationStep - currentUser={ - Object { - "isLoggedIn": true, - "login": "admin", - } - } - finished={true} - onContinue={[Function]} - onOpen={[Function]} - open={false} - stepNumber={1} - /> - <TokenStep - currentUser={ - Object { - "isLoggedIn": true, - "login": "admin", - } - } - finished={true} - onContinue={[Function]} - onOpen={[Function]} - open={false} - stepNumber={2} - /> - <AnalysisStep - onFinish={[Function]} - onReset={[Function]} - open={true} - organization="my-org" - stepNumber={3} - token="abcd1234" - /> - </div> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/AnalysisStep.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/AnalysisStep.tsx index afaa359bfb7..a91ff541119 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/AnalysisStep.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/AnalysisStep.tsx @@ -17,11 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import Step from './Step'; -import LanguageStep from './LanguageStep'; -/*:: import type { Result } from './LanguageStep'; */ +import LanguageStep, { Result } from './LanguageStep'; import JavaMaven from './commands/JavaMaven'; import JavaGradle from './commands/JavaGradle'; import DotNet from './commands/DotNet'; @@ -31,28 +29,23 @@ import Other from './commands/Other'; import { translate } from '../../../helpers/l10n'; import { getHostUrl } from '../../../helpers/urls'; -/*:: -type Props = {| - onFinish: (projectKey?: string) => void, - onReset: () => void, - open: boolean, - organization?: string, - stepNumber: number, - token: string -|}; -*/ - -/*:: -type State = { - result?: Result -}; -*/ - -export default class AnalysisStep extends React.PureComponent { - /*:: props: Props; */ - state /*: State */ = {}; - - handleLanguageSelect = (result /*: Result | void */) => { +interface Props { + onFinish: (projectKey?: string) => void; + onReset: () => void; + open: boolean; + organization?: string; + stepNumber: number; + token?: string; +} + +interface State { + result?: Result; +} + +export default class AnalysisStep extends React.PureComponent<Props, State> { + state: State = {}; + + handleLanguageSelect = (result?: Result) => { this.setState({ result }); const projectKey = result && result.language !== 'java' ? result.projectKey : undefined; this.props.onFinish(projectKey); @@ -80,7 +73,7 @@ export default class AnalysisStep extends React.PureComponent { ); }; - renderFormattedCommand = (...lines /*: Array<string> */) => ( + renderFormattedCommand = (...lines: Array<string>) => ( // keep this "useless" concatentation for the readability reason // eslint-disable-next-line no-useless-concat <pre>{lines.join(' ' + '\\' + '\n' + ' ')}</pre> @@ -108,69 +101,87 @@ export default class AnalysisStep extends React.PureComponent { } }; - renderCommandForMaven = () => ( - <JavaMaven - host={getHostUrl()} - organization={this.props.organization} - token={this.props.token} - /> - ); + renderCommandForMaven = () => { + const { token } = this.props; + if (!token) { + return null; + } + return <JavaMaven host={getHostUrl()} organization={this.props.organization} token={token} />; + }; - renderCommandForGradle = () => ( - <JavaGradle - host={getHostUrl()} - organization={this.props.organization} - token={this.props.token} - /> - ); + renderCommandForGradle = () => { + const { token } = this.props; + if (!token) { + return null; + } + return <JavaGradle host={getHostUrl()} organization={this.props.organization} token={token} />; + }; renderCommandForDotNet = () => { + const { token } = this.props; + const { result } = this.state; + if (!result || !result.projectKey || !token) { + return null; + } return ( <DotNet host={getHostUrl()} organization={this.props.organization} - // $FlowFixMe - projectKey={this.state.result.projectKey} - token={this.props.token} + projectKey={result.projectKey} + token={token} /> ); }; renderCommandForMSVC = () => { + const { token } = this.props; + const { result } = this.state; + if (!result || !result.projectKey || !token) { + return null; + } return ( <Msvc host={getHostUrl()} organization={this.props.organization} - // $FlowFixMe - projectKey={this.state.result.projectKey} - token={this.props.token} + projectKey={result.projectKey} + token={token} /> ); }; - renderCommandForClangGCC = () => ( - <ClangGCC - host={getHostUrl()} - organization={this.props.organization} - // $FlowFixMe - os={this.state.result.os} - // $FlowFixMe - projectKey={this.state.result.projectKey} - token={this.props.token} - /> - ); + renderCommandForClangGCC = () => { + const { token } = this.props; + const { result } = this.state; + if (!result || !result.projectKey || !result.os || !token) { + return null; + } + return ( + <ClangGCC + host={getHostUrl()} + organization={this.props.organization} + os={result.os} + projectKey={result.projectKey} + token={token} + /> + ); + }; - renderCommandForOther = () => ( - <Other - host={getHostUrl()} - organization={this.props.organization} - // $FlowFixMe - os={this.state.result.os} - // $FlowFixMe - projectKey={this.state.result.projectKey} - token={this.props.token} - /> - ); + renderCommandForOther = () => { + const { token } = this.props; + const { result } = this.state; + if (!result || !result.projectKey || !result.os || !token) { + return null; + } + return ( + <Other + host={getHostUrl()} + organization={this.props.organization} + os={result.os} + projectKey={result.projectKey} + token={token} + /> + ); + }; renderResult = () => null; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/LanguageStep.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/LanguageStep.tsx index c9bd1c15e5a..25b7544ddcc 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/LanguageStep.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/LanguageStep.tsx @@ -17,38 +17,31 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; + +import * as React from 'react'; import NewProjectForm from './NewProjectForm'; import RadioToggle from '../../../components/controls/RadioToggle'; import { translate } from '../../../helpers/l10n'; import { isSonarCloud } from '../../../helpers/system'; -/*:: -type Props = {| - onDone: (result: Result) => void, - onReset: () => void, - organization?: string, -|}; -*/ - -/*:: -type State = { - language?: string, - javaBuild?: string, - cFamilyCompiler?: string, - os?: string, - projectKey?: string -}; -*/ +export interface Result { + language?: string; + javaBuild?: string; + cFamilyCompiler?: string; + os?: string; + projectKey?: string; +} -/*:: -export type Result = State; */ +interface Props { + onDone: (result: Result) => void; + onReset: () => void; + organization?: string; +} -export default class LanguageStep extends React.PureComponent { - /*:: props: Props; */ +type State = Result; - state /*: State */ = {}; +export default class LanguageStep extends React.PureComponent<Props, State> { + state: State = {}; isConfigured = () => { const { language, javaBuild, cFamilyCompiler, os, projectKey } = this.state; @@ -69,23 +62,23 @@ export default class LanguageStep extends React.PureComponent { } }; - handleLanguageChange = (language /*: string */) => { + handleLanguageChange = (language: string) => { this.setState({ language }, this.handleChange); }; - handleJavaBuildChange = (javaBuild /*: string */) => { + handleJavaBuildChange = (javaBuild: string) => { this.setState({ javaBuild }, this.handleChange); }; - handleCFamilyCompilerChange = (cFamilyCompiler /*: string */) => { + handleCFamilyCompilerChange = (cFamilyCompiler: string) => { this.setState({ cFamilyCompiler }, this.handleChange); }; - handleOSChange = (os /*: string */) => { + handleOSChange = (os: string) => { this.setState({ os }, this.handleChange); }; - handleProjectKeyDone = (projectKey /*: string */) => { + handleProjectKeyDone = (projectKey: string) => { this.setState({ projectKey }, this.handleChange); }; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewOrganizationForm.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewOrganizationForm.tsx index 11be750a41b..1a0e5500129 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewOrganizationForm.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewOrganizationForm.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { debounce } from 'lodash'; import { createOrganization, @@ -29,29 +28,23 @@ import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon' import { DeleteButton, SubmitButton } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; -/*:: -type Props = {| - onDelete: () => void, - onDone: (organization: string) => void, - organization?: string -|}; -*/ +interface Props { + onDelete: () => void; + onDone: (organization: string) => void; + organization?: string; +} -/*:: -type State = { - done: boolean, - loading: boolean, - organization: string, - unique: boolean -}; -*/ +interface State { + done: boolean; + loading: boolean; + organization: string; + unique: boolean; +} -export default class NewOrganizationForm extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: props: Props; */ - /*:: state: State; */ +export default class NewOrganizationForm extends React.PureComponent<Props, State> { + mounted = false; - constructor(props /*: Props */) { + constructor(props: Props) { super(props); this.state = { done: props.organization != null, @@ -76,7 +69,7 @@ export default class NewOrganizationForm extends React.PureComponent { } }; - validateOrganization = (organization /*: string */) => { + validateOrganization = (organization: string) => { getOrganization(organization).then(response => { if (this.mounted) { this.setState({ unique: response == null }); @@ -84,19 +77,19 @@ export default class NewOrganizationForm extends React.PureComponent { }); }; - sanitizeOrganization = (organization /*: string */) => + sanitizeOrganization = (organization: string) => organization .toLowerCase() .replace(/[^a-z0-9-]/, '') .replace(/^-/, ''); - handleOrganizationChange = (event /*: { target: HTMLInputElement } */) => { + handleOrganizationChange = (event: React.ChangeEvent<HTMLInputElement>) => { const organization = this.sanitizeOrganization(event.target.value); this.setState({ organization }); this.validateOrganization(organization); }; - handleOrganizationCreate = (event /*: Event */) => { + handleOrganizationCreate = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); const { organization } = this.state; if (organization) { diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewProjectForm.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewProjectForm.tsx index cde4629a0a8..f09b4d152bf 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewProjectForm.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewProjectForm.tsx @@ -17,35 +17,28 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { createProject, deleteProject } from '../../../api/components'; import { DeleteButton, SubmitButton } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; -/*:: -type Props = {| - onDelete: () => void, - onDone: (projectKey: string) => void, - organization?: string, - projectKey?: string -|}; -*/ +interface Props { + onDelete: () => void; + onDone: (projectKey: string) => void; + organization?: string; + projectKey?: string; +} -/*:: -type State = { - done: boolean, - loading: boolean, - projectKey: string -}; -*/ +interface State { + done: boolean; + loading: boolean; + projectKey: string; +} -export default class NewProjectForm extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: props: Props; */ - /*:: state: State; */ +export default class NewProjectForm extends React.PureComponent<Props, State> { + mounted = false; - constructor(props /*: Props */) { + constructor(props: Props) { super(props); this.state = { done: props.projectKey != null, @@ -68,16 +61,20 @@ export default class NewProjectForm extends React.PureComponent { } }; - sanitizeProjectKey = (projectKey /*: string */) => projectKey.replace(/[^-_a-zA-Z0-9.:]/, ''); + sanitizeProjectKey = (projectKey: string) => projectKey.replace(/[^-_a-zA-Z0-9.:]/, ''); - handleProjectKeyChange = (event /*: { target: HTMLInputElement } */) => { + handleProjectKeyChange = (event: React.ChangeEvent<HTMLInputElement>) => { this.setState({ projectKey: this.sanitizeProjectKey(event.target.value) }); }; - handleProjectCreate = (event /*: Event */) => { + handleProjectCreate = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); const { projectKey } = this.state; - const data /*: { [string]: string } */ = { + const data: { + name: string; + project: string; + organization?: string; + } = { name: projectKey, project: projectKey }; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/OrganizationStep.tsx index e55c34a341a..0d155ef9eee 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/OrganizationStep.tsx @@ -17,9 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; -import classNames from 'classnames'; +import * as React from 'react'; +import * as classNames from 'classnames'; import { sortBy } from 'lodash'; import Step from './Step'; import NewOrganizationForm from './NewOrganizationForm'; @@ -30,32 +29,27 @@ import Select from '../../../components/controls/Select'; import { translate } from '../../../helpers/l10n'; import { Button } from '../../../components/ui/buttons'; -/*:: -type Props = {| - currentUser: { login: string, isLoggedIn: boolean }, - finished: boolean, - onOpen: () => void, - onContinue: (organization: string) => void, - open: boolean, - stepNumber: number -|}; -*/ +interface Props { + currentUser: { login: string; isLoggedIn: boolean }; + finished: boolean; + onOpen: () => void; + onContinue: (organization: string) => void; + open: boolean; + stepNumber: number; +} -/*:: -type State = { - loading: boolean, - newOrganization?: string, - existingOrganization?: string, - existingOrganizations: Array<string>, - personalOrganization?: string, - selection: 'personal' | 'existing' | 'new' -}; -*/ +interface State { + loading: boolean; + newOrganization?: string; + existingOrganization?: string; + existingOrganizations: Array<string>; + personalOrganization?: string; + selection: 'personal' | 'existing' | 'new'; +} -export default class OrganizationStep extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: props: Props; */ - state /*: State */ = { +export default class OrganizationStep extends React.PureComponent<Props, State> { + mounted = false; + state: State = { loading: true, existingOrganizations: [], selection: 'personal' @@ -112,22 +106,22 @@ export default class OrganizationStep extends React.PureComponent { } }; - handlePersonalClick = (event /*: Event */) => { + handlePersonalClick = (event: React.MouseEvent<HTMLAnchorElement>) => { event.preventDefault(); this.setState({ selection: 'personal' }); }; - handleExistingClick = (event /*: Event */) => { + handleExistingClick = (event: React.MouseEvent<HTMLAnchorElement>) => { event.preventDefault(); this.setState({ selection: 'existing' }); }; - handleNewClick = (event /*: Event */) => { + handleNewClick = (event: React.MouseEvent<HTMLAnchorElement>) => { event.preventDefault(); this.setState({ selection: 'new' }); }; - handleOrganizationCreate = (newOrganization /*: string */) => { + handleOrganizationCreate = (newOrganization: string) => { this.setState({ newOrganization }); }; @@ -135,7 +129,7 @@ export default class OrganizationStep extends React.PureComponent { this.setState({ newOrganization: undefined }); }; - handleExistingOrganizationSelect = ({ value } /*: { value: string } */) => { + handleExistingOrganizationSelect = ({ value }: { value: string }) => { this.setState({ existingOrganization: value }); }; diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx new file mode 100644 index 00000000000..384e1726e39 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx @@ -0,0 +1,206 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as PropTypes from 'prop-types'; +import Helmet from 'react-helmet'; +import { connect } from 'react-redux'; +import TokenStep from './TokenStep'; +import OrganizationStep from './OrganizationStep'; +import AnalysisStep from './AnalysisStep'; +import ProjectWatcher from './ProjectWatcher'; +import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; +import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer'; +import { CurrentUser, isLoggedIn } from '../../../app/types'; +import { ResetButtonLink } from '../../../components/ui/buttons'; +import { getProjectUrl } from '../../../helpers/urls'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { isSonarCloud } from '../../../helpers/system'; +import '../styles.css'; + +interface OwnProps { + automatic?: boolean; + onFinish: () => void; +} + +interface StateProps { + currentUser: CurrentUser; + organizationsEnabled: boolean; +} + +type Props = OwnProps & StateProps; + +interface State { + finished: boolean; + organization?: string; + projectKey?: string; + step: string; + token?: string; +} + +export class ProjectOnboarding extends React.PureComponent<Props, State> { + mounted = false; + static contextTypes = { + router: PropTypes.object + }; + + constructor(props: Props) { + super(props); + this.state = { + finished: false, + step: props.organizationsEnabled ? 'organization' : 'token' + }; + } + + componentDidMount() { + this.mounted = true; + + // useCapture = true to receive the event before inputs + window.addEventListener('keydown', this.onKeyDown, true); + + if (!isLoggedIn(this.props.currentUser)) { + handleRequiredAuthentication(); + } + } + + componentWillUnmount() { + window.removeEventListener('keydown', this.onKeyDown, true); + this.mounted = false; + } + + onKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + this.finishOnboarding(); + } + }; + + finishOnboarding = () => { + this.props.onFinish(); + if (this.state.projectKey) { + this.context.router.push(getProjectUrl(this.state.projectKey)); + } + }; + + handleTimeout = () => { + // unset `projectKey` to display a generic "Finish this tutorial" button + this.setState({ projectKey: undefined }); + }; + + handleTokenDone = (token: string) => { + this.setState({ step: 'analysis', token }); + }; + + handleOrganizationDone = (organization: string) => { + this.setState({ organization, step: 'token' }); + }; + + handleTokenOpen = () => this.setState({ step: 'token' }); + + handleOrganizationOpen = () => this.setState({ step: 'organization' }); + + handleFinish = (projectKey?: string) => this.setState({ finished: true, projectKey }); + + handleReset = () => this.setState({ finished: false, projectKey: undefined }); + + render() { + const { automatic, currentUser, organizationsEnabled } = this.props; + if (!isLoggedIn(currentUser)) { + return null; + } + + const { finished, projectKey, step, token } = this.state; + const header = translate('onboarding.project.header'); + let stepNumber = 1; + + return ( + <> + <Helmet title={header} titleTemplate="%s" /> + <header className="modal-head"> + <h2>{header}</h2> + </header> + <div className="modal-body modal-container"> + <p className="spacer-top big-spacer-bottom"> + {translateWithParameters( + 'onboarding.project.header.description', + organizationsEnabled ? 3 : 2 + )} + </p> + {organizationsEnabled && ( + <OrganizationStep + currentUser={currentUser} + finished={this.state.organization != null} + onContinue={this.handleOrganizationDone} + onOpen={this.handleOrganizationOpen} + open={step === 'organization'} + stepNumber={stepNumber++} + /> + )} + + <TokenStep + currentUser={currentUser} + finished={this.state.token != null} + onContinue={this.handleTokenDone} + onOpen={this.handleTokenOpen} + open={step === 'token'} + stepNumber={stepNumber++} + /> + + <AnalysisStep + onFinish={this.handleFinish} + onReset={this.handleReset} + open={step === 'analysis'} + organization={this.state.organization} + stepNumber={stepNumber} + token={token} + /> + </div> + <footer className="modal-foot"> + <ResetButtonLink className="js-skip" onClick={this.finishOnboarding}> + {(finished && translate('tutorials.finish')) || + (automatic ? translate('tutorials.skip') : translate('close'))} + </ResetButtonLink> + {finished && projectKey ? ( + <ProjectWatcher + onFinish={this.finishOnboarding} + onTimeout={this.handleTimeout} + projectKey={projectKey} + /> + ) : ( + <span className="pull-left note"> + {translate( + isSonarCloud() + ? 'tutorials.find_tutorial_back_in_plus' + : 'tutorials.find_tutorial_back_in_help' + )} + </span> + )} + </footer> + </> + ); + } +} + +const mapStateToProps = (state: any): StateProps => { + return { + currentUser: getCurrentUser(state), + organizationsEnabled: areThereCustomOrganizations(state) + }; +}; + +export default connect<StateProps, {}, OwnProps>(mapStateToProps)(ProjectOnboarding); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingModal.tsx index 188e5cc7927..d91e78872f4 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingModal.tsx @@ -27,12 +27,12 @@ interface Props { onFinish: () => void; } -const OnboardingContainer = lazyLoad(() => import('./OnboardingContainer')); +const ProjectOnboarding = lazyLoad(() => import('./ProjectOnboarding')); -export default function OnboardingModal(props: Props) { +export default function ProjectOnboardingModal(props: Props) { return ( <Modal contentLabel={translate('tutorials.onboarding')} large={true}> - <OnboardingContainer {...props} /> + <ProjectOnboarding {...props} /> </Modal> ); } diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingPage.tsx index 971ae8bddff..5d0978e41d9 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingPage.tsx @@ -20,14 +20,14 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import OnboardingModal from './OnboardingModal'; +import ProjectOnboardingModal from './ProjectOnboardingModal'; import { skipOnboarding } from '../../../store/users/actions'; interface DispatchProps { skipOnboarding: () => void; } -export class OnboardingPage extends React.PureComponent<DispatchProps> { +export class ProjectOnboardingPage extends React.PureComponent<DispatchProps> { static contextTypes = { router: PropTypes.object.isRequired }; @@ -38,10 +38,10 @@ export class OnboardingPage extends React.PureComponent<DispatchProps> { }; render() { - return <OnboardingModal onFinish={this.onSkipOnboardingTutorial} />; + return <ProjectOnboardingModal onFinish={this.onSkipOnboardingTutorial} />; } } const mapDispatchToProps: DispatchProps = { skipOnboarding }; -export default connect<{}, DispatchProps, {}>(null, mapDispatchToProps)(OnboardingPage); +export default connect<{}, DispatchProps>(null, mapDispatchToProps)(ProjectOnboardingPage); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/ProjectWatcher.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectWatcher.tsx index e836e988fbe..31683d2f4cd 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/ProjectWatcher.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectWatcher.tsx @@ -17,8 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; + +import * as React from 'react'; +import * as classNames from 'classnames'; import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon'; import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon'; import { getTasksForComponent } from '../../../api/ce'; @@ -28,35 +29,27 @@ import { translate } from '../../../helpers/l10n'; const INTERVAL = 5000; const TIMEOUT = 10 * 60 * 1000; // 10 min -/*:: -type Props = { - onFinish: () => void, - onTimeout: () => void, - projectKey: string -}; -*/ +interface Props { + onFinish: () => void; + onTimeout: () => void; + projectKey: string; +} -/*:: -type State = { - inQueue: boolean, - status: ?string -}; -*/ +interface State { + inQueue: boolean; + status?: string; +} -export default class ProjectWatcher extends React.PureComponent { - /*:: interval: number; */ - /*:: mounted: boolean; */ - /*:: props: Props; */ - /*:: timeout: number; */ - state /*: State */ = { - inQueue: false, - status: null - }; +export default class ProjectWatcher extends React.PureComponent<Props, State> { + interval?: number; + timeout?: number; + mounted = false; + state: State = { inQueue: false }; componentDidMount() { this.mounted = true; this.watch(); - this.timeout = setTimeout(this.props.onTimeout, TIMEOUT); + this.timeout = window.setTimeout(this.props.onTimeout, TIMEOUT); } componentWillUnmount() { @@ -65,7 +58,7 @@ export default class ProjectWatcher extends React.PureComponent { this.mounted = false; } - watch = () => (this.interval = setTimeout(this.checkProject, INTERVAL)); + watch = () => (this.interval = window.setTimeout(this.checkProject, INTERVAL)); checkProject = () => { const { projectKey } = this.props; @@ -93,10 +86,11 @@ export default class ProjectWatcher extends React.PureComponent { render() { const { inQueue, status } = this.state; + const className = 'pull-left note'; if (status === STATUSES.SUCCESS) { return ( - <div className="big-spacer-top note text-center"> + <div className={classNames(className, 'display-inline-flex-center')}> <AlertSuccessIcon className="spacer-right" /> {translate('onboarding.project_watcher.finished')} </div> @@ -105,7 +99,7 @@ export default class ProjectWatcher extends React.PureComponent { if (inQueue || status === STATUSES.PENDING || status === STATUSES.IN_PROGRESS) { return ( - <div className="big-spacer-top note text-center"> + <div className={className}> <i className="spinner spacer-right" /> {translate('onboarding.project_watcher.in_progress')} </div> @@ -114,17 +108,13 @@ export default class ProjectWatcher extends React.PureComponent { if (status != null) { return ( - <div className="big-spacer-top note text-center"> + <div className={classNames(className, 'display-inline-flex-center')}> <AlertErrorIcon className="spacer-right" /> {translate('onboarding.project_watcher.failed')} </div> ); } - return ( - <div className="big-spacer-top note text-center"> - {translate('onboarding.project_watcher.not_started')} - </div> - ); + return <div className={className}>{translate('onboarding.project_watcher.not_started')}</div>; } } diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/Step.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/Step.tsx index d9f6394a75f..7fda3877528 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/Step.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/Step.tsx @@ -17,23 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; -import classNames from 'classnames'; +/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/no-noninteractive-tabindex */ +import * as React from 'react'; +import * as classNames from 'classnames'; -/*:: -type Props = {| - finished: boolean, - onOpen: () => void, - open: boolean, - renderForm: () => React.Element<*>, - renderResult: () => ?React.Element<*>, - stepNumber: number, - stepTitle: React.Element<*> | string -|}; -*/ +interface Props { + finished: boolean; + onOpen: () => void; + open: boolean; + renderForm: () => React.ReactNode; + renderResult: () => React.ReactNode; + stepNumber: number; + stepTitle: React.ReactNode; +} -export default function Step(props /*: Props */) { +export default function Step(props: Props) { const className = classNames('boxed-group', 'onboarding-step', { 'is-open': props.open, 'is-finished': props.finished @@ -41,7 +39,7 @@ export default function Step(props /*: Props */) { const clickable = !props.open && props.finished; - const handleClick = (event /*: Event */) => { + const handleClick = (event: React.MouseEvent<HTMLDivElement>) => { event.preventDefault(); props.onOpen(); }; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/TokenStep.tsx index 6ef86cd7b3a..e0cd2c5aacc 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/TokenStep.tsx @@ -17,9 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; -import classNames from 'classnames'; + +import * as React from 'react'; +import * as classNames from 'classnames'; import Step from './Step'; import { getTokens, generateToken, revokeToken } from '../../../api/user-tokens'; import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon'; @@ -27,32 +27,28 @@ import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessI import { DeleteButton, SubmitButton, Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; -/*:: -type Props = {| - currentUser: { login: string }, - finished: boolean, - open: boolean, - onContinue: (token: string) => void, - onOpen: () => void, - stepNumber: number -|}; -*/ +interface Props { + currentUser: { login: string }; + finished: boolean; + open: boolean; + onContinue: (token: string) => void; + onOpen: () => void; + stepNumber: number; +} -/*:: -type State = { - canUseExisting: boolean, - existingToken: string, - loading: boolean, - selection: string, - tokenName?: string, - token?: string -}; -*/ +interface State { + canUseExisting: boolean; + existingToken: string; + loading: boolean; + selection: string; + tokenName?: string; + token?: string; +} -export default class TokenStep extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: props: Props; */ - state /*: State */ = { +export default class TokenStep extends React.PureComponent<Props, State> { + mounted = false; + + state: State = { canUseExisting: false, existingToken: '', loading: false, @@ -87,27 +83,20 @@ export default class TokenStep extends React.PureComponent { ); }; - handleTokenNameChange = (event /*: { target: HTMLInputElement } */) => { + handleTokenNameChange = (event: React.ChangeEvent<HTMLInputElement>) => { this.setState({ tokenName: event.target.value }); }; - handleTokenGenerate = (event /*: Event */) => { + handleTokenGenerate = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); const { tokenName } = this.state; if (tokenName) { this.setState({ loading: true }); - generateToken({ name: tokenName }).then( - ({ token }) => { - if (this.mounted) { - this.setState({ loading: false, token }); - } - }, - () => { - if (this.mounted) { - this.setState({ loading: false }); - } + generateToken({ name: tokenName }).then(({ token }) => { + if (this.mounted) { + this.setState({ loading: false, token }); } - ); + }, this.stopLoading); } }; @@ -115,18 +104,11 @@ export default class TokenStep extends React.PureComponent { const { tokenName } = this.state; if (tokenName) { this.setState({ loading: true }); - revokeToken({ name: tokenName }).then( - () => { - if (this.mounted) { - this.setState({ loading: false, token: undefined, tokenName: undefined }); - } - }, - () => { - if (this.mounted) { - this.setState({ loading: false }); - } + revokeToken({ name: tokenName }).then(() => { + if (this.mounted) { + this.setState({ loading: false, token: undefined, tokenName: undefined }); } - ); + }, this.stopLoading); } }; @@ -137,20 +119,26 @@ export default class TokenStep extends React.PureComponent { } }; - handleGenerateClick = (event /*: Event */) => { + handleGenerateClick = (event: React.MouseEvent<HTMLAnchorElement>) => { event.preventDefault(); this.setState({ selection: 'generate' }); }; - handleUseExistingClick = (event /*: Event */) => { + handleUseExistingClick = (event: React.MouseEvent<HTMLAnchorElement>) => { event.preventDefault(); this.setState({ selection: 'use-existing' }); }; - handleExisingTokenChange = (event /*: { currentTarget: HTMLInputElement } */) => { + handleExisingTokenChange = (event: React.ChangeEvent<HTMLInputElement>) => { this.setState({ existingToken: event.currentTarget.value }); }; + stopLoading = () => { + if (this.mounted) { + this.setState({ loading: false }); + } + }; + renderGenerateOption = () => ( <div> {this.state.canUseExisting ? ( @@ -163,10 +151,10 @@ export default class TokenStep extends React.PureComponent { 'is-checked': this.state.selection === 'generate' })} /> - {translate('onboading.token.generate_token')} + {translate('onboarding.token.generate_token')} </a> ) : ( - translate('onboading.token.generate_token') + translate('onboarding.token.generate_token') )} {this.state.selection === 'generate' && ( <div className="big-spacer-top"> @@ -175,7 +163,7 @@ export default class TokenStep extends React.PureComponent { autoFocus={true} className="input-large spacer-right text-middle" onChange={this.handleTokenNameChange} - placeholder={translate('onboading.token.generate_token.placeholder')} + placeholder={translate('onboarding.token.generate_token.placeholder')} required={true} type="text" value={this.state.tokenName || ''} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/LanguageStep-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/LanguageStep-test.tsx index 2609b550dd5..629044f2c4c 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/LanguageStep-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/LanguageStep-test.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import LanguageStep from '../LanguageStep'; import { isSonarCloud } from '../../../../helpers/system'; @@ -26,29 +25,29 @@ import { isSonarCloud } from '../../../../helpers/system'; jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() })); beforeEach(() => { - isSonarCloud.mockImplementation(() => false); + (isSonarCloud as jest.Mock<any>).mockImplementation(() => false); }); it('selects java', () => { const onDone = jest.fn(); const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />); - wrapper.find('RadioToggle').prop('onCheck')('java'); + (wrapper.find('RadioToggle').prop('onCheck') as Function)('java'); wrapper.update(); expect(wrapper).toMatchSnapshot(); - wrapper + (wrapper .find('RadioToggle') .at(1) - .prop('onCheck')('maven'); + .prop('onCheck') as Function)('maven'); wrapper.update(); expect(wrapper).toMatchSnapshot(); expect(onDone).lastCalledWith({ language: 'java', javaBuild: 'maven' }); - wrapper + (wrapper .find('RadioToggle') .at(1) - .prop('onCheck')('gradle'); + .prop('onCheck') as Function)('gradle'); wrapper.update(); expect(wrapper).toMatchSnapshot(); expect(onDone).lastCalledWith({ language: 'java', javaBuild: 'gradle' }); @@ -58,52 +57,52 @@ it('selects c#', () => { const onDone = jest.fn(); const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />); - wrapper.find('RadioToggle').prop('onCheck')('dotnet'); + (wrapper.find('RadioToggle').prop('onCheck') as Function)('dotnet'); wrapper.update(); expect(wrapper).toMatchSnapshot(); - wrapper.find('NewProjectForm').prop('onDone')('project-foo'); + (wrapper.find('NewProjectForm').prop('onDone') as Function)('project-foo'); expect(onDone).lastCalledWith({ language: 'dotnet', projectKey: 'project-foo' }); }); it('selects c-family', () => { - isSonarCloud.mockImplementation(() => true); + (isSonarCloud as jest.Mock<any>).mockImplementation(() => true); const onDone = jest.fn(); const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />); - wrapper.find('RadioToggle').prop('onCheck')('c-family'); + (wrapper.find('RadioToggle').prop('onCheck') as Function)('c-family'); wrapper.update(); expect(wrapper).toMatchSnapshot(); - wrapper + (wrapper .find('RadioToggle') .at(1) - .prop('onCheck')('msvc'); + .prop('onCheck') as Function)('msvc'); wrapper.update(); expect(wrapper).toMatchSnapshot(); - wrapper.find('NewProjectForm').prop('onDone')('project-foo'); + (wrapper.find('NewProjectForm').prop('onDone') as Function)('project-foo'); expect(onDone).lastCalledWith({ language: 'c-family', cFamilyCompiler: 'msvc', projectKey: 'project-foo' }); - wrapper + (wrapper .find('RadioToggle') .at(1) - .prop('onCheck')('clang-gcc'); + .prop('onCheck') as Function)('clang-gcc'); wrapper.update(); expect(wrapper).toMatchSnapshot(); - wrapper + (wrapper .find('RadioToggle') .at(2) - .prop('onCheck')('linux'); + .prop('onCheck') as Function)('linux'); wrapper.update(); expect(wrapper).toMatchSnapshot(); - wrapper.find('NewProjectForm').prop('onDone')('project-foo'); + (wrapper.find('NewProjectForm').prop('onDone') as Function)('project-foo'); expect(onDone).lastCalledWith({ language: 'c-family', cFamilyCompiler: 'clang-gcc', @@ -116,17 +115,17 @@ it('selects other', () => { const onDone = jest.fn(); const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />); - wrapper.find('RadioToggle').prop('onCheck')('other'); + (wrapper.find('RadioToggle').prop('onCheck') as Function)('other'); wrapper.update(); expect(wrapper).toMatchSnapshot(); - wrapper + (wrapper .find('RadioToggle') .at(1) - .prop('onCheck')('mac'); + .prop('onCheck') as Function)('mac'); wrapper.update(); expect(wrapper).toMatchSnapshot(); - wrapper.find('NewProjectForm').prop('onDone')('project-foo'); + (wrapper.find('NewProjectForm').prop('onDone') as Function)('project-foo'); expect(onDone).lastCalledWith({ language: 'other', os: 'mac', projectKey: 'project-foo' }); }); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewOrganizationForm-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewOrganizationForm-test.tsx index fcfbf8d810c..bb16f901167 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewOrganizationForm-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewOrganizationForm-test.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { mount } from 'enzyme'; import NewOrganizationForm from '../NewOrganizationForm'; import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils'; @@ -48,7 +47,7 @@ it('deletes organization', async () => { const wrapper = mount(<NewOrganizationForm onDelete={onDelete} onDone={jest.fn()} />); wrapper.setState({ done: true, loading: false, organization: 'foo' }); expect(wrapper).toMatchSnapshot(); - wrapper.find('DeleteButton').prop('onClick')(); + (wrapper.find('DeleteButton').prop('onClick') as Function)(); wrapper.update(); expect(wrapper).toMatchSnapshot(); // spinner await waitAndUpdate(wrapper); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewProjectForm-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewProjectForm-test.tsx index 2d290f10503..2c8c18cace4 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewProjectForm-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewProjectForm-test.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { mount } from 'enzyme'; import NewProjectForm from '../NewProjectForm'; import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils'; @@ -47,7 +46,7 @@ it('deletes project', async () => { const wrapper = mount(<NewProjectForm onDelete={onDelete} onDone={jest.fn()} />); wrapper.setState({ done: true, loading: false, projectKey: 'foo' }); expect(wrapper).toMatchSnapshot(); - wrapper.find('DeleteButton').prop('onClick')(); + (wrapper.find('DeleteButton').prop('onClick') as Function)(); wrapper.update(); expect(wrapper).toMatchSnapshot(); // spinner await waitAndUpdate(wrapper); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/OrganizationStep-test.tsx index 01a5b711a2a..b2192a0bce7 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/OrganizationStep-test.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { mount } from 'enzyme'; import OrganizationStep from '../OrganizationStep'; import { click, waitAndUpdate } from '../../../../helpers/testUtils'; @@ -35,7 +34,7 @@ jest.mock('../../../../api/organizations', () => ({ const currentUser = { isLoggedIn: true, login: 'user' }; beforeEach(() => { - getOrganizations.mockClear(); + (getOrganizations as jest.Mock<any>).mockClear(); }); // FIXME @@ -73,10 +72,10 @@ it('works with existing organization', async () => { await waitAndUpdate(wrapper); click(wrapper.find('.js-existing')); expect(wrapper).toMatchSnapshot(); - wrapper + (wrapper .find('Select') .first() - .prop('onChange')({ value: 'another' }); + .prop('onChange') as Function)({ value: 'another' }); wrapper.update(); click(wrapper.find('[className="js-continue"]')); expect(onContinue).toBeCalledWith('another'); @@ -96,7 +95,7 @@ it('works with new organization', async () => { ); await waitAndUpdate(wrapper); click(wrapper.find('.js-new')); - wrapper.find('NewOrganizationForm').prop('onDone')('new'); + (wrapper.find('NewOrganizationForm').prop('onDone') as Function)('new'); wrapper.update(); click(wrapper.find('[className="js-continue"]')); expect(onContinue).toBeCalledWith('new'); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/Onboarding-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectOnboarding-test.tsx index e3acb2a83f6..078d6702aa6 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/Onboarding-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectOnboarding-test.tsx @@ -17,10 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { shallow, mount } from 'enzyme'; -import Onboarding from '../Onboarding'; +import { ProjectOnboarding } from '../ProjectOnboarding'; import { click, doAsync } from '../../../../helpers/testUtils'; import { getInstance, isSonarCloud } from '../../../../helpers/system'; @@ -36,11 +35,10 @@ jest.mock('../../../../helpers/system', () => ({ const currentUser = { login: 'admin', isLoggedIn: true }; it('guides for on-premise', () => { - getInstance.mockImplementation(() => 'SonarQube'); - isSonarCloud.mockImplementation(() => false); + (getInstance as jest.Mock<any>).mockImplementation(() => 'SonarQube'); + (isSonarCloud as jest.Mock<any>).mockImplementation(() => false); const wrapper = shallow( - <Onboarding - className="modal-container" + <ProjectOnboarding currentUser={currentUser} onFinish={jest.fn()} organizationsEnabled={false} @@ -48,39 +46,36 @@ it('guides for on-premise', () => { ); expect(wrapper).toMatchSnapshot(); - // $FlowFixMe - wrapper.instance().handleTokenDone('abcd1234'); + (wrapper.instance() as ProjectOnboarding).handleTokenDone('abcd1234'); wrapper.update(); expect(wrapper).toMatchSnapshot(); }); it('guides for sonarcloud', () => { - getInstance.mockImplementation(() => 'SonarCloud'); - isSonarCloud.mockImplementation(() => true); + (getInstance as jest.Mock<any>).mockImplementation(() => 'SonarCloud'); + (isSonarCloud as jest.Mock<any>).mockImplementation(() => true); const wrapper = shallow( - <Onboarding currentUser={currentUser} onFinish={jest.fn()} organizationsEnabled={true} /> + <ProjectOnboarding currentUser={currentUser} onFinish={jest.fn()} organizationsEnabled={true} /> ); expect(wrapper).toMatchSnapshot(); - // $FlowFixMe - wrapper.instance().handleOrganizationDone('my-org'); + (wrapper.instance() as ProjectOnboarding).handleOrganizationDone('my-org'); wrapper.update(); expect(wrapper).toMatchSnapshot(); - // $FlowFixMe - wrapper.instance().handleTokenDone('abcd1234'); + (wrapper.instance() as ProjectOnboarding).handleTokenDone('abcd1234'); wrapper.update(); expect(wrapper).toMatchSnapshot(); }); it('finishes', () => { - getInstance.mockImplementation(() => 'SonarQube'); - isSonarCloud.mockImplementation(() => false); + (getInstance as jest.Mock<any>).mockImplementation(() => 'SonarQube'); + (isSonarCloud as jest.Mock<any>).mockImplementation(() => false); const onFinish = jest.fn(); const wrapper = mount( - <Onboarding currentUser={currentUser} onFinish={onFinish} organizationsEnabled={false} /> + <ProjectOnboarding currentUser={currentUser} onFinish={onFinish} organizationsEnabled={false} /> ); - click(wrapper.find('.js-skip')); + click(wrapper.find('ResetButtonLink')); return doAsync(() => { expect(onFinish).toBeCalled(); }); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/ProjectWatcher-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectWatcher-test.tsx index cc6699fdefa..16215259325 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/ProjectWatcher-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectWatcher-test.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { shallow, mount } from 'enzyme'; import ProjectWatcher from '../ProjectWatcher'; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/Step-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/Step-test.tsx index 396c4fa769a..8a9664f71c8 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/Step-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/Step-test.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import Step from '../Step'; import { click } from '../../../../helpers/testUtils'; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/TokenStep-test.tsx index 4f439b440b5..25b5f0c7462 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/TokenStep-test.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { mount } from 'enzyme'; import TokenStep from '../TokenStep'; import { change, click, submit, waitAndUpdate } from '../../../../helpers/testUtils'; @@ -67,7 +66,7 @@ it('revokes token', async () => { await new Promise(setImmediate); wrapper.setState({ token: 'abcd1234', tokenName: 'my token' }); expect(wrapper).toMatchSnapshot(); - wrapper.find('DeleteButton').prop('onClick')(); + (wrapper.find('DeleteButton').prop('onClick') as Function)(); wrapper.update(); expect(wrapper).toMatchSnapshot(); // spinner await waitAndUpdate(wrapper); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/LanguageStep-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/LanguageStep-test.tsx.snap index 8e82d384b3f..8e82d384b3f 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/LanguageStep-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/LanguageStep-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewOrganizationForm-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap index eae8fcdb833..eae8fcdb833 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewOrganizationForm-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewProjectForm-test.tsx.snap index f6722a21bf6..f6722a21bf6 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewProjectForm-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OrganizationStep-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/OrganizationStep-test.tsx.snap index 7e30dedc4a9..7e30dedc4a9 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OrganizationStep-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/OrganizationStep-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectOnboarding-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectOnboarding-test.tsx.snap new file mode 100644 index 00000000000..0c5b93e108a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectOnboarding-test.tsx.snap @@ -0,0 +1,349 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`guides for on-premise 1`] = ` +<React.Fragment> + <HelmetWrapper + defer={true} + encodeSpecialCharacters={true} + title="onboarding.project.header" + titleTemplate="%s" + /> + <header + className="modal-head" + > + <h2> + onboarding.project.header + </h2> + </header> + <div + className="modal-body modal-container" + > + <p + className="spacer-top big-spacer-bottom" + > + onboarding.project.header.description.2 + </p> + <TokenStep + currentUser={ + Object { + "isLoggedIn": true, + "login": "admin", + } + } + finished={false} + onContinue={[Function]} + onOpen={[Function]} + open={true} + stepNumber={1} + /> + <AnalysisStep + onFinish={[Function]} + onReset={[Function]} + open={false} + stepNumber={2} + /> + </div> + <footer + className="modal-foot" + > + <ResetButtonLink + className="js-skip" + onClick={[Function]} + > + close + </ResetButtonLink> + <span + className="pull-left note" + > + tutorials.find_tutorial_back_in_help + </span> + </footer> +</React.Fragment> +`; + +exports[`guides for on-premise 2`] = ` +<React.Fragment> + <HelmetWrapper + defer={true} + encodeSpecialCharacters={true} + title="onboarding.project.header" + titleTemplate="%s" + /> + <header + className="modal-head" + > + <h2> + onboarding.project.header + </h2> + </header> + <div + className="modal-body modal-container" + > + <p + className="spacer-top big-spacer-bottom" + > + onboarding.project.header.description.2 + </p> + <TokenStep + currentUser={ + Object { + "isLoggedIn": true, + "login": "admin", + } + } + finished={true} + onContinue={[Function]} + onOpen={[Function]} + open={false} + stepNumber={1} + /> + <AnalysisStep + onFinish={[Function]} + onReset={[Function]} + open={true} + stepNumber={2} + token="abcd1234" + /> + </div> + <footer + className="modal-foot" + > + <ResetButtonLink + className="js-skip" + onClick={[Function]} + > + close + </ResetButtonLink> + <span + className="pull-left note" + > + tutorials.find_tutorial_back_in_help + </span> + </footer> +</React.Fragment> +`; + +exports[`guides for sonarcloud 1`] = ` +<React.Fragment> + <HelmetWrapper + defer={true} + encodeSpecialCharacters={true} + title="onboarding.project.header" + titleTemplate="%s" + /> + <header + className="modal-head" + > + <h2> + onboarding.project.header + </h2> + </header> + <div + className="modal-body modal-container" + > + <p + className="spacer-top big-spacer-bottom" + > + onboarding.project.header.description.3 + </p> + <OrganizationStep + currentUser={ + Object { + "isLoggedIn": true, + "login": "admin", + } + } + finished={false} + onContinue={[Function]} + onOpen={[Function]} + open={true} + stepNumber={1} + /> + <TokenStep + currentUser={ + Object { + "isLoggedIn": true, + "login": "admin", + } + } + finished={false} + onContinue={[Function]} + onOpen={[Function]} + open={false} + stepNumber={2} + /> + <AnalysisStep + onFinish={[Function]} + onReset={[Function]} + open={false} + stepNumber={3} + /> + </div> + <footer + className="modal-foot" + > + <ResetButtonLink + className="js-skip" + onClick={[Function]} + > + close + </ResetButtonLink> + <span + className="pull-left note" + > + tutorials.find_tutorial_back_in_plus + </span> + </footer> +</React.Fragment> +`; + +exports[`guides for sonarcloud 2`] = ` +<React.Fragment> + <HelmetWrapper + defer={true} + encodeSpecialCharacters={true} + title="onboarding.project.header" + titleTemplate="%s" + /> + <header + className="modal-head" + > + <h2> + onboarding.project.header + </h2> + </header> + <div + className="modal-body modal-container" + > + <p + className="spacer-top big-spacer-bottom" + > + onboarding.project.header.description.3 + </p> + <OrganizationStep + currentUser={ + Object { + "isLoggedIn": true, + "login": "admin", + } + } + finished={true} + onContinue={[Function]} + onOpen={[Function]} + open={false} + stepNumber={1} + /> + <TokenStep + currentUser={ + Object { + "isLoggedIn": true, + "login": "admin", + } + } + finished={false} + onContinue={[Function]} + onOpen={[Function]} + open={true} + stepNumber={2} + /> + <AnalysisStep + onFinish={[Function]} + onReset={[Function]} + open={false} + organization="my-org" + stepNumber={3} + /> + </div> + <footer + className="modal-foot" + > + <ResetButtonLink + className="js-skip" + onClick={[Function]} + > + close + </ResetButtonLink> + <span + className="pull-left note" + > + tutorials.find_tutorial_back_in_plus + </span> + </footer> +</React.Fragment> +`; + +exports[`guides for sonarcloud 3`] = ` +<React.Fragment> + <HelmetWrapper + defer={true} + encodeSpecialCharacters={true} + title="onboarding.project.header" + titleTemplate="%s" + /> + <header + className="modal-head" + > + <h2> + onboarding.project.header + </h2> + </header> + <div + className="modal-body modal-container" + > + <p + className="spacer-top big-spacer-bottom" + > + onboarding.project.header.description.3 + </p> + <OrganizationStep + currentUser={ + Object { + "isLoggedIn": true, + "login": "admin", + } + } + finished={true} + onContinue={[Function]} + onOpen={[Function]} + open={false} + stepNumber={1} + /> + <TokenStep + currentUser={ + Object { + "isLoggedIn": true, + "login": "admin", + } + } + finished={true} + onContinue={[Function]} + onOpen={[Function]} + open={false} + stepNumber={2} + /> + <AnalysisStep + onFinish={[Function]} + onReset={[Function]} + open={true} + organization="my-org" + stepNumber={3} + token="abcd1234" + /> + </div> + <footer + className="modal-foot" + > + <ResetButtonLink + className="js-skip" + onClick={[Function]} + > + close + </ResetButtonLink> + <span + className="pull-left note" + > + tutorials.find_tutorial_back_in_plus + </span> + </footer> +</React.Fragment> +`; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/ProjectWatcher-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectWatcher-test.tsx.snap index 4ecd80c2ae3..1cc4d9991f6 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/ProjectWatcher-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectWatcher-test.tsx.snap @@ -2,7 +2,7 @@ exports[`renders 1`] = ` <div - className="big-spacer-top note text-center" + className="pull-left note" > onboarding.project_watcher.not_started </div> @@ -10,7 +10,7 @@ exports[`renders 1`] = ` exports[`renders 2`] = ` <div - className="big-spacer-top note text-center" + className="pull-left note" > <i className="spinner spacer-right" @@ -21,7 +21,7 @@ exports[`renders 2`] = ` exports[`renders 3`] = ` <div - className="big-spacer-top note text-center" + className="pull-left note display-inline-flex-center" > <AlertSuccessIcon className="spacer-right" @@ -32,7 +32,7 @@ exports[`renders 3`] = ` exports[`renders 4`] = ` <div - className="big-spacer-top note text-center" + className="pull-left note" > <i className="spinner spacer-right" diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Step-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/Step-test.tsx.snap index 4667ca7ae8a..4667ca7ae8a 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Step-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/Step-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/TokenStep-test.tsx.snap index 9b991a92305..54847972e8c 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/TokenStep-test.tsx.snap @@ -50,7 +50,7 @@ exports[`generates token 1`] = ` <i className="icon-radio spacer-right is-checked" /> - onboading.token.generate_token + onboarding.token.generate_token </a> <div className="big-spacer-top" @@ -62,7 +62,7 @@ exports[`generates token 1`] = ` autoFocus={true} className="input-large spacer-right text-middle" onChange={[Function]} - placeholder="onboading.token.generate_token.placeholder" + placeholder="onboarding.token.generate_token.placeholder" required={true} type="text" value="" @@ -166,7 +166,7 @@ exports[`generates token 2`] = ` <i className="icon-radio spacer-right is-checked" /> - onboading.token.generate_token + onboarding.token.generate_token </a> <div className="big-spacer-top" @@ -178,7 +178,7 @@ exports[`generates token 2`] = ` autoFocus={true} className="input-large spacer-right text-middle" onChange={[Function]} - placeholder="onboading.token.generate_token.placeholder" + placeholder="onboarding.token.generate_token.placeholder" required={true} type="text" value="my token" @@ -582,7 +582,7 @@ exports[`revokes token 3`] = ` <i className="icon-radio spacer-right is-checked" /> - onboading.token.generate_token + onboarding.token.generate_token </a> <div className="big-spacer-top" @@ -594,7 +594,7 @@ exports[`revokes token 3`] = ` autoFocus={true} className="input-large spacer-right text-middle" onChange={[Function]} - placeholder="onboading.token.generate_token.placeholder" + placeholder="onboarding.token.generate_token.placeholder" required={true} type="text" value="" diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/BuildWrapper.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/BuildWrapper.tsx index 47fe5202388..ed03adbf911 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/BuildWrapper.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/BuildWrapper.tsx @@ -17,24 +17,22 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { translate } from '../../../../helpers/l10n'; +import { getBaseUrl } from '../../../../helpers/urls'; -/*:: -type Props = { - className?: string, - os: string -}; -*/ +interface Props { + className?: string; + os: string; +} -const filenames = { +const filenames: { [key: string]: string } = { win: 'build-wrapper-win-x86.zip', linux: 'build-wrapper-linux-x86.zip', mac: 'build-wrapper-macosx-x86.zip' }; -export default function BuildWrapper(props /*: Props */) { +export default function BuildWrapper(props: Props) { return ( <div className={props.className}> <h4 className="spacer-bottom"> @@ -50,7 +48,7 @@ export default function BuildWrapper(props /*: Props */) { <a className="button" download={filenames[props.os]} - href={window.baseUrl + '/static/cpp/' + filenames[props.os]} + href={`${getBaseUrl()}/static/cpp/${filenames[props.os]}`} target="_blank"> {translate('download_verb')} </a> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/ClangGCC.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/ClangGCC.tsx index 0c1fdab16cf..6ebe42a3ca9 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/ClangGCC.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/ClangGCC.tsx @@ -17,31 +17,28 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import SQScanner from './SQScanner'; import BuildWrapper from './BuildWrapper'; import CodeSnippet from '../../../../components/common/CodeSnippet'; import InstanceMessage from '../../../../components/common/InstanceMessage'; import { translate } from '../../../../helpers/l10n'; -/*:: -type Props = { - host: string, - os: string, - organization?: string, - projectKey: string, - token: string -}; -*/ +interface Props { + host: string; + os: string; + organization?: string; + projectKey: string; + token: string; +} -const executables = { +const executables: { [key: string]: string } = { linux: 'build-wrapper-linux-x86-64', win: 'build-wrapper-win-x86-64.exe', mac: 'build-wrapper-macosx-x86' }; -export default function ClangGCC(props /*: Props */) { +export default function ClangGCC(props: Props) { const command1 = `${executables[props.os]} --out-dir bw-output make clean all`; const command2 = [ diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/DotNet.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/DotNet.tsx index a5bd3fd4285..347ea01b7ac 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/DotNet.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/DotNet.tsx @@ -17,23 +17,20 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import MSBuildScanner from './MSBuildScanner'; import CodeSnippet from '../../../../components/common/CodeSnippet'; import InstanceMessage from '../../../../components/common/InstanceMessage'; import { translate } from '../../../../helpers/l10n'; -/*:: -type Props = {| - host: string, - organization?: string, - projectKey: string, - token: string -|}; -*/ +interface Props { + host: string; + organization?: string; + projectKey: string; + token: string; +} -export default function DotNet(props /*: Props */) { +export default function DotNet(props: Props) { const command1 = [ 'SonarQube.Scanner.MSBuild.exe begin', `/k:"${props.projectKey}"`, diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/JavaGradle.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaGradle.tsx index d3cc1c847da..02da9fd1a2a 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/JavaGradle.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaGradle.tsx @@ -17,21 +17,18 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import CodeSnippet from '../../../../components/common/CodeSnippet'; import InstanceMessage from '../../../../components/common/InstanceMessage'; import { translate } from '../../../../helpers/l10n'; -/*:: -type Props = {| - host: string, - organization?: string, - token: string -|}; -*/ +interface Props { + host: string; + organization?: string; + token: string; +} -export default function JavaGradle(props /*: Props */) { +export default function JavaGradle(props: Props) { const config = 'plugins {\n id "org.sonarqube" version "2.6"\n}'; const command = [ diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/JavaMaven.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaMaven.tsx index 8a02fb75630..5663b1f2911 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/JavaMaven.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaMaven.tsx @@ -17,21 +17,18 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import CodeSnippet from '../../../../components/common/CodeSnippet'; import InstanceMessage from '../../../../components/common/InstanceMessage'; import { translate } from '../../../../helpers/l10n'; -/*:: -type Props = {| - host: string, - organization?: string, - token: string -|}; -*/ +interface Props { + host: string; + organization?: string; + token: string; +} -export default function JavaMaven(props /*: Props */) { +export default function JavaMaven(props: Props) { const command = [ 'mvn sonar:sonar', props.organization && `-Dsonar.organization=${props.organization}`, diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/MSBuildScanner.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/MSBuildScanner.tsx index af0ade047c3..65c4005af86 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/MSBuildScanner.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/MSBuildScanner.tsx @@ -17,17 +17,15 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; + +import * as React from 'react'; import { translate } from '../../../../helpers/l10n'; -/*:: -type Props = { - className?: string -}; -*/ +interface Props { + className?: string; +} -export default function MSBuildScanner(props /*: Props */) { +export default function MSBuildScanner(props: Props) { return ( <div className={props.className}> <h4 className="spacer-bottom">{translate('onboarding.analysis.msbuild.header')}</h4> @@ -39,6 +37,7 @@ export default function MSBuildScanner(props /*: Props */) { <a className="button" href="http://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html" + rel="noopener noreferrer" target="_blank"> {translate('download_verb')} </a> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Msvc.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Msvc.tsx index 03f5fe3eb63..cf6120a3b31 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Msvc.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Msvc.tsx @@ -17,24 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import MSBuildScanner from './MSBuildScanner'; import BuildWrapper from './BuildWrapper'; import CodeSnippet from '../../../../components/common/CodeSnippet'; import InstanceMessage from '../../../../components/common/InstanceMessage'; import { translate } from '../../../../helpers/l10n'; -/*:: -type Props = {| - host: string, - organization?: string, - projectKey: string, - token: string -|}; -*/ +interface Props { + host: string; + organization?: string; + projectKey: string; + token: string; +} -export default function Msvc(props /*: Props */) { +export default function Msvc(props: Props) { const command1 = [ 'SonarQube.Scanner.MSBuild.exe begin', `/k:"${props.projectKey}"`, diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Other.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Other.tsx index 5bb0d0dd649..3d0b3b2a718 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Other.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Other.tsx @@ -17,24 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import SQScanner from './SQScanner'; import CodeSnippet from '../../../../components/common/CodeSnippet'; import InstanceMessage from '../../../../components/common/InstanceMessage'; import { translate } from '../../../../helpers/l10n'; -/*:: -type Props = {| - host: string, - organization?: string, - os: string, - projectKey: string, - token: string -|}; -*/ +interface Props { + host: string; + organization?: string; + os: string; + projectKey: string; + token: string; +} -export default function Other(props /*: Props */) { +export default function Other(props: Props) { const command = [ props.os === 'win' ? 'sonar-scanner.bat' : 'sonar-scanner', `-Dsonar.projectKey=${props.projectKey}`, diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/SQScanner.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/SQScanner.tsx index 3fd3e3e9711..78b90f851bc 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/SQScanner.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/SQScanner.tsx @@ -17,18 +17,16 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; + +import * as React from 'react'; import { translate } from '../../../../helpers/l10n'; -/*:: -type Props = { - className?: string, - os: string -}; -*/ +interface Props { + className?: string; + os: string; +} -export default function SQScanner(props /*: Props */) { +export default function SQScanner(props: Props) { return ( <div className={props.className}> <h4 className="spacer-bottom"> @@ -44,6 +42,7 @@ export default function SQScanner(props /*: Props */) { <a className="button" href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" + rel="noopener noreferrer" target="_blank"> {translate('download_verb')} </a> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/BuildWrapper-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/BuildWrapper-test.tsx index 4d3f979b3c8..b19e334793b 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/BuildWrapper-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/BuildWrapper-test.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import BuildWrapper from '../BuildWrapper'; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/ClangGCC-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/ClangGCC-test.tsx index 9f8f9243da5..a6f7c4f550a 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/ClangGCC-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/ClangGCC-test.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; + +import * as React from 'react'; import { shallow } from 'enzyme'; import ClangGCC from '../ClangGCC'; @@ -35,8 +35,8 @@ it('renders correctly', () => { shallow( <ClangGCC host="host" - os="linux" organization="organization" + os="linux" projectKey="projectKey" token="token" /> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/DotNet-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/DotNet-test.tsx index b9dc347956a..91ecab828b2 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/DotNet-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/DotNet-test.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; + +import * as React from 'react'; import { shallow } from 'enzyme'; import DotNet from '../DotNet'; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/JavaGradle-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaGradle-test.tsx index bfddcc9c728..f0d130a248d 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/JavaGradle-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaGradle-test.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; + +import * as React from 'react'; import { shallow } from 'enzyme'; import JavaGradle from '../JavaGradle'; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/JavaMaven-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaMaven-test.tsx index 592e578b3bc..dbda16ca13e 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/JavaMaven-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaMaven-test.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; + +import * as React from 'react'; import { shallow } from 'enzyme'; import JavaMaven from '../JavaMaven'; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/MSBuildScanner-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/MSBuildScanner-test.tsx index 5551fcead5c..e5411341d10 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/MSBuildScanner-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/MSBuildScanner-test.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; + +import * as React from 'react'; import { shallow } from 'enzyme'; import MSBuildScanner from '../MSBuildScanner'; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/Msvc-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Msvc-test.tsx index 62ccf801fb9..78470bbd2e6 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/Msvc-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Msvc-test.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; + +import * as React from 'react'; import { shallow } from 'enzyme'; import Msvc from '../Msvc'; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/Other-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Other-test.tsx index b7175078c96..16cc44ae69f 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/Other-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Other-test.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; + +import * as React from 'react'; import { shallow } from 'enzyme'; import Other from '../Other'; @@ -35,8 +35,8 @@ it('renders correctly', () => { shallow( <Other host="host" - os="linux" organization="organization" + os="linux" projectKey="projectKey" token="token" /> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/SQScanner-test.js b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/SQScanner-test.tsx index 11a0ccb58ad..2be4e546bb3 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/SQScanner-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/SQScanner-test.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; + +import * as React from 'react'; import { shallow } from 'enzyme'; import SQScanner from '../SQScanner'; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/BuildWrapper-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap index 07e8eb45f0a..07e8eb45f0a 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/BuildWrapper-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/ClangGCC-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap index cdffd4c2061..cdffd4c2061 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/ClangGCC-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/DotNet-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/DotNet-test.tsx.snap index 89b57fa28b4..89b57fa28b4 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/DotNet-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/DotNet-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/JavaGradle-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap index 401137a9234..401137a9234 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/JavaGradle-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/JavaMaven-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap index d4462b957c6..d4462b957c6 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/JavaMaven-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/MSBuildScanner-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap index c7f156bc53f..740e26ed12b 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/MSBuildScanner-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap @@ -19,6 +19,7 @@ exports[`renders correctly 1`] = ` <a className="button" href="http://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html" + rel="noopener noreferrer" target="_blank" > download_verb diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Msvc-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Msvc-test.tsx.snap index 2240a13995c..2240a13995c 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Msvc-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Msvc-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Other-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Other-test.tsx.snap index 73aba357dca..73aba357dca 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Other-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Other-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/SQScanner-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap index bcdac075ce4..cf6a8de4ce3 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/SQScanner-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap @@ -19,6 +19,7 @@ exports[`renders correctly 1`] = ` <a className="button" href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" + rel="noopener noreferrer" target="_blank" > download_verb @@ -46,6 +47,7 @@ exports[`renders correctly 2`] = ` <a className="button" href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" + rel="noopener noreferrer" target="_blank" > download_verb @@ -73,6 +75,7 @@ exports[`renders correctly 3`] = ` <a className="button" href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" + rel="noopener noreferrer" target="_blank" > download_verb diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/styles.css b/server/sonar-web/src/main/js/apps/tutorials/styles.css index 9c8dfc093fc..f73428e0f40 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/styles.css +++ b/server/sonar-web/src/main/js/apps/tutorials/styles.css @@ -17,9 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -.onboarding { - min-height: calc(70vh - 60px); -} .onboarding-step { position: relative; @@ -58,12 +55,8 @@ outline: none; } -.onboarding .page-actions { - text-align: right; - margin-bottom: 0; -} - -.onboarding .page-actions p { - line-height: 16px; - margin-top: 6px; +.onboarding-choices { + display: flex; + justify-content: space-around; + padding: 24px 0 44px; } diff --git a/server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/TeamOnboardingModal.tsx b/server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/TeamOnboardingModal.tsx new file mode 100644 index 00000000000..0763e82b9ab --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/TeamOnboardingModal.tsx @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router'; +import Modal from '../../../components/controls/Modal'; +import { translate } from '../../../helpers/l10n'; +import { ResetButtonLink } from '../../../components/ui/buttons'; + +interface Props { + onFinish: () => void; +} + +export default class TeamOnboardingModal extends React.PureComponent<Props> { + render() { + const header = translate('onboarding.team.header'); + return ( + <Modal + contentLabel={header} + medium={true} + onRequestClose={this.props.onFinish} + shouldCloseOnOverlayClick={false}> + <header className="modal-head"> + <h2>{header}</h2> + </header> + <div className="modal-body"> + <div className="alert alert-info modal-alert"> + {translate('onboarding.team.work_in_progress')} + </div> + <p className="spacer-top big-spacer-bottom">{translate('onboarding.team.first_step')}</p> + <p className="spacer-top big-spacer-bottom"> + <FormattedMessage + defaultMessage={translate('onboarding.team.how_to_join')} + id="onboarding.team.how_to_join" + values={{ + link: ( + <Link onClick={this.props.onFinish} to="/documentation/organizations/manage-team"> + {translate('as_explained_here')} + </Link> + ) + }} + /> + </p> + </div> + <footer className="modal-foot"> + <ResetButtonLink onClick={this.props.onFinish}>{translate('close')}</ResetButtonLink> + </footer> + </Modal> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.d.ts b/server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/__tests__/TeamOnboardingModal-test.tsx index 4b70adcc600..0bd903f80f2 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.d.ts +++ b/server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/__tests__/TeamOnboardingModal-test.tsx @@ -18,9 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { shallow } from 'enzyme'; +import TeamOnboardingModal from '../TeamOnboardingModal'; -export interface Props { - onFinish: () => void; -} - -export default class OnboardingModal extends React.PureComponent<Props> {} +it('renders correctly', () => { + expect(shallow(<TeamOnboardingModal onFinish={jest.fn()} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/__tests__/__snapshots__/TeamOnboardingModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/__tests__/__snapshots__/TeamOnboardingModal-test.tsx.snap new file mode 100644 index 00000000000..a510b0e0eb9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/__tests__/__snapshots__/TeamOnboardingModal-test.tsx.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<Modal + contentLabel="onboarding.team.header" + medium={true} + onRequestClose={[MockFunction]} + shouldCloseOnOverlayClick={false} +> + <header + className="modal-head" + > + <h2> + onboarding.team.header + </h2> + </header> + <div + className="modal-body" + > + <div + className="alert alert-info modal-alert" + > + onboarding.team.work_in_progress + </div> + <p + className="spacer-top big-spacer-bottom" + > + onboarding.team.first_step + </p> + <p + className="spacer-top big-spacer-bottom" + > + <FormattedMessage + defaultMessage="onboarding.team.how_to_join" + id="onboarding.team.how_to_join" + values={ + Object { + "link": <Link + onClick={[MockFunction]} + onlyActiveOnIndex={false} + style={Object {}} + to="/documentation/organizations/manage-team" + > + as_explained_here + </Link>, + } + } + /> + </p> + </div> + <footer + className="modal-foot" + > + <ResetButtonLink + onClick={[MockFunction]} + > + close + </ResetButtonLink> + </footer> +</Modal> +`; diff --git a/server/sonar-web/src/main/js/helpers/testUtils.ts b/server/sonar-web/src/main/js/helpers/testUtils.ts index d650182b932..f3f96a9506a 100644 --- a/server/sonar-web/src/main/js/helpers/testUtils.ts +++ b/server/sonar-web/src/main/js/helpers/testUtils.ts @@ -44,13 +44,13 @@ export function clickOutside(event = {}): void { window.dispatchEvent(dispatchedEvent); } -export function submit(element: ShallowWrapper): void { +export function submit(element: ShallowWrapper | ReactWrapper): void { element.simulate('submit', { preventDefault() {} }); } -export function change(element: ShallowWrapper, value: string, event = {}): void { +export function change(element: ShallowWrapper | ReactWrapper, value: string, event = {}): void { element.simulate('change', { target: { value }, currentTarget: { value }, diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index ac5f5a477ce..0027ae95882 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -202,6 +202,7 @@ no=No and_worse=and worse are_you_sure=Are you sure? +as_explained_here=as explained here assigned_to=Assigned to bulk_change=Bulk Change bulleted_point=Bulleted point @@ -921,8 +922,8 @@ shortcuts.section.rules.deactivate=deactivate selected rule tutorials.onboarding=Analyze a new project tutorials.skip=Skip this tutorial tutorials.finish=Finish this tutorial -tutorials.find_it_back_in_help=Find it back anytime in the Help section -tutorials.find_it_back_in_plus=Find it back anytime in the "+" menu +tutorials.find_tutorial_back_in_help=Find this tutorial back anytime in the Help section +tutorials.find_tutorial_back_in_plus=Find this tutorial back anytime in the "+" menu #------------------------------------------------------------------------------ @@ -2589,15 +2590,30 @@ footer.web_api=Web API # ONBOARDING # #------------------------------------------------------------------------------ -onboarding.header=Welcome to {instance}! -onboarding.header.description=Want to quickly analyze a first project? Follow these {0} easy steps. +onboarding.header=Welcome to SonarCloud! +onboarding.header.description=Let us help you get started. What do you want to do? + +onboarding.project.header=Analyze a project +onboarding.project.header.description=Want to quickly analyze a first project? Follow these {0} easy steps. + +onboarding.team.header=Join a team +onboarding.team.first_step=Well congrats, the first step is done! +onboarding.team.how_to_join=To join a team, the only thing you need to do is to be a user registered on Sonarcloud. The administrator of the Sonarcloud organization you wish to join has to add you to his organization's members {link}. Ask him to do so! +onboarding.team.work_in_progress=We are currently working on a better way to join a team or invite people to yours. + +onboarding.analyze_public_code=I want to analyze public code +onboarding.analyze_public_code.button=Analyze a project +onboarding.analyze_private_code=I want to analyze private code +onboarding.analyze_private_code.button=Setup a new organization +onboarding.contribute_existing_project=I want to contribute to an existing project +onboarding.contribute_existing_project.button=Join a team onboarding.token.header=Provide a token onboarding.token.text=The token is used to identify you when an analysis is performed. If it has been compromised, you can revoke it at any point of time in your user account. onboarding.token.generate=Generate onboarding.token.placeholder=Enter a name for your token -onboading.token.generate_token=Generate a token -onboading.token.generate_token.placeholder=Enter a name for your token +onboarding.token.generate_token=Generate a token +onboarding.token.generate_token.placeholder=Enter a name for your token onboarding.token.use_existing_token=Use existing token onboarding.token.use_existing_token.placeholder=Enter your existing token onboarding.token.invalid_format=The token you have entered has invalid format. |