From: Stas Vilchik Date: Fri, 16 Jun 2017 12:56:47 +0000 (-0700) Subject: apply feedback on onboarding (#2175) X-Git-Tag: 6.5-M2~116 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=ac756ae021e7a9fadc7a2cbbcd80ce4278022edd;p=sonarqube.git apply feedback on onboarding (#2175) --- diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js index dc282991294..cb2d2eca428 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js @@ -25,9 +25,11 @@ import GlobalNavMenu from './GlobalNavMenu'; import GlobalNavUserContainer from './GlobalNavUserContainer'; import Search from '../../search/Search'; import GlobalHelp from '../../help/GlobalHelp'; +import Tooltip from '../../../../components/controls/Tooltip'; import HelpIcon from '../../../../components/icons-components/HelpIcon'; import OnboardingModal from '../../../../apps/tutorials/onboarding/OnboardingModal'; import { getCurrentUser, getAppState, getSettingValue } from '../../../../store/rootReducer'; +import { translate } from '../../../../helpers/l10n'; type Props = { appState: { organizationsEnabled: boolean }, @@ -37,12 +39,18 @@ type Props = { type State = { helpOpen: boolean, - onboardingTutorialOpen: boolean + onboardingTutorialOpen: boolean, + onboardingTutorialTooltip: boolean }; class GlobalNav extends React.PureComponent { + interval: ?number; props: Props; - state: State = { helpOpen: false, onboardingTutorialOpen: false }; + state: State = { + helpOpen: false, + onboardingTutorialOpen: false, + onboardingTutorialTooltip: false + }; componentDidMount() { window.addEventListener('keypress', this.onKeyPress); @@ -52,6 +60,9 @@ class GlobalNav extends React.PureComponent { } componentWillUnmount() { + if (this.interval) { + clearInterval(this.interval); + } window.removeEventListener('keypress', this.onKeyPress); } @@ -76,7 +87,14 @@ class GlobalNav extends React.PureComponent { openOnboardingTutorial = () => this.setState({ helpOpen: false, onboardingTutorialOpen: true }); - closeOnboardingTutorial = () => this.setState({ onboardingTutorialOpen: false }); + finishOnboardingTutorial = () => this.setState({ onboardingTutorialOpen: false }); + + skipOnboardingTutorial = () => { + this.setState({ onboardingTutorialOpen: false, onboardingTutorialTooltip: true }); + this.interval = setInterval(() => { + this.setState({ onboardingTutorialTooltip: false }); + }, 3000); + }; render() { return ( @@ -90,7 +108,14 @@ class GlobalNav extends React.PureComponent {
  • - + {this.state.onboardingTutorialTooltip + ? + + + : }
  • @@ -106,7 +131,10 @@ class GlobalNav extends React.PureComponent { />} {this.state.onboardingTutorialOpen && - } + } ); } diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/AnalysisStep.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/AnalysisStep.js index 83b7319a013..78733e66992 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/AnalysisStep.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/AnalysisStep.js @@ -50,7 +50,8 @@ export default class AnalysisStep extends React.PureComponent { handleLanguageSelect = (result?: Result) => { this.setState({ result }); - this.props.onFinish(result && result.projectKey); + const projectKey = result && result.language !== 'java' ? result.projectKey : undefined; + this.props.onFinish(projectKey); }; handleLanguageReset = () => { @@ -174,6 +175,8 @@ export default class AnalysisStep extends React.PureComponent { render() { return ( {}} open={this.props.open} renderForm={this.renderForm} renderResult={this.renderResult} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewOrganizationForm.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewOrganizationForm.js index 16a7c54d526..1f20fe29572 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewOrganizationForm.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewOrganizationForm.js @@ -20,6 +20,7 @@ // @flow import React from 'react'; import { debounce } from 'lodash'; +import CloseIcon from '../../../components/icons-components/CloseIcon'; import { createOrganization, deleteOrganization, @@ -124,9 +125,9 @@ export default class NewOrganizationForm extends React.PureComponent { ?
    {organization} {loading - ? - : } :
    @@ -142,7 +143,7 @@ export default class NewOrganizationForm extends React.PureComponent { value={organization} /> {loading - ? + ? : } {!unique && diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewProjectForm.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewProjectForm.js index 2bf64f8fe5a..11f24180d82 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewProjectForm.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewProjectForm.js @@ -19,6 +19,7 @@ */ // @flow import React from 'react'; +import CloseIcon from '../../../components/icons-components/CloseIcon'; import { createProject, deleteProject } from '../../../api/components'; import { translate } from '../../../helpers/l10n'; @@ -109,9 +110,9 @@ export default class NewProjectForm extends React.PureComponent { ? {projectKey} {loading - ? - : } :
    @@ -126,7 +127,7 @@ export default class NewProjectForm extends React.PureComponent { value={projectKey} /> {loading - ? + ? : }
    {translate('onboarding.project_key_requirement')} 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 index 06a5de99f38..2f79f7add07 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js @@ -19,6 +19,7 @@ */ // @flow import React from 'react'; +import Helmet from 'react-helmet'; import TokenStep from './TokenStep'; import OrganizationStep from './OrganizationStep'; import AnalysisStep from './AnalysisStep'; @@ -29,12 +30,13 @@ import { getProjectUrl } from '../../../helpers/urls'; import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; import './styles.css'; -type Props = { +type Props = {| currentUser: { login: string, isLoggedIn: boolean }, + onFinish: () => void, onSkip: () => void, organizationsEnabled: boolean, sonarCloud: boolean -}; +|}; type State = { finished: boolean, @@ -74,12 +76,16 @@ export default class Onboarding extends React.PureComponent { this.mounted = false; } - finishOnboarding = () => { + finishOnboarding = (skipped: boolean = false) => { this.setState({ skipping: true }); skipOnboarding().then( () => { if (this.mounted) { - this.props.onSkip(); + if (skipped) { + this.props.onSkip(); + } else { + this.props.onFinish(); + } if (this.state.projectKey) { this.context.router.push(getProjectUrl(this.state.projectKey)); @@ -107,9 +113,13 @@ export default class Onboarding extends React.PureComponent { this.setState({ organization, step: 'token' }); }; + handleTokenOpen = () => this.setState({ step: 'token' }); + + handleOrganizationOpen = () => this.setState({ step: 'organization' }); + handleSkipClick = (event: Event) => { event.preventDefault(); - this.finishOnboarding(); + this.finishOnboarding(true); }; handleFinish = (projectKey?: string) => this.setState({ finished: true, projectKey }); @@ -126,61 +136,69 @@ export default class Onboarding extends React.PureComponent { let stepNumber = 1; + const header = translate(sonarCloud ? 'onboarding.header.sonarcloud' : 'onboarding.header'); + return ( -
    -
    -

    - {translate(sonarCloud ? 'onboarding.header.sonarcloud' : 'onboarding.header')} -

    -
    - {this.state.skipping - ? - : - {translate('tutorials.skip')} - } -
    -
    - {translate('onboarding.header.description')} -
    -
    - - {organizationsEnabled && - + + +
    +
    +

    {header}

    +
    + {this.state.skipping + ? + : + {translate('tutorials.skip')} + } +
    +
    + {translate('onboarding.header.description')} +
    +
    + + {organizationsEnabled && + } + + } - - - - - - {this.state.finished && - !this.state.skipping && - (this.state.projectKey - ? - : )} + /> + + + + {this.state.finished && + !this.state.skipping && + (this.state.projectKey + ? + : )} +
    ); } diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.js index 13e1af88107..3ec32829d17 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.js @@ -22,9 +22,10 @@ import React from 'react'; import Modal from 'react-modal'; import { translate } from '../../../helpers/l10n'; -type Props = { - onClose: () => void -}; +type Props = {| + onFinish: () => void, + onSkip: () => void +|}; type State = { OnboardingContainer?: Object @@ -60,9 +61,10 @@ export default class OnboardingModal extends React.PureComponent { - {OnboardingContainer != null && } + {OnboardingContainer != null && + } ); } diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js index 325e8e8ac56..9270d7c6033 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js @@ -27,11 +27,14 @@ import NewOrganizationForm from './NewOrganizationForm'; import { getMyOrganizations } from '../../../api/organizations'; import { translate } from '../../../helpers/l10n'; -type Props = { +type Props = {| currentUser: { login: string, isLoggedIn: boolean }, + finished: boolean, + onOpen: () => void, + onContinue: (organization: string) => void, open: boolean, - onContinue: (organization: string) => void -}; + stepNumber: number +|}; type State = { loading: boolean, @@ -229,10 +232,12 @@ export default class OrganizationStep extends React.PureComponent { render() { return ( ); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/Step.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/Step.js index 763cef635df..fba30c8102b 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/Step.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/Step.js @@ -21,21 +21,35 @@ import React from 'react'; import classNames from 'classnames'; -type Props = { +type Props = {| + finished: boolean, + onOpen: () => void, open: boolean, renderForm: () => React.Element<*>, renderResult: () => ?React.Element<*>, stepNumber: number, stepTitle: string -}; +|}; export default function Step(props: Props) { const className = classNames('boxed-group', 'onboarding-step', { - 'onboarding-step-open': props.open + 'is-open': props.open, + 'is-finished': props.finished }); + const clickable = !props.open && props.finished; + + const handleClick = (event: Event) => { + event.preventDefault; + props.onOpen(); + }; + return ( -
    +
    {props.stepNumber}
    {!props.open && props.renderResult()}
    diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js index 9a171f7bc77..443d4f5c2ec 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js @@ -20,14 +20,17 @@ // @flow import React from 'react'; import Step from './Step'; +import CloseIcon from '../../../components/icons-components/CloseIcon'; import { generateToken, revokeToken } from '../../../api/user-tokens'; import { translate } from '../../../helpers/l10n'; -type Props = { +type Props = {| + finished: boolean, open: boolean, onContinue: (token: string) => void, + onOpen: () => void, stepNumber: number -}; +|}; type State = { loading: boolean, @@ -38,11 +41,6 @@ type State = { export default class TokenStep extends React.PureComponent { mounted: boolean; props: Props; - - static defaultProps = { - stepNumber: 1 - }; - state: State = { loading: false }; @@ -111,24 +109,20 @@ export default class TokenStep extends React.PureComponent { return (
    -
    - {translate('onboarding.token.text')} -
    - {token != null ? - {tokenName}{': '} - {token} + {tokenName}{': '} + {token} {loading - ? - : } :
    {loading - ? - : } + ? + : } } +
    + {translate('onboarding.token.text')} +
    + {token != null &&
    @@ -103,11 +116,24 @@ exports[`deletes organization 1`] = ` foo @@ -127,7 +153,7 @@ exports[`deletes organization 2`] = ` foo 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/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap index 50ef33521cc..1943af530a5 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/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap @@ -69,7 +69,7 @@ exports[`creates new project 2`] = ` value="foo" />
    @@ -136,11 +149,24 @@ exports[`deletes project 1`] = ` foo
    @@ -169,7 +195,7 @@ exports[`deletes project 2`] = ` foo
    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 index df1611144a1..5e9291b6c85 100644 --- 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 @@ -2,257 +2,313 @@ exports[`guides for on-premise 1`] = `
    -
    +
    -

    - onboarding.header -

    - -
    - onboarding.header.description -
    -
    - - + onboarding.header + + +
    + onboarding.header.description +
    + + + +
    `; exports[`guides for on-premise 2`] = `
    -
    +
    -

    - onboarding.header -

    - -
    - onboarding.header.description -
    -
    - - + onboarding.header + + +
    + onboarding.header.description +
    + + + +
    `; exports[`guides for sonarcloud 1`] = `
    -
    +
    -

    - onboarding.header.sonarcloud -

    - -
    - onboarding.header.description -
    -
    - + +
    + onboarding.header.description +
    + + - - + finished={false} + onContinue={[Function]} + onOpen={[Function]} + open={true} + stepNumber={1} + /> + + +
    `; exports[`guides for sonarcloud 2`] = `
    -
    +
    -

    - onboarding.header.sonarcloud -

    - -
    - onboarding.header.description -
    -
    - + +
    + onboarding.header.description +
    + + - - + finished={true} + onContinue={[Function]} + onOpen={[Function]} + open={false} + stepNumber={1} + /> + + +
    `; exports[`guides for sonarcloud 3`] = `
    -
    +
    -

    - onboarding.header.sonarcloud -

    - -
    - onboarding.header.description -
    -
    - + +
    + onboarding.header.description +
    + + - - + finished={true} + onContinue={[Function]} + onOpen={[Function]} + open={false} + stepNumber={1} + /> + + +
    `; 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/onboarding/__tests__/__snapshots__/Step-test.js.snap index 1df068a503c..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/onboarding/__tests__/__snapshots__/Step-test.js.snap @@ -2,7 +2,7 @@ exports[`renders 1`] = `
    -
    - onboarding.token.text -
    -
    +
    + onboarding.token.text +
    @@ -60,11 +66,15 @@ exports[`generates token 1`] = ` exports[`generates token 2`] = `
    -
    - onboarding.token.text -
    +
    + onboarding.token.text +
    @@ -118,11 +128,15 @@ exports[`generates token 2`] = ` exports[`generates token 3`] = `
    -
    - onboarding.token.text -
    - my token - : - abcd1234 + my token + : + + abcd1234 + +
    + onboarding.token.text +
    @@ -189,11 +220,15 @@ exports[`generates token 3`] = ` exports[`revokes token 1`] = `
    -
    - onboarding.token.text -
    - my token - : - abcd1234 + my token + : + + abcd1234 + +
    + onboarding.token.text +
    @@ -260,11 +312,15 @@ exports[`revokes token 1`] = ` exports[`revokes token 2`] = `
    -
    - onboarding.token.text -
    - my token - : - abcd1234 + my token + : + + abcd1234 + +
    + onboarding.token.text +
    @@ -326,11 +386,15 @@ exports[`revokes token 2`] = ` exports[`revokes token 3`] = `
    -
    - onboarding.token.text -
    -
    +
    + onboarding.token.text +
    diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/styles.css b/server/sonar-web/src/main/js/apps/tutorials/onboarding/styles.css index 870da20acde..ba588639db3 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/styles.css +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/styles.css @@ -1,8 +1,16 @@ + .onboarding { + min-height: calc(70vh - 60px); + } + .onboarding-step { position: relative; padding-left: 34px; } +.onboarding-step:not(.is-open):not(.is-finished) { + opacity: 0.4; +} + .onboarding-step .boxed-group-actions { height: 24px; line-height: 24px; @@ -16,16 +24,21 @@ height: 24px; line-height: 24px; border-radius: 24px; - background-color: #cdcdcd; + background-color: #b9b9b9; color: #fff; font-size: 14px; text-align: center; } -.onboarding-step-open .onboarding-step-number { +.onboarding-step.is-open .onboarding-step-number { background-color: #236a97; } +.onboarding-step.is-finished { + cursor: pointer; + outline: none; +} + .onboarding-command { position: relative; margin: 8px 0; diff --git a/server/sonar-web/src/main/less/components/modals.less b/server/sonar-web/src/main/less/components/modals.less index aaae44af086..4add5ce14c6 100644 --- a/server/sonar-web/src/main/less/components/modals.less +++ b/server/sonar-web/src/main/less/components/modals.less @@ -49,21 +49,11 @@ } .modal-large { - width: 90vw; - margin-left: -45vw; -} - -.modal-full-screen { - top: 30%; - width: 90vw; - height: 90vh; - margin-left: -45vw; - margin-top: -45vh; - border-radius: 2px; - - &.ReactModal__Content--after-open { - top: 50%; - } + width: ~"calc(100% - 40px)"; + max-width: 1280px; + min-width: 1040px; + margin-left: 0; + transform: translateX(-50%); } .modal-overlay, 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 7c830e984a6..6eb28fde7ae 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1091,9 +1091,10 @@ shortcuts.section.rules.deactivate=deactivate selected rule shortcuts.section.code=Code Page shortcuts.section.code.search=search components in the project scope -tutorials.onboarding=Onboarding Tutorial +tutorials.onboarding=Analyze a new project tutorials.skip=Skip this tutorial tutorials.finish=Finish this tutorial +tutorials.follow_later=Follow the tutorial later in the Help section #------------------------------------------------------------------------------ @@ -2957,7 +2958,7 @@ footer.web_api=Web API #------------------------------------------------------------------------------ onboarding.header=Welcome to SonarQube! onboarding.header.sonarcloud=Welcome to SonarCloud! -onboarding.header.description=Let's learn how to analyze your first public project. +onboarding.header.description=Let's analyze a new project. onboarding.token.header=Generate a token onboarding.token.text=We'll use it as a replacement of the user login. This will increase the security of your installation by not letting your analysis user's password going through your network.