Kaynağa Gözat

SONAR-9424 Show onboarding tutorial on first login

tags/6.5-M2
Stas Vilchik 7 yıl önce
ebeveyn
işleme
42a37b782d

+ 4
- 0
server/sonar-web/src/main/js/api/users.js Dosyayı Görüntüle

@@ -56,3 +56,7 @@ export function searchUsers(query: string, pageSize?: number) {
}
return getJSON(url, data);
}

export function skipOnboarding(): Promise<void> {
return post('/api/users/skip_onboarding_tutorial');
}

+ 6
- 2
server/sonar-web/src/main/js/app/components/help/GlobalHelp.js Dosyayı Görüntüle

@@ -28,7 +28,9 @@ import TutorialsHelp from './TutorialsHelp';
import { translate } from '../../../helpers/l10n';

type Props = {
currentUser: { isLoggedIn: boolean },
onClose: () => void,
onTutorialSelect: () => void,
sonarCloud?: boolean
};

@@ -60,7 +62,7 @@ export default class GlobalHelp extends React.PureComponent {
? <LinksHelpSonarCloud onClose={this.props.onClose} />
: <LinksHelp onClose={this.props.onClose} />;
case 'tutorials':
return <TutorialsHelp onClose={this.props.onClose} />;
return <TutorialsHelp onTutorialSelect={this.props.onTutorialSelect} />;
default:
return null;
}
@@ -80,7 +82,9 @@ export default class GlobalHelp extends React.PureComponent {

renderMenu = () => (
<ul className="side-tabs-menu">
{['shortcuts', 'tutorials', 'links'].map(this.renderMenuItem)}
{(this.props.currentUser.isLoggedIn
? ['shortcuts', 'tutorials', 'links']
: ['shortcuts', 'links']).map(this.renderMenuItem)}
</ul>
);


+ 8
- 4
server/sonar-web/src/main/js/app/components/help/TutorialsHelp.js Dosyayı Görüntüle

@@ -19,16 +19,20 @@
*/
// @flow
import React from 'react';
import { Link } from 'react-router';
import { translate } from '../../../helpers/l10n';

type Props = { onClose: () => void };
type Props = { onTutorialSelect: () => void };

export default function TutorialsHelp({ onTutorialSelect }: Props) {
const handleClick = (event: Event) => {
event.preventDefault();
onTutorialSelect();
};

export default function TutorialsHelp({ onClose }: Props) {
return (
<div>
<h2 className="spacer-top spacer-bottom">{translate('help.section.tutorials')}</h2>
<Link to="/tutorials/onboarding" onClick={onClose}>Onboarding Tutorial</Link>
<a href="#" onClick={handleClick}>{translate('tutorials.onboarding')}</a>
</div>
);
}

+ 19
- 1
server/sonar-web/src/main/js/app/components/help/__tests__/GlobalHelp-test.js Dosyayı Görüntüle

@@ -24,7 +24,13 @@ import GlobalHelp from '../GlobalHelp';
import { click } from '../../../../helpers/testUtils';

it('switches between tabs', () => {
const wrapper = shallow(<GlobalHelp onClose={jest.fn()} />);
const wrapper = shallow(
<GlobalHelp
currentUser={{ isLoggedIn: true }}
onClose={jest.fn()}
onTutorialSelect={jest.fn()}
/>
);
expect(wrapper.find('ShortcutsHelp')).toHaveLength(1);
clickOnSection(wrapper, 'links');
expect(wrapper.find('LinksHelp')).toHaveLength(1);
@@ -34,6 +40,18 @@ it('switches between tabs', () => {
expect(wrapper.find('ShortcutsHelp')).toHaveLength(1);
});

it('does not show tutorials for anonymous', () => {
expect(
shallow(
<GlobalHelp
currentUser={{ isLoggedIn: false }}
onClose={jest.fn()}
onTutorialSelect={jest.fn()}
/>
)
).toMatchSnapshot();
});

function clickOnSection(wrapper: Object, section: string) {
click(wrapper.find(`[data-section="${section}"]`), { currentTarget: { dataset: { section } } });
}

+ 72
- 0
server/sonar-web/src/main/js/app/components/help/__tests__/__snapshots__/GlobalHelp-test.js.snap Dosyayı Görüntüle

@@ -0,0 +1,72 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`does not show tutorials for anonymous 1`] = `
<Modal
ariaHideApp={true}
className="modal modal-medium"
closeTimeoutMS={0}
contentLabel="help"
isOpen={true}
onRequestClose={[Function]}
overlayClassName="modal-overlay"
parentSelector={[Function]}
portalClassName="ReactModalPortal"
shouldCloseOnOverlayClick={true}
>
<div
className="modal-head"
>
<h2>
help
</h2>
</div>
<div
className="side-tabs-layout"
>
<div
className="side-tabs-side"
>
<ul
className="side-tabs-menu"
>
<li>
<a
className="active"
data-section="shortcuts"
href="#"
onClick={[Function]}
>
help.section.shortcuts
</a>
</li>
<li>
<a
className=""
data-section="links"
href="#"
onClick={[Function]}
>
help.section.links
</a>
</li>
</ul>
</div>
<div
className="side-tabs-main"
>
<ShortcutsHelp />
</div>
</div>
<div
className="modal-foot"
>
<a
className="js-modal-close"
href="#"
onClick={[Function]}
>
close
</a>
</div>
</Modal>
`;

+ 35
- 14
server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js Dosyayı Görüntüle

@@ -17,6 +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 { connect } from 'react-redux';
import GlobalNavBranding from './GlobalNavBranding';
@@ -24,13 +25,30 @@ import GlobalNavMenu from './GlobalNavMenu';
import GlobalNavUserContainer from './GlobalNavUserContainer';
import Search from '../../search/Search';
import GlobalHelp from '../../help/GlobalHelp';
import HelpIcon from '../../../../components/icons-components/HelpIcon';
import OnboardingModal from '../../../../apps/tutorials/onboarding/OnboardingModal';
import { getCurrentUser, getAppState, getSettingValue } from '../../../../store/rootReducer';

type Props = {
appState: { organizationsEnabled: boolean },
currentUser: { isLoggedIn: boolean, showOnboardingTutorial: true },
sonarCloud: boolean
};

type State = {
helpOpen: boolean,
onboardingTutorialOpen: boolean
};

class GlobalNav extends React.PureComponent {
state = { helpOpen: false };
props: Props;
state: State = { helpOpen: false, onboardingTutorialOpen: false };

componentDidMount() {
window.addEventListener('keypress', this.onKeyPress);
if (this.props.currentUser.showOnboardingTutorial) {
this.openOnboardingTutorial();
}
}

componentWillUnmount() {
@@ -42,8 +60,7 @@ class GlobalNav extends React.PureComponent {
const code = e.keyCode || e.which;
const isInput = tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';
const isTriggerKey = code === 63;
const isModalOpen = document.querySelector('html').classList.contains('modal-open');
if (!isInput && !isModalOpen && isTriggerKey) {
if (!isInput && isTriggerKey) {
this.openHelp();
}
};
@@ -57,8 +74,11 @@ class GlobalNav extends React.PureComponent {

closeHelp = () => this.setState({ helpOpen: false });

openOnboardingTutorial = () => this.setState({ helpOpen: false, onboardingTutorialOpen: true });

closeOnboardingTutorial = () => this.setState({ onboardingTutorialOpen: false });

render() {
/* eslint-disable max-len */
return (
<nav className="navbar navbar-global page-container" id="global-navigation">
<div className="container">
@@ -67,17 +87,10 @@ class GlobalNav extends React.PureComponent {
<GlobalNavMenu {...this.props} />

<ul className="nav navbar-nav navbar-right">
<Search {...this.props} />
<Search appState={this.props.appState} currentUser={this.props.currentUser} />
<li>
<a className="navbar-help" onClick={this.handleHelpClick} href="#">
<svg width="16" height="16">
<g transform="matrix(0.0364583,0,0,0.0364583,1,-0.166667)">
<path
fill="#fff"
d="M224,344L224,296C224,293.667 223.25,291.75 221.75,290.25C220.25,288.75 218.333,288 216,288L168,288C165.667,288 163.75,288.75 162.25,290.25C160.75,291.75 160,293.667 160,296L160,344C160,346.333 160.75,348.25 162.25,349.75C163.75,351.25 165.667,352 168,352L216,352C218.333,352 220.25,351.25 221.75,349.75C223.25,348.25 224,346.333 224,344ZM288,176C288,161.333 283.375,147.75 274.125,135.25C264.875,122.75 253.333,113.083 239.5,106.25C225.667,99.417 211.5,96 197,96C156.5,96 125.583,113.75 104.25,149.25C101.75,153.25 102.417,156.75 106.25,159.75L139.25,184.75C140.417,185.75 142,186.25 144,186.25C146.667,186.25 148.75,185.25 150.25,183.25C159.083,171.917 166.25,164.25 171.75,160.25C177.417,156.25 184.583,154.25 193.25,154.25C201.25,154.25 208.375,156.417 214.625,160.75C220.875,165.083 224,170 224,175.5C224,181.833 222.333,186.917 219,190.75C215.667,194.583 210,198.333 202,202C191.5,206.667 181.875,213.875 173.125,223.625C164.375,233.375 160,243.833 160,255L160,264C160,266.333 160.75,268.25 162.25,269.75C163.75,271.25 165.667,272 168,272L216,272C218.333,272 220.25,271.25 221.75,269.75C223.25,268.25 224,266.333 224,264C224,260.833 225.792,256.708 229.375,251.625C232.958,246.542 237.5,242.417 243,239.25C248.333,236.25 252.417,233.875 255.25,232.125C258.083,230.375 261.917,227.458 266.75,223.375C271.583,219.292 275.292,215.292 277.875,211.375C280.458,207.458 282.792,202.417 284.875,196.25C286.958,190.083 288,183.333 288,176ZM384,224C384,258.833 375.417,290.958 358.25,320.375C341.083,349.792 317.792,373.083 288.375,390.25C258.958,407.417 226.833,416 192,416C157.167,416 125.042,407.417 95.625,390.25C66.208,373.083 42.917,349.792 25.75,320.375C8.583,290.958 0,258.833 0,224C0,189.167 8.583,157.042 25.75,127.625C42.917,98.208 66.208,74.917 95.625,57.75C125.042,40.583 157.167,32 192,32C226.833,32 258.958,40.583 288.375,57.75C317.792,74.917 341.083,98.208 358.25,127.625C375.417,157.042 384,189.167 384,224Z"
/>
</g>
</svg>
<HelpIcon />
</a>
</li>
<GlobalNavUserContainer {...this.props} />
@@ -85,7 +98,15 @@ class GlobalNav extends React.PureComponent {
</div>

{this.state.helpOpen &&
<GlobalHelp onClose={this.closeHelp} sonarCloud={this.props.sonarCloud} />}
<GlobalHelp
currentUser={this.props.currentUser}
onClose={this.closeHelp}
onTutorialSelect={this.openOnboardingTutorial}
sonarCloud={this.props.sonarCloud}
/>}

{this.state.onboardingTutorialOpen &&
<OnboardingModal onClose={this.closeOnboardingTutorial} />}
</nav>
);
}

+ 0
- 2
server/sonar-web/src/main/js/app/utils/startReactApp.js Dosyayı Görüntüle

@@ -63,7 +63,6 @@ import qualityProfilesRoutes from '../../apps/quality-profiles/routes';
import sessionsRoutes from '../../apps/sessions/routes';
import settingsRoutes from '../../apps/settings/routes';
import systemRoutes from '../../apps/system/routes';
import tutorialRoutes from '../../apps/tutorials/routes';
import updateCenterRoutes from '../../apps/update-center/routes';
import usersRoutes from '../../apps/users/routes';
import webAPIRoutes from '../../apps/web-api/routes';
@@ -161,7 +160,6 @@ const startReactApp = () => {
<Route path="quality_gates" childRoutes={qualityGatesRoutes} />
<Route path="portfolios" component={PortfoliosPage} />
<Route path="profiles" childRoutes={qualityProfilesRoutes} />
<Route path="tutorials" childRoutes={tutorialRoutes} />
<Route path="web_api" childRoutes={webAPIRoutes} />

<Route component={ProjectContainer}>

+ 4
- 0
server/sonar-web/src/main/js/apps/tutorials/onboarding/AnalysisStep.js Dosyayı Görüntüle

@@ -31,6 +31,8 @@ import Other from './commands/Other';
import { translate } from '../../../helpers/l10n';

type Props = {|
onFinish: () => void,
onReset: () => void,
open: boolean,
organization?: string,
sonarCloud: boolean,
@@ -48,10 +50,12 @@ export default class AnalysisStep extends React.PureComponent {

handleLanguageSelect = (result?: Result) => {
this.setState({ result });
this.props.onFinish();
};

handleLanguageReset = () => {
this.setState({ result: undefined });
this.props.onReset();
};

getHost = () => window.location.origin + window.baseUrl;

+ 53
- 1
server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js Dosyayı Görüntüle

@@ -22,37 +22,51 @@ import React from 'react';
import TokenStep from './TokenStep';
import OrganizationStep from './OrganizationStep';
import AnalysisStep from './AnalysisStep';
import { skipOnboarding } from '../../../api/users';
import { translate } from '../../../helpers/l10n';
import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
import './styles.css';

type Props = {
currentUser: { login: string, isLoggedIn: boolean },
onSkip: () => void,
organizationsEnabled: boolean,
sonarCloud: boolean
};

type State = {
finished: boolean,
organization?: string,
skipping: boolean,
step: string,
token?: string
};

export default class Onboarding extends React.PureComponent {
mounted: boolean;
props: Props;
state: State;

constructor(props: Props) {
super(props);
this.state = { step: props.organizationsEnabled ? 'organization' : 'token' };
this.state = {
finished: false,
skipping: false,
step: props.organizationsEnabled ? 'organization' : 'token'
};
}

componentDidMount() {
this.mounted = true;
if (!this.props.currentUser.isLoggedIn) {
handleRequiredAuthentication();
}
}

componentWillUnmount() {
this.mounted = false;
}

handleTokenDone = (token: string) => {
this.setState({ step: 'analysis', token });
};
@@ -61,6 +75,27 @@ export default class Onboarding extends React.PureComponent {
this.setState({ organization, step: 'token' });
};

handleSkipClick = (event: Event) => {
event.preventDefault();
this.setState({ skipping: true });
skipOnboarding().then(
() => {
if (this.mounted) {
this.props.onSkip();
}
},
() => {
if (this.mounted) {
this.setState({ skipping: false });
}
}
);
};

handleFinish = () => this.setState({ finished: true });

handleReset = () => this.setState({ finished: false });

render() {
if (!this.props.currentUser.isLoggedIn) {
return null;
@@ -77,6 +112,13 @@ export default class Onboarding extends React.PureComponent {
<h1 className="page-title">
{translate(sonarCloud ? 'onboarding.header.sonarcloud' : 'onboarding.header')}
</h1>
<div className="page-actions">
{this.state.skipping
? <i className="spinner" />
: <a className="js-skip text-muted" href="#" onClick={this.handleSkipClick}>
{translate('tutorials.skip')}
</a>}
</div>
<div className="page-description">
{translate('onboarding.header.description')}
</div>
@@ -97,12 +139,22 @@ export default class Onboarding extends React.PureComponent {
/>

<AnalysisStep
onFinish={this.handleFinish}
onReset={this.handleReset}
organization={this.state.organization}
open={step === 'analysis'}
sonarCloud={sonarCloud}
stepNumber={stepNumber}
token={token}
/>

{this.state.finished &&
!this.state.skipping &&
<footer className="text-right">
<a className="button" href="#" onClick={this.handleSkipClick}>
{translate('tutorials.finish')}
</a>
</footer>}
</div>
);
}

+ 69
- 0
server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.js Dosyayı Görüntüle

@@ -0,0 +1,69 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 Modal from 'react-modal';
import { translate } from '../../../helpers/l10n';

type Props = {
onClose: () => void
};

type State = {
OnboardingContainer?: Object
};

export default class OnboardingModal extends React.PureComponent {
mounted: boolean;
props: Props;
state: State = {};

componentDidMount() {
this.mounted = true;
// $FlowFixMe
require.ensure([], require => {
this.receiveComponent(require('./OnboardingContainer').default);
});
}

componentWillUnmount() {
this.mounted = false;
}

receiveComponent = (OnboardingContainer: Object) => {
if (this.mounted) {
this.setState({ OnboardingContainer });
}
};

render() {
const { OnboardingContainer } = this.state;

return (
<Modal
isOpen={true}
contentLabel={translate('tutorials.onboarding')}
className="modal modal-full-screen"
overlayClassName="modal-overlay">
{OnboardingContainer != null && <OnboardingContainer onSkip={this.props.onClose} />}
</Modal>
);
}
}

+ 85
- 0
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/Onboarding-test.js Dosyayı Görüntüle

@@ -0,0 +1,85 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 { shallow, mount } from 'enzyme';
import Onboarding from '../Onboarding';
import { click, doAsync } from '../../../../helpers/testUtils';

jest.mock('../../../../api/users', () => ({
skipOnboarding: () => Promise.resolve()
}));

const currentUser = { login: 'admin', isLoggedIn: true };

it('guides for on-premise', () => {
const wrapper = shallow(
<Onboarding
currentUser={currentUser}
onSkip={jest.fn()}
organizationsEnabled={false}
sonarCloud={false}
/>
);
expect(wrapper).toMatchSnapshot();

// $FlowFixMe
wrapper.instance().handleTokenDone('abcd1234');
wrapper.update();
expect(wrapper).toMatchSnapshot();
});

it('guides for sonarcloud', () => {
const wrapper = shallow(
<Onboarding
currentUser={currentUser}
onSkip={jest.fn()}
organizationsEnabled={true}
sonarCloud={true}
/>
);
expect(wrapper).toMatchSnapshot();

// $FlowFixMe
wrapper.instance().handleOrganizationDone('my-org');
wrapper.update();
expect(wrapper).toMatchSnapshot();

// $FlowFixMe
wrapper.instance().handleTokenDone('abcd1234');
wrapper.update();
expect(wrapper).toMatchSnapshot();
});

it('skips', () => {
const onSkip = jest.fn();
const wrapper = mount(
<Onboarding
currentUser={currentUser}
onSkip={onSkip}
organizationsEnabled={false}
sonarCloud={false}
/>
);
click(wrapper.find('.js-skip'));
return doAsync(() => {
expect(onSkip).toBeCalled();
});
});

+ 258
- 0
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap Dosyayı Görüntüle

@@ -0,0 +1,258 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`guides for on-premise 1`] = `
<div
className="page page-limited"
>
<header
className="page-header"
>
<h1
className="page-title"
>
onboarding.header
</h1>
<div
className="page-actions"
>
<a
className="js-skip text-muted"
href="#"
onClick={[Function]}
>
tutorials.skip
</a>
</div>
<div
className="page-description"
>
onboarding.header.description
</div>
</header>
<TokenStep
onContinue={[Function]}
open={true}
stepNumber={1}
/>
<AnalysisStep
onFinish={[Function]}
onReset={[Function]}
open={false}
sonarCloud={false}
stepNumber={2}
/>
</div>
`;

exports[`guides for on-premise 2`] = `
<div
className="page page-limited"
>
<header
className="page-header"
>
<h1
className="page-title"
>
onboarding.header
</h1>
<div
className="page-actions"
>
<a
className="js-skip text-muted"
href="#"
onClick={[Function]}
>
tutorials.skip
</a>
</div>
<div
className="page-description"
>
onboarding.header.description
</div>
</header>
<TokenStep
onContinue={[Function]}
open={false}
stepNumber={1}
/>
<AnalysisStep
onFinish={[Function]}
onReset={[Function]}
open={true}
sonarCloud={false}
stepNumber={2}
token="abcd1234"
/>
</div>
`;

exports[`guides for sonarcloud 1`] = `
<div
className="page page-limited"
>
<header
className="page-header"
>
<h1
className="page-title"
>
onboarding.header.sonarcloud
</h1>
<div
className="page-actions"
>
<a
className="js-skip text-muted"
href="#"
onClick={[Function]}
>
tutorials.skip
</a>
</div>
<div
className="page-description"
>
onboarding.header.description
</div>
</header>
<OrganizationStep
currentUser={
Object {
"isLoggedIn": true,
"login": "admin",
}
}
onContinue={[Function]}
open={true}
stepNumber={1}
/>
<TokenStep
onContinue={[Function]}
open={false}
stepNumber={2}
/>
<AnalysisStep
onFinish={[Function]}
onReset={[Function]}
open={false}
sonarCloud={true}
stepNumber={3}
/>
</div>
`;

exports[`guides for sonarcloud 2`] = `
<div
className="page page-limited"
>
<header
className="page-header"
>
<h1
className="page-title"
>
onboarding.header.sonarcloud
</h1>
<div
className="page-actions"
>
<a
className="js-skip text-muted"
href="#"
onClick={[Function]}
>
tutorials.skip
</a>
</div>
<div
className="page-description"
>
onboarding.header.description
</div>
</header>
<OrganizationStep
currentUser={
Object {
"isLoggedIn": true,
"login": "admin",
}
}
onContinue={[Function]}
open={false}
stepNumber={1}
/>
<TokenStep
onContinue={[Function]}
open={true}
stepNumber={2}
/>
<AnalysisStep
onFinish={[Function]}
onReset={[Function]}
open={false}
organization="my-org"
sonarCloud={true}
stepNumber={3}
/>
</div>
`;

exports[`guides for sonarcloud 3`] = `
<div
className="page page-limited"
>
<header
className="page-header"
>
<h1
className="page-title"
>
onboarding.header.sonarcloud
</h1>
<div
className="page-actions"
>
<a
className="js-skip text-muted"
href="#"
onClick={[Function]}
>
tutorials.skip
</a>
</div>
<div
className="page-description"
>
onboarding.header.description
</div>
</header>
<OrganizationStep
currentUser={
Object {
"isLoggedIn": true,
"login": "admin",
}
}
onContinue={[Function]}
open={false}
stepNumber={1}
/>
<TokenStep
onContinue={[Function]}
open={false}
stepNumber={2}
/>
<AnalysisStep
onFinish={[Function]}
onReset={[Function]}
open={true}
organization="my-org"
sonarCloud={true}
stepNumber={3}
token="abcd1234"
/>
</div>
`;

+ 0
- 31
server/sonar-web/src/main/js/apps/tutorials/routes.js Dosyayı Görüntüle

@@ -1,31 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
const routes = [
{
path: 'onboarding',
getComponent(_, callback) {
require.ensure([], require => {
callback(null, require('./onboarding/OnboardingContainer').default);
});
}
}
];

export default routes;

+ 37
- 0
server/sonar-web/src/main/js/components/icons-components/HelpIcon.js Dosyayı Görüntüle

@@ -0,0 +1,37 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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';

type Props = { className?: string, size?: number };

export default function HelpIcon({ className, size = 16 }: Props) {
/* eslint-disable max-len */
return (
<svg className={className} viewBox="0 0 16 16" width={size} height={size}>
<g transform="matrix(0.0364583,0,0,0.0364583,1,-0.166667)">
<path
fill="#fff"
d="M224,344L224,296C224,293.667 223.25,291.75 221.75,290.25C220.25,288.75 218.333,288 216,288L168,288C165.667,288 163.75,288.75 162.25,290.25C160.75,291.75 160,293.667 160,296L160,344C160,346.333 160.75,348.25 162.25,349.75C163.75,351.25 165.667,352 168,352L216,352C218.333,352 220.25,351.25 221.75,349.75C223.25,348.25 224,346.333 224,344ZM288,176C288,161.333 283.375,147.75 274.125,135.25C264.875,122.75 253.333,113.083 239.5,106.25C225.667,99.417 211.5,96 197,96C156.5,96 125.583,113.75 104.25,149.25C101.75,153.25 102.417,156.75 106.25,159.75L139.25,184.75C140.417,185.75 142,186.25 144,186.25C146.667,186.25 148.75,185.25 150.25,183.25C159.083,171.917 166.25,164.25 171.75,160.25C177.417,156.25 184.583,154.25 193.25,154.25C201.25,154.25 208.375,156.417 214.625,160.75C220.875,165.083 224,170 224,175.5C224,181.833 222.333,186.917 219,190.75C215.667,194.583 210,198.333 202,202C191.5,206.667 181.875,213.875 173.125,223.625C164.375,233.375 160,243.833 160,255L160,264C160,266.333 160.75,268.25 162.25,269.75C163.75,271.25 165.667,272 168,272L216,272C218.333,272 220.25,271.25 221.75,269.75C223.25,268.25 224,266.333 224,264C224,260.833 225.792,256.708 229.375,251.625C232.958,246.542 237.5,242.417 243,239.25C248.333,236.25 252.417,233.875 255.25,232.125C258.083,230.375 261.917,227.458 266.75,223.375C271.583,219.292 275.292,215.292 277.875,211.375C280.458,207.458 282.792,202.417 284.875,196.25C286.958,190.083 288,183.333 288,176ZM384,224C384,258.833 375.417,290.958 358.25,320.375C341.083,349.792 317.792,373.083 288.375,390.25C258.958,407.417 226.833,416 192,416C157.167,416 125.042,407.417 95.625,390.25C66.208,373.083 42.917,349.792 25.75,320.375C8.583,290.958 0,258.833 0,224C0,189.167 8.583,157.042 25.75,127.625C42.917,98.208 66.208,74.917 95.625,57.75C125.042,40.583 157.167,32 192,32C226.833,32 258.958,40.583 288.375,57.75C317.792,74.917 341.083,98.208 358.25,127.625C375.417,157.042 384,189.167 384,224Z"
/>
</g>
</svg>
);
}

+ 13
- 0
server/sonar-web/src/main/less/components/modals.less Dosyayı Görüntüle

@@ -53,6 +53,19 @@
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%;
}
}

.modal-overlay,
.ReactModal__Overlay {
position: fixed;

+ 4
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Dosyayı Görüntüle

@@ -1091,6 +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.skip=Skip this tutorial
tutorials.finish=Finish this tutorial


#------------------------------------------------------------------------------
#

Loading…
İptal
Kaydet