From: Stas Vilchik Date: Mon, 12 Jun 2017 10:50:27 +0000 (-0700) Subject: UI: SONAR-9355 Create onboarding tutorial (#2137) X-Git-Tag: 6.5-M2~131 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=fbc932a882b6dec72900f5242d0cead7ff03e4b2;p=sonarqube.git UI: SONAR-9355 Create onboarding tutorial (#2137) --- diff --git a/it/it-tests/src/test/java/pageobjects/settings/SettingsPage.java b/it/it-tests/src/test/java/pageobjects/settings/SettingsPage.java index dcb3794ebeb..704ba3054ae 100644 --- a/it/it-tests/src/test/java/pageobjects/settings/SettingsPage.java +++ b/it/it-tests/src/test/java/pageobjects/settings/SettingsPage.java @@ -35,7 +35,7 @@ public class SettingsPage { } public SettingsPage assertMenuContains(String categoryName) { - $(".settings-menu").$(By.linkText(categoryName)).shouldBe(visible); + $(".side-tabs-menu").$(By.linkText(categoryName)).shouldBe(visible); return this; } @@ -50,7 +50,7 @@ public class SettingsPage { } public SettingsPage openCategory(String categoryName) { - $(".settings-menu").$(By.linkText(categoryName)).click(); + $(".side-tabs-menu").$(By.linkText(categoryName)).click(); return this; } diff --git a/server/sonar-web/src/main/js/api/components.js b/server/sonar-web/src/main/js/api/components.js index 867100c4fae..e024ce947f5 100644 --- a/server/sonar-web/src/main/js/api/components.js +++ b/server/sonar-web/src/main/js/api/components.js @@ -19,6 +19,7 @@ */ // @flow import { getJSON, postJSON, post } from '../helpers/request'; +import throwGlobalError from '../app/utils/throwGlobalError'; export function getComponents(data?: Object) { const url = '/api/projects/search'; @@ -55,7 +56,7 @@ export function createProject( } ) { const url = '/api/projects/create'; - return postJSON(url, data); + return postJSON(url, data).catch(throwGlobalError); } export function searchProjectTags(data?: { ps?: number, q?: string }) { diff --git a/server/sonar-web/src/main/js/api/organizations.js b/server/sonar-web/src/main/js/api/organizations.js index 097a17ce5cb..7ca0263ba81 100644 --- a/server/sonar-web/src/main/js/api/organizations.js +++ b/server/sonar-web/src/main/js/api/organizations.js @@ -20,6 +20,7 @@ // @flow import { getJSON, post, postJSON } from '../helpers/request'; import type { Organization } from '../store/organizations/duck'; +import throwGlobalError from '../app/utils/throwGlobalError'; export const getOrganizations = (organizations?: Array) => { const data = {}; @@ -44,7 +45,9 @@ type GetOrganizationNavigation = { }; export const getOrganization = (key: string): Promise => { - return getOrganizations([key]).then(r => r.organizations.find(o => o.key === key)); + return getOrganizations([key]) + .then(r => r.organizations.find(o => o.key === key)) + .catch(throwGlobalError); }; export const getOrganizationNavigation = (key: string): Promise => { @@ -52,12 +55,13 @@ export const getOrganizationNavigation = (key: string): Promise => - postJSON('/api/organizations/create', fields).then(r => r.organization); + postJSON('/api/organizations/create', fields).then(r => r.organization, throwGlobalError); export const updateOrganization = (key: string, changes: {}) => post('/api/organizations/update', { key, ...changes }); -export const deleteOrganization = (key: string) => post('/api/organizations/delete', { key }); +export const deleteOrganization = (key: string) => + post('/api/organizations/delete', { key }).catch(throwGlobalError); export const searchMembers = ( data: { organization?: string, p?: number, ps?: number, q?: string, selected?: string } diff --git a/server/sonar-web/src/main/js/api/user-tokens.js b/server/sonar-web/src/main/js/api/user-tokens.js index 4b3b97e2bba..3947b07f1be 100644 --- a/server/sonar-web/src/main/js/api/user-tokens.js +++ b/server/sonar-web/src/main/js/api/user-tokens.js @@ -17,14 +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 { getJSON, postJSON, post } from '../helpers/request'; +import throwGlobalError from '../app/utils/throwGlobalError'; /** * List tokens for given user login * @param {string} login * @returns {Promise} */ -export function getTokens(login) { +export function getTokens(login: string) { const url = '/api/user_tokens/search'; const data = { login }; return getJSON(url, data).then(r => r.userTokens); @@ -36,10 +38,16 @@ export function getTokens(login) { * @param {string} tokenName * @returns {Promise} */ -export function generateToken(userLogin, tokenName) { +export function generateToken( + tokenName: string, + userLogin?: string +): Promise<{ name: string, token: string }> { const url = '/api/user_tokens/generate'; - const data = { login: userLogin, name: tokenName }; - return postJSON(url, data); + const data: { [string]: string } = { name: tokenName }; + if (userLogin) { + data.login = userLogin; + } + return postJSON(url, data).catch(throwGlobalError); } /** @@ -48,8 +56,11 @@ export function generateToken(userLogin, tokenName) { * @param {string} tokenName * @returns {Promise} */ -export function revokeToken(userLogin, tokenName) { +export function revokeToken(tokenName: string, userLogin?: string) { const url = '/api/user_tokens/revoke'; - const data = { login: userLogin, name: tokenName }; - return post(url, data); + const data: { [string]: string } = { name: tokenName }; + if (userLogin) { + data.login = userLogin; + } + return post(url, data).catch(throwGlobalError); } diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooterForSonarQubeDotCom.js b/server/sonar-web/src/main/js/app/components/GlobalFooterForSonarQubeDotCom.js index 755a34e933d..ab8ec94f97a 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalFooterForSonarQubeDotCom.js +++ b/server/sonar-web/src/main/js/app/components/GlobalFooterForSonarQubeDotCom.js @@ -43,7 +43,7 @@ export default function GlobalFooterForSonarQubeDotCom() { {' - '} {translate('footer.help')} {' - '} - {{translate('footer.about')}} + {translate('footer.about')} ); diff --git a/server/sonar-web/src/main/js/app/components/help/GlobalHelp.js b/server/sonar-web/src/main/js/app/components/help/GlobalHelp.js new file mode 100644 index 00000000000..553f5e3cc71 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/help/GlobalHelp.js @@ -0,0 +1,118 @@ +/* + * 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 classNames from 'classnames'; +import LinksHelp from './LinksHelp'; +import LinksHelpSonarCloud from './LinksHelpSonarCloud'; +import ShortcutsHelp from './ShortcutsHelp'; +import TutorialsHelp from './TutorialsHelp'; +import { translate } from '../../../helpers/l10n'; + +type Props = { + onClose: () => void, + sonarCloud?: boolean +}; + +type State = { + section: string +}; + +export default class GlobalHelp extends React.PureComponent { + props: Props; + state: State = { section: 'shortcuts' }; + + handleCloseClick = (event: Event) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleSectionClick = (event: Event & { currentTarget: HTMLElement }) => { + event.preventDefault(); + const { section } = event.currentTarget.dataset; + this.setState({ section }); + }; + + renderSection = () => { + switch (this.state.section) { + case 'shortcuts': + return ; + case 'links': + return this.props.sonarCloud + ? + : ; + case 'tutorials': + return ; + default: + return null; + } + }; + + renderMenuItem = (section: string) => ( +
  • + + {translate('help.section', section)} + +
  • + ); + + renderMenu = () => ( +
      + {['shortcuts', 'tutorials', 'links'].map(this.renderMenuItem)} +
    + ); + + render() { + return ( + + +
    +

    {translate('help')}

    +
    + +
    +
    + {this.renderMenu()} +
    +
    + {this.renderSection()} +
    +
    + + + +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/app/components/help/LinksHelp.js b/server/sonar-web/src/main/js/app/components/help/LinksHelp.js new file mode 100644 index 00000000000..e76ef2c6bb6 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/help/LinksHelp.js @@ -0,0 +1,50 @@ +/* + * 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 { Link } from 'react-router'; +import { translate } from '../../../helpers/l10n'; + +type Props = { onClose: () => void }; + +export default function LinksHelp({ onClose }: Props) { + return ( +
    +

    {translate('help.section.links')}

    + + {translate('footer.community')}{' - '} + + {translate('footer.documentation')} + + {' - '} + + {translate('footer.support')} + + {' - '} + + {translate('footer.plugins')} + + {' - '} + {translate('footer.web_api')} + {' - '} + {translate('footer.about')} +
    + ); +} diff --git a/server/sonar-web/src/main/js/app/components/help/LinksHelpSonarCloud.js b/server/sonar-web/src/main/js/app/components/help/LinksHelpSonarCloud.js new file mode 100644 index 00000000000..6efdac91b50 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/help/LinksHelpSonarCloud.js @@ -0,0 +1,45 @@ +/* + * 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 { Link } from 'react-router'; +import { translate } from '../../../helpers/l10n'; + +type Props = { onClose: () => void }; + +export default function LinksHelpSonarCloud({ onClose }: Props) { + return ( +
    +

    {translate('help.section.links')}

    + + {translate('footer.news')} + {' - '} + {translate('footer.terms')} + {' - '} + {translate('footer.twitter')} + {' - '} + {translate('footer.get_started')} + {' - '} + {translate('footer.help')} + {' - '} + {translate('footer.about')} +
    + ); +} diff --git a/server/sonar-web/src/main/js/app/components/help/ShortcutsHelp.js b/server/sonar-web/src/main/js/app/components/help/ShortcutsHelp.js new file mode 100644 index 00000000000..1787524f5dc --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/help/ShortcutsHelp.js @@ -0,0 +1,135 @@ +/* + * 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 { translate } from '../../../helpers/l10n'; + +export default function ShortcutsHelp() { + return ( +
    +

    {translate('help.section.shortcuts')}

    + +
    +
    +
    +

    {translate('shortcuts.section.global')}

    +
      +
    • + s + {translate('shortcuts.section.global.search')} +
    • +
    • + ? + {translate('shortcuts.section.global.shortcuts')} +
    • +
    +
    + +

    {translate('shortcuts.section.rules')}

    +
      +
    • + ↑ + ↓ + {translate('shortcuts.section.rules.navigate_between_rules')} +
    • +
    • + → + {translate('shortcuts.section.rules.open_details')} +
    • +
    • + ← + {translate('shortcuts.section.rules.return_to_list')} +
    • +
    • + a + {translate('shortcuts.section.rules.activate')} +
    • +
    • + d + {translate('shortcuts.section.rules.deactivate')} +
    • +
    +
    + +
    +

    {translate('shortcuts.section.issues')}

    +
      +
    • + ↑ + ↓ + {translate('shortcuts.section.issues.navigate_between_issues')} +
    • +
    • + → + {translate('shortcuts.section.issues.open_details')} +
    • +
    • + ← + {translate('shortcuts.section.issues.return_to_list')} +
    • +
    • + alt + + + ↑ + ↓ + {translate('issues.to_navigate_issue_locations')} +
    • +
    • + alt + + + ← + → + {translate('issues.to_switch_flows')} +
    • +
    • + f + {translate('shortcuts.section.issue.do_transition')} +
    • +
    • + a + {translate('shortcuts.section.issue.assign')} +
    • +
    • + m + {translate('shortcuts.section.issue.assign_to_me')} +
    • +
    • + i + {translate('shortcuts.section.issue.change_severity')} +
    • +
    • + c + {translate('shortcuts.section.issue.comment')} +
    • +
    • + ctrl + enter + {translate('shortcuts.section.issue.submit_comment')} +
    • +
    • + t + {translate('shortcuts.section.issue.change_tags')} +
    • +
    +
    +
    +
    + ); +} diff --git a/server/sonar-web/src/main/js/app/components/help/TutorialsHelp.js b/server/sonar-web/src/main/js/app/components/help/TutorialsHelp.js new file mode 100644 index 00000000000..112b6393361 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/help/TutorialsHelp.js @@ -0,0 +1,34 @@ +/* + * 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 { Link } from 'react-router'; +import { translate } from '../../../helpers/l10n'; + +type Props = { onClose: () => void }; + +export default function TutorialsHelp({ onClose }: Props) { + return ( +
    +

    {translate('help.section.tutorials')}

    + Onboarding Tutorial +
    + ); +} diff --git a/server/sonar-web/src/main/js/app/components/help/__tests__/GlobalHelp-test.js b/server/sonar-web/src/main/js/app/components/help/__tests__/GlobalHelp-test.js new file mode 100644 index 00000000000..ff0a3dd6bef --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/help/__tests__/GlobalHelp-test.js @@ -0,0 +1,39 @@ +/* + * 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 } from 'enzyme'; +import GlobalHelp from '../GlobalHelp'; +import { click } from '../../../../helpers/testUtils'; + +it('switches between tabs', () => { + const wrapper = shallow(); + expect(wrapper.find('ShortcutsHelp')).toHaveLength(1); + clickOnSection(wrapper, 'links'); + expect(wrapper.find('LinksHelp')).toHaveLength(1); + clickOnSection(wrapper, 'tutorials'); + expect(wrapper.find('TutorialsHelp')).toHaveLength(1); + clickOnSection(wrapper, 'shortcuts'); + expect(wrapper.find('ShortcutsHelp')).toHaveLength(1); +}); + +function clickOnSection(wrapper: Object, section: string) { + click(wrapper.find(`[data-section="${section}"]`), { currentTarget: { dataset: { section } } }); +} 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 2e2659d7125..8d432d498a0 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 @@ -23,8 +23,8 @@ import GlobalNavBranding from './GlobalNavBranding'; import GlobalNavMenu from './GlobalNavMenu'; import GlobalNavUserContainer from './GlobalNavUserContainer'; import Search from '../../search/Search'; -import ShortcutsHelp from './ShortcutsHelp'; -import { getCurrentUser, getAppState } from '../../../../store/rootReducer'; +import GlobalHelp from '../../help/GlobalHelp'; +import { getCurrentUser, getAppState, getSettingValue } from '../../../../store/rootReducer'; class GlobalNav extends React.PureComponent { state = { helpOpen: false }; @@ -84,15 +84,21 @@ class GlobalNav extends React.PureComponent { - {this.state.helpOpen && } + {this.state.helpOpen && + } ); } } -const mapStateToProps = state => ({ - currentUser: getCurrentUser(state), - appState: getAppState(state) -}); +const mapStateToProps = state => { + const sonarCloudSetting = getSettingValue(state, 'sonar.lf.sonarqube.com.enabled'); + + return { + currentUser: getCurrentUser(state), + appState: getAppState(state), + sonarCloud: sonarCloudSetting != null && sonarCloudSetting.value === 'true' + }; +}; export default connect(mapStateToProps)(GlobalNav); diff --git a/server/sonar-web/src/main/js/app/components/nav/global/ShortcutsHelp.js b/server/sonar-web/src/main/js/app/components/nav/global/ShortcutsHelp.js deleted file mode 100644 index 273b370f17a..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/global/ShortcutsHelp.js +++ /dev/null @@ -1,188 +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. - */ -// @flow -import React from 'react'; -import Modal from 'react-modal'; -import { Link } from 'react-router'; -import { translate } from '../../../../helpers/l10n'; - -type Props = { - onClose: () => void -}; - -export default class ShortcutsHelp extends React.PureComponent { - props: Props; - - handleCloseClick = (event: Event) => { - event.preventDefault(); - this.props.onClose(); - }; - - render() { - return ( - - -
    -

    {translate('help')}

    -
    - -
    -
    - {translate('footer.community')}{' - '} - - {translate('footer.documentation')} - - {' - '} - - {translate('footer.support')} - - {' - '} - - {translate('footer.plugins')} - - {' - '} - {translate('footer.web_api')} - {' - '} - {translate('footer.about')} -
    - -

    {translate('shortcuts.modal_title')}

    - -
    -
    -
    -

    {translate('shortcuts.section.global')}

    -
      -
    • - s - {translate('shortcuts.section.global.search')} -
    • -
    • - ? - {translate('shortcuts.section.global.shortcuts')} -
    • -
    -
    - -

    {translate('shortcuts.section.rules')}

    -
      -
    • - ↑ - ↓ - {translate('shortcuts.section.rules.navigate_between_rules')} -
    • -
    • - → - {translate('shortcuts.section.rules.open_details')} -
    • -
    • - ← - {translate('shortcuts.section.rules.return_to_list')} -
    • -
    • - a - {translate('shortcuts.section.rules.activate')} -
    • -
    • - d - {translate('shortcuts.section.rules.deactivate')} -
    • -
    -
    - -
    -

    {translate('shortcuts.section.issues')}

    -
      -
    • - ↑ - ↓ - {translate('shortcuts.section.issues.navigate_between_issues')} -
    • -
    • - → - {translate('shortcuts.section.issues.open_details')} -
    • -
    • - ← - {translate('shortcuts.section.issues.return_to_list')} -
    • -
    • - alt - + - ↑ - ↓ - {translate('issues.to_navigate_issue_locations')} -
    • -
    • - alt - + - ← - → - {translate('issues.to_switch_flows')} -
    • -
    • - f - {translate('shortcuts.section.issue.do_transition')} -
    • -
    • - a - {translate('shortcuts.section.issue.assign')} -
    • -
    • - m - {translate('shortcuts.section.issue.assign_to_me')} -
    • -
    • - i - {translate('shortcuts.section.issue.change_severity')} -
    • -
    • - c - {translate('shortcuts.section.issue.comment')} -
    • -
    • - ctrl - enter - {translate('shortcuts.section.issue.submit_comment')} -
    • -
    • - t - {translate('shortcuts.section.issue.change_tags')} -
    • -
    -
    -
    -
    - - - -
    - ); - } -} diff --git a/server/sonar-web/src/main/js/app/styles/boxed-group.css b/server/sonar-web/src/main/js/app/styles/boxed-group.css index 24c56850676..f41ae1927d6 100644 --- a/server/sonar-web/src/main/js/app/styles/boxed-group.css +++ b/server/sonar-web/src/main/js/app/styles/boxed-group.css @@ -34,6 +34,8 @@ } .boxed-group-actions { + position: relative; + z-index: 12; float: right; margin-top: 15px; margin-right: 20px; 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 5b4a0d37e15..f0793da6798 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.js +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js @@ -63,6 +63,7 @@ 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'; @@ -159,6 +160,7 @@ const startReactApp = () => { + diff --git a/server/sonar-web/src/main/js/app/utils/throwGlobalError.js b/server/sonar-web/src/main/js/app/utils/throwGlobalError.js new file mode 100644 index 00000000000..75cedec4e68 --- /dev/null +++ b/server/sonar-web/src/main/js/app/utils/throwGlobalError.js @@ -0,0 +1,28 @@ +/* + * 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 getStore from './getStore'; +import { onFail } from '../../store/rootActions'; + +export default function throwGlobalError(error: Object) { + const store = getStore(); + onFail(store.dispatch)(error); + return Promise.reject(); +} diff --git a/server/sonar-web/src/main/js/apps/account/tokens-view.js b/server/sonar-web/src/main/js/apps/account/tokens-view.js index 62c6f8f9143..29ef6f5f9cd 100644 --- a/server/sonar-web/src/main/js/apps/account/tokens-view.js +++ b/server/sonar-web/src/main/js/apps/account/tokens-view.js @@ -52,17 +52,13 @@ export default Marionette.ItemView.extend({ this.errors = []; this.newToken = null; const tokenName = this.$('.js-generate-token-form input').val(); - generateToken(this.model.id, tokenName) - .then(response => { + generateToken(tokenName, this.model.id).then( + response => { this.newToken = response; this.requestTokens(); - }) - .catch(error => { - error.response.json().then(response => { - this.errors = response.errors; - this.render(); - }); - }); + }, + () => {} + ); }, onRevokeTokenFormSubmit(e) { @@ -71,7 +67,7 @@ export default Marionette.ItemView.extend({ const token = this.tokens.find(token => token.name === `${tokenName}`); if (token) { if (token.deleting) { - revokeToken(this.model.id, tokenName).then(this.requestTokens.bind(this)); + revokeToken(tokenName, this.model.id).then(this.requestTokens.bind(this), () => {}); } else { token.deleting = true; this.render(); diff --git a/server/sonar-web/src/main/js/apps/projects-admin/CreateProjectForm.js b/server/sonar-web/src/main/js/apps/projects-admin/CreateProjectForm.js index acc503123ed..0266656aa8d 100644 --- a/server/sonar-web/src/main/js/apps/projects-admin/CreateProjectForm.js +++ b/server/sonar-web/src/main/js/apps/projects-admin/CreateProjectForm.js @@ -103,10 +103,9 @@ export default class CreateProjectForm extends React.PureComponent { this.props.onProjectCreated(); } }, - error => { + () => { if (this.mounted) { this.setState({ loading: false }); - this.props.onRequestFail(error); } } ); diff --git a/server/sonar-web/src/main/js/apps/settings/components/App.js b/server/sonar-web/src/main/js/apps/settings/components/App.js index d357cff1fbc..9d45c3749e7 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/App.js +++ b/server/sonar-web/src/main/js/apps/settings/components/App.js @@ -83,15 +83,15 @@ class App extends React.PureComponent { -
    -
    +
    +
    -
    +
    {selectedCategory === 'exclusions' && } diff --git a/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js b/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js index 3ad92ee2b96..676f5511174 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js +++ b/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js @@ -70,7 +70,7 @@ export default class CategoriesList extends React.PureComponent { const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase()); return ( -
      +
        {sortedCategories.map(category => (
      • {this.renderLink(category)} diff --git a/server/sonar-web/src/main/js/apps/settings/styles.css b/server/sonar-web/src/main/js/apps/settings/styles.css index a529c5c5c5f..4d188e811dc 100644 --- a/server/sonar-web/src/main/js/apps/settings/styles.css +++ b/server/sonar-web/src/main/js/apps/settings/styles.css @@ -1,60 +1,7 @@ .settings-layout { - display: flex; - justify-content: space-between; - align-items: stretch; margin-bottom: 60px; } -.settings-main { - position: relative; - z-index: 2; - flex-grow: 1; - padding: 15px 20px; - border: 1px solid #e6e6e6; - box-sizing: border-box; - background-color: #fff; -} - -.settings-side { - position: relative; - z-index: 3; - width: 160px; - flex-shrink: 0; - padding: 10px 0; - box-sizing: border-box; - transform: translateX(1px); -} - -.settings-menu {} - -.settings-menu > li { - margin-bottom: 4px; -} - -.settings-menu > li > a { - display: block; - padding: 10px 10px; - line-height: 1.5; - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; - border: 1px solid #e6e6e6; - border-right: none; - overflow: hidden; - text-overflow: ellipsis; - transition: color 0.3s ease, background-color 0.3s ease; -} - -.settings-menu > li > a:hover, -.settings-menu > li > a:focus, -.settings-menu > li > a.active { - background-color: #fff; -} - -.settings-menu > li > a.active { - color: #444; - cursor: default; -} - .settings-definitions-list > li + li { margin-top: 30px; } 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 new file mode 100644 index 00000000000..78db84e6ef9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/AnalysisStep.js @@ -0,0 +1,181 @@ +/* + * 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 Step from './Step'; +import LanguageStep from './LanguageStep'; +import type { Result } from './LanguageStep'; +import JavaMaven from './commands/JavaMaven'; +import JavaGradle from './commands/JavaGradle'; +import DotNet from './commands/DotNet'; +import Msvc from './commands/Msvc'; +import ClangGCC from './commands/ClangGCC'; +import Other from './commands/Other'; +import { translate } from '../../../helpers/l10n'; + +type Props = {| + open: boolean, + organization?: string, + sonarCloud: boolean, + stepNumber: number, + token: string +|}; + +type State = { + result?: Result +}; + +export default class AnalysisStep extends React.PureComponent { + props: Props; + state: State = {}; + + handleLanguageSelect = (result?: Result) => { + this.setState({ result }); + }; + + handleLanguageReset = () => { + this.setState({ result: undefined }); + }; + + getHost = () => window.location.origin + window.baseUrl; + + renderForm = () => { + return ( +
        +
        +
        + +
        +
        + {this.renderCommand()} +
        +
        +
        + ); + }; + + renderFormattedCommand = (...lines: Array) => ( +
        {lines.join(' ' + '\\' + '\n' + '  ')}
        + ); + + renderCommand = () => { + const { result } = this.state; + + if (!result) { + return null; + } + + if (result.language === 'java') { + return result.javaBuild === 'maven' + ? this.renderCommandForMaven() + : this.renderCommandForGradle(); + } else if (result.language === 'dotnet') { + return this.renderCommandForDotNet(); + } else if (result.language === 'c-family') { + return result.cFamilyCompiler === 'msvc' + ? this.renderCommandForMSVC() + : this.renderCommandForClangGCC(); + } else { + return this.renderCommandForOther(); + } + }; + + renderCommandForMaven = () => ( + + ); + + renderCommandForGradle = () => ( + + ); + + renderCommandForDotNet = () => { + return ( + + ); + }; + + renderCommandForMSVC = () => { + return ( + + ); + }; + + renderCommandForClangGCC = () => ( + + ); + + renderCommandForOther = () => ( + + ); + + renderResult = () => null; + + render() { + return ( + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/LanguageStep.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/LanguageStep.js new file mode 100644 index 00000000000..44bfb4ed766 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/LanguageStep.js @@ -0,0 +1,188 @@ +/* + * 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 NewProjectForm from './NewProjectForm'; +import RadioToggle from '../../../components/controls/RadioToggle'; +import { translate } from '../../../helpers/l10n'; + +type Props = {| + onDone: (result: Result) => void, + onReset: () => void, + organization?: string, + sonarCloud: boolean +|}; + +type State = { + language?: string, + javaBuild?: string, + cFamilyCompiler?: string, + os?: string, + projectKey?: string +}; + +export type Result = State; + +export default class LanguageStep extends React.PureComponent { + props: Props; + + static defaultProps = { sonarCloud: false }; + + state: State = {}; + + isConfigured = () => { + const { language, javaBuild, cFamilyCompiler, os, projectKey } = this.state; + const isJavaConfigured = language === 'java' && javaBuild != null; + const isDotNetConfigured = language === 'dotnet' && projectKey != null; + const isCFamilyConfigured = + language === 'c-family' && (cFamilyCompiler === 'msvc' || os != null) && projectKey != null; + const isOtherConfigured = language === 'other' && projectKey != null; + + return isJavaConfigured || isDotNetConfigured || isCFamilyConfigured || isOtherConfigured; + }; + + handleChange = () => { + if (this.isConfigured()) { + this.props.onDone(this.state); + } else { + this.props.onReset(); + } + }; + + handleLanguageChange = (language: string) => { + this.setState({ language }, this.handleChange); + }; + + handleJavaBuildChange = (javaBuild: string) => { + this.setState({ javaBuild }, this.handleChange); + }; + + handleCFamilyCompilerChange = (cFamilyCompiler: string) => { + this.setState({ cFamilyCompiler }, this.handleChange); + }; + + handleOSChange = (os: string) => { + this.setState({ os }, this.handleChange); + }; + + handleProjectKeyDone = (projectKey: string) => { + this.setState({ projectKey }, this.handleChange); + }; + + handleProjectKeyDelete = () => { + this.setState({ projectKey: undefined }, this.handleChange); + }; + + renderJavaBuild = () => ( +
        +

        + {translate('onboarding.language.java.build_technology')} +

        + ({ + label: translate('onboarding.language.java.build_technology', build), + value: build + }))} + value={this.state.javaBuild} + /> +
        + ); + + renderCFamilyCompiler = () => ( +
        +

        + {translate('onboarding.language.c-family.compiler')} +

        + ({ + label: translate('onboarding.language.c-family.compiler', compiler), + value: compiler + }))} + value={this.state.cFamilyCompiler} + /> +
        + ); + + renderOS = () => ( +
        +

        + {translate('onboarding.language.os')} +

        + ({ + label: translate('onboarding.language.os', os), + value: os + }))} + value={this.state.os} + /> +
        + ); + + renderProjectKey = () => ( + + ); + + render() { + const shouldAskProjectKey = + this.state.language === 'dotnet' || + (this.state.language === 'c-family' && + (this.state.cFamilyCompiler === 'msvc' || + (this.state.cFamilyCompiler === 'clang-gcc' && this.state.os != null))) || + (this.state.language === 'other' && this.state.os !== undefined); + + const languages = this.props.sonarCloud + ? ['java', 'dotnet', 'c-family', 'other'] + : ['java', 'dotnet', 'other']; + + return ( +
        +
        +

        {translate('onboarding.language')}

        + ({ + label: translate('onboarding.language', language), + value: language + }))} + value={this.state.language} + /> +
        + {this.state.language === 'java' && this.renderJavaBuild()} + {this.state.language === 'c-family' && this.renderCFamilyCompiler()} + {((this.state.language === 'c-family' && this.state.cFamilyCompiler === 'clang-gcc') || + this.state.language === 'other') && + this.renderOS()} + {shouldAskProjectKey && this.renderProjectKey()} +
        + ); + } +} 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 new file mode 100644 index 00000000000..16a7c54d526 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewOrganizationForm.js @@ -0,0 +1,157 @@ +/* + * 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 { debounce } from 'lodash'; +import { + createOrganization, + deleteOrganization, + getOrganization +} from '../../../api/organizations'; +import { translate } from '../../../helpers/l10n'; + +type Props = {| + onDelete: () => void, + onDone: (organization: string) => void, + organization?: string +|}; + +type State = { + done: boolean, + loading: boolean, + organization: string, + unique: boolean +}; + +export default class NewOrganizationForm extends React.PureComponent { + mounted: boolean; + props: Props; + state: State; + + constructor(props: Props) { + super(props); + this.state = { + done: props.organization != null, + loading: false, + organization: props.organization || '', + unique: true + }; + this.validateOrganization = debounce(this.validateOrganization, 500); + } + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + stopLoading = () => { + if (this.mounted) { + this.setState({ loading: false }); + } + }; + + validateOrganization = (organization: string) => { + getOrganization(organization).then(response => { + if (this.mounted) { + this.setState({ unique: response == null }); + } + }); + }; + + sanitizeOrganization = (organization: string) => + organization.toLowerCase().replace(/[^a-z0-9-]/, '').replace(/^-/, ''); + + handleOrganizationChange = (event: { target: HTMLInputElement }) => { + const organization = this.sanitizeOrganization(event.target.value); + this.setState({ organization }); + this.validateOrganization(organization); + }; + + handleOrganizationCreate = (event: Event) => { + event.preventDefault(); + const { organization } = this.state; + if (organization) { + this.setState({ loading: true }); + createOrganization({ key: organization, name: organization }).then(() => { + if (this.mounted) { + this.setState({ done: true, loading: false }); + this.props.onDone(organization); + } + }, this.stopLoading); + } + }; + + handleOrganizationDelete = (event: Event) => { + event.preventDefault(); + const { organization } = this.state; + if (organization) { + this.setState({ loading: true }); + deleteOrganization(organization).then(() => { + if (this.mounted) { + this.setState({ done: false, loading: false, organization: '' }); + this.props.onDelete(); + } + }, this.stopLoading); + } + }; + + render() { + const { done, loading, organization, unique } = this.state; + + const valid = unique && organization.length >= 2; + + return done + ?
        + {organization} + {loading + ? + : } + + :
        + + {loading + ? + : } + {!unique && + + + {translate('this_name_is_already_taken')} + } +
        + {translate('onboarding.organization.key_requirement')} +
        + ; + } +} 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 new file mode 100644 index 00000000000..2bf64f8fe5a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewProjectForm.js @@ -0,0 +1,145 @@ +/* + * 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 { createProject, deleteProject } from '../../../api/components'; +import { translate } from '../../../helpers/l10n'; + +type Props = {| + onDelete: () => void, + onDone: (projectKey: string) => void, + organization?: string, + projectKey?: string +|}; + +type State = { + done: boolean, + loading: boolean, + projectKey: string +}; + +export default class NewProjectForm extends React.PureComponent { + mounted: boolean; + props: Props; + state: State; + + constructor(props: Props) { + super(props); + this.state = { + done: props.projectKey != null, + loading: false, + projectKey: props.projectKey || '' + }; + } + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + stopLoading = () => { + if (this.mounted) { + this.setState({ loading: false }); + } + }; + + sanitizeProjectKey = (projectKey: string) => projectKey.replace(/[^a-zA-Z0-9-_\.:]/, ''); + + handleProjectKeyChange = (event: { target: HTMLInputElement }) => { + this.setState({ projectKey: this.sanitizeProjectKey(event.target.value) }); + }; + + handleProjectCreate = (event: Event) => { + event.preventDefault(); + const { projectKey } = this.state; + const data: { [string]: string } = { + name: projectKey, + project: projectKey + }; + if (this.props.organization) { + data.organization = this.props.organization; + } + this.setState({ loading: true }); + createProject(data).then(() => { + if (this.mounted) { + this.setState({ done: true, loading: false }); + this.props.onDone(projectKey); + } + }, this.stopLoading); + }; + + handleProjectDelete = (event: Event) => { + event.preventDefault(); + const { projectKey } = this.state; + this.setState({ loading: true }); + deleteProject(projectKey).then(() => { + if (this.mounted) { + this.setState({ done: false, loading: false, projectKey: '' }); + this.props.onDelete(); + } + }, this.stopLoading); + }; + + render() { + const { done, loading, projectKey } = this.state; + + const valid = projectKey.length > 0; + + const form = done + ?
        + {projectKey} + {loading + ? + : } + + :
        + + {loading + ? + : } +
        + {translate('onboarding.project_key_requirement')} +
        + ; + + return ( +
        +

        + {translate('onboarding.language.project_key')} +

        + {form} +
        + ); + } +} 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 new file mode 100644 index 00000000000..0382a33e0d6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js @@ -0,0 +1,109 @@ +/* + * 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 TokenStep from './TokenStep'; +import OrganizationStep from './OrganizationStep'; +import AnalysisStep from './AnalysisStep'; +import { translate } from '../../../helpers/l10n'; +import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; +import './styles.css'; + +type Props = { + currentUser: { login: string, isLoggedIn: boolean }, + organizationsEnabled: boolean, + sonarCloud: boolean +}; + +type State = { + organization?: string, + step: string, + token?: string +}; + +export default class Onboarding extends React.PureComponent { + props: Props; + state: State; + + constructor(props: Props) { + super(props); + this.state = { step: props.organizationsEnabled ? 'organization' : 'token' }; + } + + componentDidMount() { + if (!this.props.currentUser.isLoggedIn) { + handleRequiredAuthentication(); + } + } + + handleTokenDone = (token: string) => { + this.setState({ step: 'analysis', token }); + }; + + handleOrganizationDone = (organization: string) => { + this.setState({ organization, step: 'token' }); + }; + + render() { + if (!this.props.currentUser.isLoggedIn) { + return null; + } + + const { organizationsEnabled, sonarCloud } = this.props; + const { step, token } = this.state; + + let stepNumber = 1; + + return ( +
        +
        +

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

        +
        + {translate('onboarding.header.description')} +
        +
        + + {organizationsEnabled && + } + + + + +
        + ); + } +} 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 new file mode 100644 index 00000000000..a7eecc2e9e4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingContainer.js @@ -0,0 +1,39 @@ +/* + * 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 { connect } from 'react-redux'; +import Onboarding from './Onboarding'; +import { + getCurrentUser, + areThereCustomOrganizations, + getSettingValue +} from '../../../store/rootReducer'; + +const mapStateToProps = state => { + const sonarCloudSetting = getSettingValue(state, 'sonar.lf.sonarqube.com.enabled'); + + return { + currentUser: getCurrentUser(state), + organizationsEnabled: areThereCustomOrganizations(state), + sonarCloud: sonarCloudSetting != null && sonarCloudSetting.value === 'true' + }; +}; + +export default connect(mapStateToProps)(Onboarding); 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 new file mode 100644 index 00000000000..325e8e8ac56 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js @@ -0,0 +1,240 @@ +/* + * 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 Select from 'react-select'; +import classNames from 'classnames'; +import { sortBy } from 'lodash'; +import Step from './Step'; +import NewOrganizationForm from './NewOrganizationForm'; +import { getMyOrganizations } from '../../../api/organizations'; +import { translate } from '../../../helpers/l10n'; + +type Props = { + currentUser: { login: string, isLoggedIn: boolean }, + open: boolean, + onContinue: (organization: string) => void +}; + +type State = { + loading: boolean, + newOrganization?: string, + existingOrganization?: string, + existingOrganizations: Array, + selection: 'personal' | 'existing' | 'new' +}; + +export default class OrganizationStep extends React.PureComponent { + mounted: boolean; + props: Props; + state: State = { + loading: true, + existingOrganizations: [], + selection: 'personal' + }; + + componentDidMount() { + this.mounted = true; + this.fetchOrganizations(); + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchOrganizations = () => { + getMyOrganizations().then( + organizations => { + if (this.mounted) { + this.setState({ + loading: false, + existingOrganizations: sortBy( + organizations.filter(organization => organization !== this.props.currentUser.login) + ) + }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + }; + + getSelectedOrganization = () => { + switch (this.state.selection) { + case 'personal': + return this.props.currentUser.login; + case 'existing': + return this.state.existingOrganization; + case 'new': + return this.state.newOrganization; + default: + return null; + } + }; + + handlePersonalClick = (event: Event) => { + event.preventDefault(); + this.setState({ selection: 'personal' }); + }; + + handleExistingClick = (event: Event) => { + event.preventDefault(); + this.setState({ selection: 'existing' }); + }; + + handleNewClick = (event: Event) => { + event.preventDefault(); + this.setState({ selection: 'new' }); + }; + + handleOrganizationCreate = (newOrganization: string) => { + this.setState({ newOrganization }); + }; + + handleOrganizationDelete = () => { + this.setState({ newOrganization: undefined }); + }; + + handleExistingOrganizationSelect = ({ value }: { value: string }) => { + this.setState({ existingOrganization: value }); + }; + + handleContinueClick = (event: Event) => { + event.preventDefault(); + const organization = this.getSelectedOrganization(); + if (organization) { + this.props.onContinue(organization); + } + }; + + renderPersonalOrganizationOption = () => ( + + ); + + renderExistingOrganizationOption = () => ( +
        + + + {translate('onboarding.organization.exising_organization')} + + {this.state.selection === 'existing' && +
        + + {loading + ? + : } +
        + {translate('onboarding.project_key_requirement')} +
        + ; + + return ( +
        +

        + {translate('onboarding.language.project_key')} +

        + {form} +
        + ); + } +} 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 new file mode 100644 index 00000000000..763cef635df --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/Step.js @@ -0,0 +1,47 @@ +/* + * 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 classNames from 'classnames'; + +type Props = { + 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 + }); + + return ( +
        +
        {props.stepNumber}
        + {!props.open && props.renderResult()} +
        +

        {props.stepTitle}

        +
        + {props.open ? props.renderForm() :
        } +
        + ); +} 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 new file mode 100644 index 00000000000..9a171f7bc77 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js @@ -0,0 +1,180 @@ +/* + * 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 Step from './Step'; +import { generateToken, revokeToken } from '../../../api/user-tokens'; +import { translate } from '../../../helpers/l10n'; + +type Props = { + open: boolean, + onContinue: (token: string) => void, + stepNumber: number +}; + +type State = { + loading: boolean, + tokenName?: string, + token?: string +}; + +export default class TokenStep extends React.PureComponent { + mounted: boolean; + props: Props; + + static defaultProps = { + stepNumber: 1 + }; + + state: State = { + loading: false + }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleTokenNameChange = (event: { target: HTMLInputElement }) => { + this.setState({ tokenName: event.target.value }); + }; + + handleTokenGenerate = (event: Event) => { + event.preventDefault(); + const { tokenName } = this.state; + if (tokenName) { + this.setState({ loading: true }); + generateToken(tokenName).then( + ({ token }) => { + if (this.mounted) { + this.setState({ loading: false, token }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + } + }; + + handleTokenRevoke = (event: Event) => { + event.preventDefault(); + const { tokenName } = this.state; + if (tokenName) { + this.setState({ loading: true }); + revokeToken(tokenName).then( + () => { + if (this.mounted) { + this.setState({ loading: false, token: undefined, tokenName: undefined }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + } + }; + + handleContinueClick = (event: Event) => { + event.preventDefault(); + if (this.state.token) { + this.props.onContinue(this.state.token); + } + }; + + renderForm = () => { + const { loading, token, tokenName } = this.state; + + return ( +
        +
        + {translate('onboarding.token.text')} +
        + + {token != null + ?
        + {tokenName}{': '} + {token} + {loading + ? + : } + + :
        + + {loading + ? + : } + } + + {token != null && +
        + +
        } +
        + ); + }; + + renderResult = () => { + const { token, tokenName } = this.state; + + if (!token) { + return null; + } + + return ( +
        + + {tokenName}{': '} + {token} +
        + ); + }; + + render() { + return ( + + ); + } +} 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/onboarding/__tests__/LanguageStep-test.js new file mode 100644 index 00000000000..414b0dba7fe --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/LanguageStep-test.js @@ -0,0 +1,106 @@ +/* + * 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 } from 'enzyme'; +import LanguageStep from '../LanguageStep'; + +it('selects java', () => { + const onDone = jest.fn(); + const wrapper = shallow(); + + wrapper.find('RadioToggle').prop('onCheck')('java'); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + + wrapper.find('RadioToggle').at(1).prop('onCheck')('maven'); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + expect(onDone).lastCalledWith({ language: 'java', javaBuild: 'maven' }); + + wrapper.find('RadioToggle').at(1).prop('onCheck')('gradle'); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + expect(onDone).lastCalledWith({ language: 'java', javaBuild: 'gradle' }); +}); + +it('selects c#', () => { + const onDone = jest.fn(); + const wrapper = shallow(); + + wrapper.find('RadioToggle').prop('onCheck')('dotnet'); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + + wrapper.find('NewProjectForm').prop('onDone')('project-foo'); + expect(onDone).lastCalledWith({ language: 'dotnet', projectKey: 'project-foo' }); +}); + +it('selects c-family', () => { + const onDone = jest.fn(); + const wrapper = shallow(); + + wrapper.find('RadioToggle').prop('onCheck')('c-family'); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + + wrapper.find('RadioToggle').at(1).prop('onCheck')('msvc'); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + + wrapper.find('NewProjectForm').prop('onDone')('project-foo'); + expect(onDone).lastCalledWith({ + language: 'c-family', + cFamilyCompiler: 'msvc', + projectKey: 'project-foo' + }); + + wrapper.find('RadioToggle').at(1).prop('onCheck')('clang-gcc'); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + + wrapper.find('RadioToggle').at(2).prop('onCheck')('linux'); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + + wrapper.find('NewProjectForm').prop('onDone')('project-foo'); + expect(onDone).lastCalledWith({ + language: 'c-family', + cFamilyCompiler: 'clang-gcc', + os: 'linux', + projectKey: 'project-foo' + }); +}); + +it('selects other', () => { + const onDone = jest.fn(); + const wrapper = shallow(); + + wrapper.find('RadioToggle').prop('onCheck')('other'); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + + wrapper.find('RadioToggle').at(1).prop('onCheck')('mac'); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + + wrapper.find('NewProjectForm').prop('onDone')('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/onboarding/__tests__/NewOrganizationForm-test.js new file mode 100644 index 00000000000..79a5542b3ee --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewOrganizationForm-test.js @@ -0,0 +1,56 @@ +/* + * 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 { mount } from 'enzyme'; +import NewOrganizationForm from '../NewOrganizationForm'; +import { change, doAsync, submit } from '../../../../helpers/testUtils'; + +jest.mock('../../../../api/organizations', () => ({ + createOrganization: () => Promise.resolve(), + deleteOrganization: () => Promise.resolve(), + getOrganization: () => Promise.resolve(null) +})); + +it('creates new organization', () => { + const onDone = jest.fn(); + const wrapper = mount(); + expect(wrapper).toMatchSnapshot(); + change(wrapper.find('input'), 'foo'); + submit(wrapper.find('form')); + expect(wrapper).toMatchSnapshot(); // spinner + return doAsync(() => { + expect(wrapper).toMatchSnapshot(); + expect(onDone).toBeCalledWith('foo'); + }); +}); + +it('deletes organization', () => { + const onDelete = jest.fn(); + const wrapper = mount(); + wrapper.setState({ done: true, loading: false, organization: 'foo' }); + expect(wrapper).toMatchSnapshot(); + submit(wrapper.find('form')); + expect(wrapper).toMatchSnapshot(); // spinner + return doAsync(() => { + expect(wrapper).toMatchSnapshot(); + expect(onDelete).toBeCalled(); + }); +}); 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/onboarding/__tests__/NewProjectForm-test.js new file mode 100644 index 00000000000..7b05724cbe7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewProjectForm-test.js @@ -0,0 +1,55 @@ +/* + * 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 { mount } from 'enzyme'; +import NewProjectForm from '../NewProjectForm'; +import { change, doAsync, submit } from '../../../../helpers/testUtils'; + +jest.mock('../../../../api/components', () => ({ + createProject: () => Promise.resolve(), + deleteProject: () => Promise.resolve() +})); + +it('creates new project', () => { + const onDone = jest.fn(); + const wrapper = mount(); + expect(wrapper).toMatchSnapshot(); + change(wrapper.find('input'), 'foo'); + submit(wrapper.find('form')); + expect(wrapper).toMatchSnapshot(); // spinner + return doAsync(() => { + expect(wrapper).toMatchSnapshot(); + expect(onDone).toBeCalledWith('foo'); + }); +}); + +it('deletes project', () => { + const onDelete = jest.fn(); + const wrapper = mount(); + wrapper.setState({ done: true, loading: false, projectKey: 'foo' }); + expect(wrapper).toMatchSnapshot(); + submit(wrapper.find('form')); + expect(wrapper).toMatchSnapshot(); // spinner + return doAsync(() => { + expect(wrapper).toMatchSnapshot(); + expect(onDelete).toBeCalled(); + }); +}); 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/onboarding/__tests__/OrganizationStep-test.js new file mode 100644 index 00000000000..9352556f5d1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js @@ -0,0 +1,63 @@ +/* + * 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 { mount } from 'enzyme'; +import OrganizationStep from '../OrganizationStep'; +import { click, doAsync } from '../../../../helpers/testUtils'; + +jest.mock('../../../../api/organizations', () => ({ + getMyOrganizations: () => Promise.resolve(['user', 'another']) +})); + +const currentUser = { isLoggedIn: true, login: 'user' }; + +it('works with personal organization', () => { + const onContinue = jest.fn(); + const wrapper = mount( + + ); + click(wrapper.find('.js-continue')); + expect(onContinue).toBeCalledWith('user'); +}); + +it('works with existing organization', () => { + const onContinue = jest.fn(); + const wrapper = mount( + + ); + return doAsync(() => { + click(wrapper.find('.js-existing')); + wrapper.find('Select').prop('onChange')({ value: 'another' }); + click(wrapper.find('.js-continue')); + expect(onContinue).toBeCalledWith('another'); + }); +}); + +it('works with new organization', () => { + const onContinue = jest.fn(); + const wrapper = mount( + + ); + click(wrapper.find('.js-new')); + wrapper.find('NewOrganizationForm').prop('onDone')('new'); + click(wrapper.find('.js-continue')); + expect(onContinue).toBeCalledWith('new'); +}); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/ProjectKeyStep-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/ProjectKeyStep-test.js new file mode 100644 index 00000000000..2af50f6a3b5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/ProjectKeyStep-test.js @@ -0,0 +1,55 @@ +/* + * 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 { mount } from 'enzyme'; +import ProjectKeyStep from '../ProjectKeyStep'; +import { change, doAsync, submit } from '../../../../helpers/testUtils'; + +jest.mock('../../../../api/components', () => ({ + createProject: () => Promise.resolve(), + deleteProject: () => Promise.resolve() +})); + +it('creates new project', () => { + const onDone = jest.fn(); + const wrapper = mount(); + expect(wrapper).toMatchSnapshot(); + change(wrapper.find('input'), 'foo'); + submit(wrapper.find('form')); + expect(wrapper).toMatchSnapshot(); // spinner + return doAsync(() => { + expect(wrapper).toMatchSnapshot(); + expect(onDone).toBeCalledWith('foo'); + }); +}); + +it('deletes project', () => { + const onDelete = jest.fn(); + const wrapper = mount(); + wrapper.setState({ done: true, loading: false, projectKey: 'foo' }); + expect(wrapper).toMatchSnapshot(); + submit(wrapper.find('form')); + expect(wrapper).toMatchSnapshot(); // spinner + return doAsync(() => { + expect(wrapper).toMatchSnapshot(); + expect(onDelete).toBeCalled(); + }); +}); 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/onboarding/__tests__/Step-test.js new file mode 100644 index 00000000000..89d7a3b7ba4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/Step-test.js @@ -0,0 +1,38 @@ +/* + * 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 } from 'enzyme'; +import Step from '../Step'; + +it('renders', () => { + const wrapper = shallow( +
        form
        } + renderResult={() =>
        result
        } + stepNumber={1} + stepTitle="First Step" + /> + ); + expect(wrapper).toMatchSnapshot(); + wrapper.setProps({ open: false }); + expect(wrapper).toMatchSnapshot(); +}); 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/onboarding/__tests__/TokenStep-test.js new file mode 100644 index 00000000000..9498fc27ada --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js @@ -0,0 +1,55 @@ +/* + * 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 { mount } from 'enzyme'; +import TokenStep from '../TokenStep'; +import { change, click, doAsync, submit } from '../../../../helpers/testUtils'; + +jest.mock('../../../../api/user-tokens', () => ({ + generateToken: () => Promise.resolve({ token: 'abcd1234' }), + revokeToken: () => Promise.resolve() +})); + +it('generates token', () => { + const wrapper = mount(); + expect(wrapper).toMatchSnapshot(); + change(wrapper.find('input'), 'my token'); + submit(wrapper.find('form')); + expect(wrapper).toMatchSnapshot(); // spinner + return doAsync(() => expect(wrapper).toMatchSnapshot()); +}); + +it('revokes token', () => { + const wrapper = mount(); + wrapper.setState({ token: 'abcd1234', tokenName: 'my token' }); + expect(wrapper).toMatchSnapshot(); + submit(wrapper.find('form')); + expect(wrapper).toMatchSnapshot(); // spinner + return doAsync(() => expect(wrapper).toMatchSnapshot()); +}); + +it('continues', () => { + const onContinue = jest.fn(); + const wrapper = mount(); + wrapper.setState({ token: 'abcd1234', tokenName: 'my token' }); + click(wrapper.find('.js-continue')); + expect(onContinue).toBeCalledWith('abcd1234'); +}); 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/onboarding/__tests__/__snapshots__/LanguageStep-test.js.snap new file mode 100644 index 00000000000..8e82d384b3f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/LanguageStep-test.js.snap @@ -0,0 +1,687 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`selects c# 1`] = ` +
        +
        +

        + onboarding.language +

        + +
        + +
        +`; + +exports[`selects c-family 1`] = ` +
        +
        +

        + onboarding.language +

        + +
        +
        +

        + onboarding.language.c-family.compiler +

        + +
        +
        +`; + +exports[`selects c-family 2`] = ` +
        +
        +

        + onboarding.language +

        + +
        +
        +

        + onboarding.language.c-family.compiler +

        + +
        + +
        +`; + +exports[`selects c-family 3`] = ` +
        +
        +

        + onboarding.language +

        + +
        +
        +

        + onboarding.language.c-family.compiler +

        + +
        +
        +

        + onboarding.language.os +

        + +
        +
        +`; + +exports[`selects c-family 4`] = ` +
        +
        +

        + onboarding.language +

        + +
        +
        +

        + onboarding.language.c-family.compiler +

        + +
        +
        +

        + onboarding.language.os +

        + +
        + +
        +`; + +exports[`selects java 1`] = ` +
        +
        +

        + onboarding.language +

        + +
        +
        +

        + onboarding.language.java.build_technology +

        + +
        +
        +`; + +exports[`selects java 2`] = ` +
        +
        +

        + onboarding.language +

        + +
        +
        +

        + onboarding.language.java.build_technology +

        + +
        +
        +`; + +exports[`selects java 3`] = ` +
        +
        +

        + onboarding.language +

        + +
        +
        +

        + onboarding.language.java.build_technology +

        + +
        +
        +`; + +exports[`selects other 1`] = ` +
        +
        +

        + onboarding.language +

        + +
        +
        +

        + onboarding.language.os +

        + +
        +
        +`; + +exports[`selects other 2`] = ` +
        +
        +

        + onboarding.language +

        + +
        +
        +

        + onboarding.language.os +

        + +
        + +
        +`; 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/onboarding/__tests__/__snapshots__/NewOrganizationForm-test.js.snap new file mode 100644 index 00000000000..c5f05454644 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewOrganizationForm-test.js.snap @@ -0,0 +1,168 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`creates new organization 1`] = ` + +
        + + +
        + onboarding.organization.key_requirement +
        +
        +
        +`; + +exports[`creates new organization 2`] = ` + +
        + + +
        + onboarding.organization.key_requirement +
        + +
        +`; + +exports[`creates new organization 3`] = ` + +
        + + foo + + + +
        +`; + +exports[`deletes organization 1`] = ` + +
        + + foo + + + +
        +`; + +exports[`deletes organization 2`] = ` + +
        + + foo + + + +
        +`; + +exports[`deletes organization 3`] = ` + +
        + + +
        + onboarding.organization.key_requirement +
        +
        +
        +`; 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 new file mode 100644 index 00000000000..50ef33521cc --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap @@ -0,0 +1,219 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`creates new project 1`] = ` + +
        +

        + onboarding.language.project_key +

        +
        + + +
        + onboarding.project_key_requirement +
        +
        +
        +
        +`; + +exports[`creates new project 2`] = ` + +
        +

        + onboarding.language.project_key +

        +
        + + +
        + onboarding.project_key_requirement +
        + +
        +
        +`; + +exports[`creates new project 3`] = ` + +
        +

        + onboarding.language.project_key +

        +
        + + foo + + + +
        +
        +`; + +exports[`deletes project 1`] = ` + +
        +

        + onboarding.language.project_key +

        +
        + + foo + + + +
        +
        +`; + +exports[`deletes project 2`] = ` + +
        +

        + onboarding.language.project_key +

        +
        + + foo + + + +
        +
        +`; + +exports[`deletes project 3`] = ` + +
        +

        + onboarding.language.project_key +

        +
        + + +
        + onboarding.project_key_requirement +
        +
        +
        +
        +`; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/ProjectKeyStep-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/ProjectKeyStep-test.js.snap new file mode 100644 index 00000000000..cbfbf1da7ca --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/ProjectKeyStep-test.js.snap @@ -0,0 +1,219 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`creates new project 1`] = ` + +
        +

        + onboarding.language.project_key +

        +
        + + +
        + onboarding.project_key_requirement +
        +
        +
        +
        +`; + +exports[`creates new project 2`] = ` + +
        +

        + onboarding.language.project_key +

        +
        + + +
        + onboarding.project_key_requirement +
        + +
        +
        +`; + +exports[`creates new project 3`] = ` + +
        +

        + onboarding.language.project_key +

        +
        + + foo + + + +
        +
        +`; + +exports[`deletes project 1`] = ` + +
        +

        + onboarding.language.project_key +

        +
        + + foo + + + +
        +
        +`; + +exports[`deletes project 2`] = ` + +
        +

        + onboarding.language.project_key +

        +
        + + foo + + + +
        +
        +`; + +exports[`deletes project 3`] = ` + +
        +

        + onboarding.language.project_key +

        +
        + + +
        + onboarding.project_key_requirement +
        +
        +
        +
        +`; 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 new file mode 100644 index 00000000000..1df068a503c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Step-test.js.snap @@ -0,0 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +
        +
        + 1 +
        +
        +

        + First Step +

        +
        +
        + form +
        +
        +`; + +exports[`renders 2`] = ` +
        +
        + 1 +
        +
        + result +
        +
        +

        + First Step +

        +
        +
        +
        +`; 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/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap new file mode 100644 index 00000000000..73ae3896df4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap @@ -0,0 +1,383 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generates token 1`] = ` + + +
        +
        + 1 +
        +
        +

        + onboarding.token.header +

        +
        +
        +
        + onboarding.token.text +
        +
        + + +
        +
        +
        +
        +
        +`; + +exports[`generates token 2`] = ` + + +
        +
        + 1 +
        +
        +

        + onboarding.token.header +

        +
        +
        +
        + onboarding.token.text +
        +
        + + + +
        +
        +
        +
        +`; + +exports[`generates token 3`] = ` + + +
        +
        + 1 +
        +
        +

        + onboarding.token.header +

        +
        +
        +
        + onboarding.token.text +
        +
        + my token + : + + abcd1234 + + + +
        + +
        +
        +
        +
        +
        +`; + +exports[`revokes token 1`] = ` + + +
        +
        + 1 +
        +
        +

        + onboarding.token.header +

        +
        +
        +
        + onboarding.token.text +
        +
        + my token + : + + abcd1234 + + + +
        + +
        +
        +
        +
        +
        +`; + +exports[`revokes token 2`] = ` + + +
        +
        + 1 +
        +
        +

        + onboarding.token.header +

        +
        +
        +
        + onboarding.token.text +
        +
        + my token + : + + abcd1234 + + + +
        + +
        +
        +
        +
        +
        +`; + +exports[`revokes token 3`] = ` + + +
        +
        + 1 +
        +
        +

        + onboarding.token.header +

        +
        +
        +
        + onboarding.token.text +
        +
        + + +
        +
        +
        +
        +
        +`; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/BuildWrapper.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/BuildWrapper.js new file mode 100644 index 00000000000..3b0624e4629 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/BuildWrapper.js @@ -0,0 +1,58 @@ +/* + * 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 { translate } from '../../../../helpers/l10n'; + +type Props = { + className?: string, + os: string +}; + +const filenames = { + linux: 'build-wrapper-win-x86.zip', + win: 'build-wrapper-linux-x86.zip', + mac: 'build-wrapper-macosx-x86.zip' +}; + +export default function BuildWrapper(props: Props) { + return ( +
        +

        + {translate('onboarding.analysis.build_wrapper.header', props.os)} +

        +

        +

        + + {translate('download_verb')} + +

        +
        + ); +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/ClangGCC.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/ClangGCC.js new file mode 100644 index 00000000000..7940263931c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/ClangGCC.js @@ -0,0 +1,76 @@ +/* + * 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 Command from './Command'; +import SQScanner from './SQScanner'; +import BuildWrapper from './BuildWrapper'; +import { translate } from '../../../../helpers/l10n'; + +type Props = { + host: string, + os: string, + organization?: string, + projectKey: string, + token: string +}; + +const executables = { + linux: 'build-wrapper-linux-x86-64', + win: 'build-wrapper-win-x86-64.exe', + mac: 'build-wrapper-macosx-x86' +}; + +export default function ClangGCC(props: Props) { + const command1 = `${executables[props.os]} --out-dir bw-output make clean all`; + + const command2 = [ + props.os === 'win' ? 'sonar-scanner.bat' : 'sonar-scanner', + `-Dsonar.projectKey=${props.projectKey}`, + props.organization && `-Dsonar.organization=${props.organization}`, + '-Dsonar.sources=.', + '-Dsonar.cfamily.build-wrapper-output=bw-output', + `-Dsonar.host.url=${props.host}`, + `-Dsonar.login=${props.token}` + ]; + + return ( +
        + + + +

        + {translate('onboarding.analysis.sq_scanner.execute')} +

        +

        + + +

        +

        + ); +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Command.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Command.js new file mode 100644 index 00000000000..ec9f980083d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Command.js @@ -0,0 +1,89 @@ +/* + * 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 Clipboard from 'clipboard'; +import Tooltip from '../../../../components/controls/Tooltip'; +import { translate } from '../../../../helpers/l10n'; + +type Props = { + command: string | Array +}; + +type State = { + tooltipShown: boolean +}; + +const s = ' \\' + '\n '; + +export default class Command extends React.PureComponent { + clipboard: Object; + copyButton: HTMLButtonElement; + mounted: boolean; + props: Props; + state: State = { tooltipShown: false }; + + componentDidMount() { + this.mounted = true; + this.clipboard = new Clipboard(this.copyButton); + this.clipboard.on('success', this.showTooltip); + } + + componentWillUnmount() { + this.mounted = false; + this.clipboard.destroy(); + } + + showTooltip = () => { + if (this.mounted) { + this.setState({ tooltipShown: true }); + setTimeout(this.hideTooltip, 1000); + } + }; + + hideTooltip = () => { + if (this.mounted) { + this.setState({ tooltipShown: false }); + } + }; + + render() { + const { command } = this.props; + const commandArray = Array.isArray(command) ? command.filter(line => line != null) : [command]; + const finalCommand = commandArray.join(s); + + const button = ( + + ); + + return ( +
        +
        {finalCommand}
        + {this.state.tooltipShown + ? + {button} + + : button} +
        + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/DotNet.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/DotNet.js new file mode 100644 index 00000000000..8885458953b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/DotNet.js @@ -0,0 +1,68 @@ +/* + * 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 Command from './Command'; +import MSBuildScanner from './MSBuildScanner'; +import { translate } from '../../../../helpers/l10n'; + +type Props = {| + host: string, + organization?: string, + projectKey: string, + token: string +|}; + +export default function DotNet(props: Props) { + const command1 = [ + 'SonarQube.Scanner.MSBuild.exe begin', + `/k:"${props.projectKey}"`, + props.organization && `/d:"sonar.organization=${props.organization}"`, + `/d:"sonar.host.url=${props.host}`, + `/d:"sonar.login=${props.token}"` + ]; + + const command2 = 'MsBuild.exe /t:Rebuild'; + + const command3 = ['SonarQube.Scanner.MSBuild.exe end', `/d:"sonar.login=${props.token}"`]; + + return ( +
        + + +

        + {translate('onboarding.analysis.msbuild.execute')} +

        +

        + + + +

        +

        + ); +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/JavaGradle.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/JavaGradle.js new file mode 100644 index 00000000000..3f89a2bb241 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/JavaGradle.js @@ -0,0 +1,59 @@ +/* + * 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 Command from './Command'; +import { translate } from '../../../../helpers/l10n'; + +type Props = {| + host: string, + organization?: string, + token: string +|}; + +export default function JavaGradle(props: Props) { + const config = 'plugins {\n id "org.sonarqube" version "2.2"\n}'; + + const command = [ + './gradlew sonarqube', + props.organization && `-Dsonar.organization=${props.organization}`, + `-Dsonar.host.url=${props.host}`, + `-Dsonar.login=${props.token}` + ]; + + return ( +
        +

        {translate('onboarding.analysis.java.gradle.header')}

        +

        + +

        + {translate('onboarding.analysis.java.gradle.text.2')} +

        + +

        +

        + ); +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/JavaMaven.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/JavaMaven.js new file mode 100644 index 00000000000..6317b1bac2e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/JavaMaven.js @@ -0,0 +1,50 @@ +/* + * 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 Command from './Command'; +import { translate } from '../../../../helpers/l10n'; + +type Props = {| + host: string, + organization?: string, + token: string +|}; + +export default function JavaMaven(props: Props) { + const command = [ + 'mvn sonar:sonar', + props.organization && `-Dsonar.organization=${props.organization}`, + `-Dsonar.host.url=${props.host}`, + `-Dsonar.login=${props.token}` + ]; + + return ( +
        +

        {translate('onboarding.analysis.java.maven.header')}

        +

        {translate('onboarding.analysis.java.maven.text')}

        + +

        +

        + ); +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/MSBuildScanner.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/MSBuildScanner.js new file mode 100644 index 00000000000..3be38395ca9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/MSBuildScanner.js @@ -0,0 +1,46 @@ +/* + * 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 { translate } from '../../../../helpers/l10n'; + +type Props = { + className?: string +}; + +export default function MSBuildScanner(props: Props) { + return ( +
        +

        {translate('onboarding.analysis.msbuild.header')}

        +

        +

        + + {translate('download_verb')} + +

        +
        + ); +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Msvc.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Msvc.js new file mode 100644 index 00000000000..00fba90474a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Msvc.js @@ -0,0 +1,71 @@ +/* + * 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 Command from './Command'; +import MSBuildScanner from './MSBuildScanner'; +import BuildWrapper from './BuildWrapper'; +import { translate } from '../../../../helpers/l10n'; + +type Props = {| + host: string, + organization?: string, + projectKey: string, + token: string +|}; + +export default function Msvc(props: Props) { + const command1 = [ + 'SonarQube.Scanner.MSBuild.exe begin', + `/k:"${props.projectKey}"`, + props.organization && `/d:"sonar.organization=${props.organization}"`, + '/d:"sonar.cfamily.build-wrapper-output=bw-output"', + `/d:"sonar.host.url=${props.host}`, + `/d:"sonar.login=${props.token}"` + ]; + + const command2 = 'build-wrapper-win-x86-64.exe --out-dir bw-output MsBuild.exe /t:Rebuild'; + + const command3 = ['SonarQube.Scanner.MSBuild.exe end', `/d:"sonar.login=${props.token}"`]; + + return ( +
        + + + +

        + {translate('onboarding.analysis.msbuild.execute')} +

        +

        + + + +

        +

        + ); +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Other.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Other.js new file mode 100644 index 00000000000..534be2f8584 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Other.js @@ -0,0 +1,64 @@ +/* + * 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 Command from './Command'; +import SQScanner from './SQScanner'; +import { translate } from '../../../../helpers/l10n'; + +type Props = {| + host: string, + organization?: string, + os: string, + projectKey: string, + token: string +|}; + +export default function Other(props: Props) { + const command = [ + props.os === 'win' ? 'sonar-scanner.bat' : 'sonar-scanner', + `-Dsonar.projectKey=${props.projectKey}`, + props.organization && `-Dsonar.organization=${props.organization}`, + '-Dsonar.sources=.', + `-Dsonar.host.url=${props.host}`, + `-Dsonar.login=${props.token}` + ]; + + return ( +
        + + +

        + {translate('onboarding.analysis.sq_scanner.execute')} +

        +

        + +

        +

        + ); +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/SQScanner.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/SQScanner.js new file mode 100644 index 00000000000..d36c6379259 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/SQScanner.js @@ -0,0 +1,51 @@ +/* + * 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 { translate } from '../../../../helpers/l10n'; + +type Props = { + className?: string, + os: string +}; + +export default function SQScanner(props: Props) { + return ( +
        +

        + {translate('onboarding.analysis.sq_scanner.header', props.os)} +

        +

        +

        + + {translate('download_verb')} + +

        +
        + ); +} 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/onboarding/commands/__tests__/BuildWrapper-test.js new file mode 100644 index 00000000000..2135abc7606 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/BuildWrapper-test.js @@ -0,0 +1,29 @@ +/* + * 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 } from 'enzyme'; +import BuildWrapper from '../BuildWrapper'; + +it('renders correctly', () => { + expect(shallow()).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); +}); 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/onboarding/commands/__tests__/ClangGCC-test.js new file mode 100644 index 00000000000..16c458660b8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/ClangGCC-test.js @@ -0,0 +1,45 @@ +/* + * 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 } from 'enzyme'; +import ClangGCC from '../ClangGCC'; + +it('renders correctly', () => { + expect( + shallow() + ).toMatchSnapshot(); + + expect( + shallow() + ).toMatchSnapshot(); + + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/Command-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/Command-test.js new file mode 100644 index 00000000000..19ee425c791 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/Command-test.js @@ -0,0 +1,27 @@ +/* + * 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 } from 'enzyme'; +import Command from '../Command'; + +it('renders correctly', () => { + expect(shallow()).toMatchSnapshot(); +}); 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/onboarding/commands/__tests__/DotNet-test.js new file mode 100644 index 00000000000..f1e320e2b3f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/DotNet-test.js @@ -0,0 +1,32 @@ +/* + * 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 } from 'enzyme'; +import DotNet from '../DotNet'; + +it('renders correctly', () => { + expect(shallow()).toMatchSnapshot(); + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); 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/onboarding/commands/__tests__/JavaGradle-test.js new file mode 100644 index 00000000000..ef326ddc8c9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/JavaGradle-test.js @@ -0,0 +1,30 @@ +/* + * 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 } from 'enzyme'; +import JavaGradle from '../JavaGradle'; + +it('renders correctly', () => { + expect(shallow()).toMatchSnapshot(); + expect( + shallow() + ).toMatchSnapshot(); +}); 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/onboarding/commands/__tests__/JavaMaven-test.js new file mode 100644 index 00000000000..bb24f89e611 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/JavaMaven-test.js @@ -0,0 +1,30 @@ +/* + * 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 } from 'enzyme'; +import JavaMaven from '../JavaMaven'; + +it('renders correctly', () => { + expect(shallow()).toMatchSnapshot(); + expect( + shallow() + ).toMatchSnapshot(); +}); 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/onboarding/commands/__tests__/MSBuildScanner-test.js new file mode 100644 index 00000000000..e2c22bc157e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/MSBuildScanner-test.js @@ -0,0 +1,27 @@ +/* + * 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 } from 'enzyme'; +import MSBuildScanner from '../MSBuildScanner'; + +it('renders correctly', () => { + expect(shallow()).toMatchSnapshot(); +}); 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/onboarding/commands/__tests__/Msvc-test.js new file mode 100644 index 00000000000..28bfa947aa6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/Msvc-test.js @@ -0,0 +1,30 @@ +/* + * 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 } from 'enzyme'; +import Msvc from '../Msvc'; + +it('renders correctly', () => { + expect(shallow()).toMatchSnapshot(); + expect( + shallow() + ).toMatchSnapshot(); +}); 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/onboarding/commands/__tests__/Other-test.js new file mode 100644 index 00000000000..0eda78f2f94 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/Other-test.js @@ -0,0 +1,45 @@ +/* + * 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 } from 'enzyme'; +import Other from '../Other'; + +it('renders correctly', () => { + expect( + shallow() + ).toMatchSnapshot(); + + expect( + shallow() + ).toMatchSnapshot(); + + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); 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/onboarding/commands/__tests__/SQScanner-test.js new file mode 100644 index 00000000000..3b2881377ad --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/SQScanner-test.js @@ -0,0 +1,29 @@ +/* + * 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 } from 'enzyme'; +import SQScanner from '../SQScanner'; + +it('renders correctly', () => { + expect(shallow()).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); +}); 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/onboarding/commands/__tests__/__snapshots__/BuildWrapper-test.js.snap new file mode 100644 index 00000000000..4a1e0c3944c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/BuildWrapper-test.js.snap @@ -0,0 +1,85 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
        +

        + onboarding.analysis.build_wrapper.header.win +

        +

        +

        + + download_verb + +

        +
        +`; + +exports[`renders correctly 2`] = ` +
        +

        + onboarding.analysis.build_wrapper.header.linux +

        +

        +

        + + download_verb + +

        +
        +`; + +exports[`renders correctly 3`] = ` +
        +

        + onboarding.analysis.build_wrapper.header.mac +

        +

        +

        + + download_verb + +

        +
        +`; 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/onboarding/commands/__tests__/__snapshots__/ClangGCC-test.js.snap new file mode 100644 index 00000000000..1ec053c4083 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/ClangGCC-test.js.snap @@ -0,0 +1,148 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
        + + +

        + onboarding.analysis.sq_scanner.execute +

        +

        + + +

        +

        +`; + +exports[`renders correctly 2`] = ` +
        + + +

        + onboarding.analysis.sq_scanner.execute +

        +

        + + +

        +

        +`; + +exports[`renders correctly 3`] = ` +
        + + +

        + onboarding.analysis.sq_scanner.execute +

        +

        + + +

        +

        +`; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Command-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Command-test.js.snap new file mode 100644 index 00000000000..9ce1c887f5f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Command-test.js.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
        +
        +    foo
        +    bar
        +  
        + +
        +`; 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/onboarding/commands/__tests__/__snapshots__/DotNet-test.js.snap new file mode 100644 index 00000000000..3672a2f4c8d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/DotNet-test.js.snap @@ -0,0 +1,99 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
        + +

        + onboarding.analysis.msbuild.execute +

        +

        + + + +

        +

        +`; + +exports[`renders correctly 2`] = ` +
        + +

        + onboarding.analysis.msbuild.execute +

        +

        + + + +

        +

        +`; 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/onboarding/commands/__tests__/__snapshots__/JavaGradle-test.js.snap new file mode 100644 index 00000000000..df6bf9db47f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/JavaGradle-test.js.snap @@ -0,0 +1,93 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
        +

        + onboarding.analysis.java.gradle.header +

        +

        + +

        + onboarding.analysis.java.gradle.text.2 +

        + +

        +

        +`; + +exports[`renders correctly 2`] = ` +
        +

        + onboarding.analysis.java.gradle.header +

        +

        + +

        + onboarding.analysis.java.gradle.text.2 +

        + +

        +

        +`; 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/onboarding/commands/__tests__/__snapshots__/JavaMaven-test.js.snap new file mode 100644 index 00000000000..3cd4796988b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/JavaMaven-test.js.snap @@ -0,0 +1,67 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
        +

        + onboarding.analysis.java.maven.header +

        +

        + onboarding.analysis.java.maven.text +

        + +

        +

        +`; + +exports[`renders correctly 2`] = ` +
        +

        + onboarding.analysis.java.maven.header +

        +

        + onboarding.analysis.java.maven.text +

        + +

        +

        +`; 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/onboarding/commands/__tests__/__snapshots__/MSBuildScanner-test.js.snap new file mode 100644 index 00000000000..c7f156bc53f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/MSBuildScanner-test.js.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
        +

        + onboarding.analysis.msbuild.header +

        +

        +

        + + 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/onboarding/commands/__tests__/__snapshots__/Msvc-test.js.snap new file mode 100644 index 00000000000..c2f54b40496 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Msvc-test.js.snap @@ -0,0 +1,109 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
        + + +

        + onboarding.analysis.msbuild.execute +

        +

        + + + +

        +

        +`; + +exports[`renders correctly 2`] = ` +
        + + +

        + onboarding.analysis.msbuild.execute +

        +

        + + + +

        +

        +`; 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/onboarding/commands/__tests__/__snapshots__/Other-test.js.snap new file mode 100644 index 00000000000..699ff84d414 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Other-test.js.snap @@ -0,0 +1,124 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
        + +

        + onboarding.analysis.sq_scanner.execute +

        +

        + +

        +

        +`; + +exports[`renders correctly 2`] = ` +
        + +

        + onboarding.analysis.sq_scanner.execute +

        +

        + +

        +

        +`; + +exports[`renders correctly 3`] = ` +
        + +

        + onboarding.analysis.sq_scanner.execute +

        +

        + +

        +

        +`; 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/onboarding/commands/__tests__/__snapshots__/SQScanner-test.js.snap new file mode 100644 index 00000000000..bcdac075ce4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/SQScanner-test.js.snap @@ -0,0 +1,82 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
        +

        + onboarding.analysis.sq_scanner.header.win +

        +

        +

        + + download_verb + +

        +
        +`; + +exports[`renders correctly 2`] = ` +
        +

        + onboarding.analysis.sq_scanner.header.linux +

        +

        +

        + + download_verb + +

        +
        +`; + +exports[`renders correctly 3`] = ` +
        +

        + onboarding.analysis.sq_scanner.header.mac +

        +

        +

        + + 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/onboarding/styles.css new file mode 100644 index 00000000000..870da20acde --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/styles.css @@ -0,0 +1,59 @@ +.onboarding-step { + position: relative; + padding-left: 34px; +} + +.onboarding-step .boxed-group-actions { + height: 24px; + line-height: 24px; +} + +.onboarding-step-number { + position: absolute; + top: 15px; + left: 15px; + width: 24px; + height: 24px; + line-height: 24px; + border-radius: 24px; + background-color: #cdcdcd; + color: #fff; + font-size: 14px; + text-align: center; +} + +.onboarding-step-open .onboarding-step-number { + background-color: #236a97; +} + +.onboarding-command { + position: relative; + margin: 8px 0; +} + +.onboarding-command pre { + padding: 15px; + border-radius: 2px; + background: #404040; + color: #f0f0f0; + overflow: auto; +} + +.onboarding-command button { + position: absolute; + top: 15px; + right: 15px; + height: 20px; + line-height: 18px; + border: 1px solid #fff; + color: #fff; + font-size: 11px; + font-weight: normal; +} + +.onboarding-command button:hover, +.onboarding-command button:focus, +.onboarding-command button:active { + background-color: #fff; + color: #404040; +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/routes.js b/server/sonar-web/src/main/js/apps/tutorials/routes.js new file mode 100644 index 00000000000..3a7111d0ae0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/routes.js @@ -0,0 +1,31 @@ +/* + * 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; diff --git a/server/sonar-web/src/main/js/apps/users/tokens-view.js b/server/sonar-web/src/main/js/apps/users/tokens-view.js index 01fa9a6636e..01889e1d848 100644 --- a/server/sonar-web/src/main/js/apps/users/tokens-view.js +++ b/server/sonar-web/src/main/js/apps/users/tokens-view.js @@ -54,17 +54,13 @@ export default Modal.extend({ this.errors = []; this.newToken = null; const tokenName = this.$('.js-generate-token-form input').val(); - generateToken(this.model.id, tokenName) - .then(response => { + generateToken(tokenName, this.model.id).then( + response => { this.newToken = response; this.requestTokens(); - }) - .catch(error => { - error.response.json().then(response => { - this.errors = response.errors; - this.render(); - }); - }); + }, + () => {} + ); }, onRevokeTokenFormSubmit(e) { @@ -73,7 +69,7 @@ export default Modal.extend({ const token = this.tokens.find(t => t.name === `${tokenName}`); if (token) { if (token.deleting) { - revokeToken(this.model.id, tokenName).then(this.requestTokens.bind(this)); + revokeToken(tokenName, this.model.id).then(this.requestTokens.bind(this), () => {}); } else { token.deleting = true; this.render(); diff --git a/server/sonar-web/src/main/js/helpers/testUtils.js b/server/sonar-web/src/main/js/helpers/testUtils.js index 4ae17931866..a69169ceeae 100644 --- a/server/sonar-web/src/main/js/helpers/testUtils.js +++ b/server/sonar-web/src/main/js/helpers/testUtils.js @@ -54,3 +54,11 @@ export const elementKeydown = (element, keyCode) => { preventDefault() {} }); }; + +export const doAsync = fn => + new Promise(resolve => { + setTimeout(() => { + fn(); + resolve(); + }, 0); + }); diff --git a/server/sonar-web/src/main/less/components/modals.less b/server/sonar-web/src/main/less/components/modals.less index 2920733b485..4853c080759 100644 --- a/server/sonar-web/src/main/less/components/modals.less +++ b/server/sonar-web/src/main/less/components/modals.less @@ -43,6 +43,11 @@ opacity: 1; } +.modal-medium { + width: 800px; + margin-left: -400px; +} + .modal-large { width: 90vw; margin-left: -45vw; diff --git a/server/sonar-web/src/main/less/components/side-tabs.less b/server/sonar-web/src/main/less/components/side-tabs.less new file mode 100644 index 00000000000..b7cd0da337c --- /dev/null +++ b/server/sonar-web/src/main/less/components/side-tabs.less @@ -0,0 +1,86 @@ +/* + * 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. + */ +@import (reference) "../mixins"; +@import (reference) "../variables"; + +.side-tabs-layout { + display: flex; + justify-content: space-between; + align-items: stretch; + + .modal & { + padding-left: 10px; + background-color: @barBackgroundColor; + } +} + +.side-tabs-main { + position: relative; + z-index: 2; + flex-grow: 1; + padding: 15px 20px; + border: 1px solid @barBorderColor; + box-sizing: border-box; + background-color: #fff; + + .modal & { + border-top: none; + border-bottom: none; + border-right: none; + } +} + +.side-tabs-side { + position: relative; + z-index: 3; + width: 160px; + flex-shrink: 0; + padding: 10px 0; + box-sizing: border-box; + transform: translateX(1px); +} + +.side-tabs-menu > li { + margin-bottom: 4px; +} + +.side-tabs-menu > li > a { + display: block; + padding: 10px 10px; + line-height: 1.5; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + border: 1px solid @barBorderColor; + border-right: none; + overflow: hidden; + text-overflow: ellipsis; + transition: color 0.3s ease, background-color 0.3s ease; +} + +.side-tabs-menu > li > a:hover, +.side-tabs-menu > li > a:focus, +.side-tabs-menu > li > a.active { + background-color: #fff; +} + +.side-tabs-menu > li > a.active { + color: #444; + cursor: default; +} diff --git a/server/sonar-web/src/main/less/init/type.less b/server/sonar-web/src/main/less/init/type.less index 75fe1839413..d26cae210ba 100644 --- a/server/sonar-web/src/main/less/init/type.less +++ b/server/sonar-web/src/main/less/init/type.less @@ -114,6 +114,7 @@ small, .text-top { vertical-align: top; } .text-middle { vertical-align: middle; } .text-bottom { vertical-align: bottom; } +.text-text-top { vertical-align: text-top !important; } // Overflow diff --git a/server/sonar-web/src/main/less/sonar.less b/server/sonar-web/src/main/less/sonar.less index 876ad49a4e2..29e5875e709 100644 --- a/server/sonar-web/src/main/less/sonar.less +++ b/server/sonar-web/src/main/less/sonar.less @@ -58,6 +58,7 @@ @import "components/search"; @import "components/pills"; @import "components/react-select"; +@import "components/side-tabs"; @import "pages/coding-rules"; @import "pages/maintenance"; 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 8ebbb162e05..b64d2721158 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -47,6 +47,7 @@ component=Component configurable=Configurable configure=Configure confirm=Confirm +continue=Continue copy=Copy create=Create created=Created @@ -301,6 +302,7 @@ since_previous_version.short=\u0394 version since_previous_version_detailed=since previous version ({0} - {1}) since_previous_version_with_only_date=since previous version ({0}) since_previous_version_detailed.short=\u0394 version ({0}) +this_name_is_already_taken=This name is already taken. time_changes=Time changes work_duration.x_days={0}d work_duration.x_hours={0}h @@ -1054,10 +1056,12 @@ search.placeholder=Search for projects, sub-projects and files... #------------------------------------------------------------------------------ # -# SHORTCUTS +# GLOBAL HELP # #------------------------------------------------------------------------------ -shortcuts.modal_title=Shortcuts +help.section.links=Links +help.section.shortcuts=Shortcuts +help.section.tutorials=Tutorials shortcuts.section.global=Global shortcuts.section.global.search=quickly open search bar @@ -2937,3 +2941,79 @@ footer.terms=Terms footer.twitter=Twitter footer.version_x=Version {0} footer.web_api=Web API + + +#------------------------------------------------------------------------------ +# +# ONBOARDING +# +#------------------------------------------------------------------------------ +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.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. +onboarding.token.generate=Generate +onboarding.token.placeholder=Enter a name for your token + +onboarding.organization.header=Choose an organization for your project +onboarding.organization.text=Organizations are where your projects belong. You can add your team members to your organization later to allow them to contribute to your projects. +onboarding.organization.placeholder=Enter a name for your organization +onboarding.organization.my_personal_organization=My personal organization +onboarding.organization.exising_organization=Existing organization +onboarding.organization.create_another_organization=Create another organization +onboarding.organization.key_requirement=2 to 32 characters. All chars must be lower-case letters (a to z), digits or dash (but dash can neither be trailing nor heading) + +onboarding.project_key_requirement=Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit. 400 characters max. + +onboarding.analysis.header=Run analysis on your project + +onboarding.language=What is your project's main language? +onboarding.language.java=Java +onboarding.language.java.build_technology=You are developing primarily in Java: what is your build technology? +onboarding.language.java.build_technology.maven=Maven +onboarding.language.java.build_technology.gradle=Gradle +onboarding.language.dotnet=C# or VB.NET +onboarding.language.c-family=C, C++, Objective-C +onboarding.language.c-family.compiler=Which compiler are you using? +onboarding.language.c-family.compiler.msvc=Microsoft Visual C++ +onboarding.language.c-family.compiler.clang-gcc=CLang or GGC +onboarding.language.other=Other (JS, Python, PHP, ...) +onboarding.language.os=What is your OS? +onboarding.language.os.linux=Linux +onboarding.language.os.win=Windows +onboarding.language.os.mac=macOS +onboarding.language.project_key=Define a unique project key + +onboarding.analysis.java.maven.header=Execute the SonarQube Scanner for Maven from your computer +onboarding.analysis.java.maven.text=Running a SonarQube analysis with Maven is straighforward. You just need to run the following command in your project's folder. +onboarding.analysis.java.maven.docs=Please visit the official documentation of the SonarQube Scanner for Maven for more details. + +onboarding.analysis.java.gradle.header=Execute the SonarQube Scanner for Gradle from your computer +onboarding.analysis.java.gradle.text.1=Running a SonarQube analysis with Gradle is straighforward. You just need to declare the org.sonarqube plugin in your build.gradle file: +onboarding.analysis.java.gradle.text.2=and run the following command: +onboarding.analysis.java.gradle.docs=Please visit the official documentation of the SonarQube Scanner for Gradle for more details. + +onboarding.analysis.msbuild.header=Download and unzip the SonarQube Scanner for MSBuild +onboarding.analysis.msbuild.text=And add the executable's directory to the %PATH% environment variable +onboarding.analysis.msbuild.execute=Execute the SonarQube Scanner for MSBuild from your computer +onboarding.analysis.msbuild.execute.text=Running a SonarQube analysis is straighforward. You just need to execute the following commands at the root of your solution. +onboarding.analysis.msbuild.docs=Please visit the official documentation of the SonarQube Scanner for MSBuild for more details. + +onboarding.analysis.build_wrapper.header.linux=Download and unzip the Build Wrapper for Linux +onboarding.analysis.build_wrapper.header.win=Download and unzip the Build Wrapper for Windows +onboarding.analysis.build_wrapper.header.mac=Download and unzip the Build Wrapper for macOS +onboarding.analysis.build_wrapper.text.linux=And add the executable's directory to the PATH environment variable +onboarding.analysis.build_wrapper.text.win=And add the executable's directory to the %PATH% environment variable +onboarding.analysis.build_wrapper.text.mac=And add the executable's directory to the PATH environment variable + +onboarding.analysis.sq_scanner.header.linux=Download and unzip the SonarQube Scanner for Linux +onboarding.analysis.sq_scanner.header.win=Download and unzip the SonarQube Scanner for Windows +onboarding.analysis.sq_scanner.header.mac=Download and unzip the SonarQube Scanner for macOS +onboarding.analysis.sq_scanner.text.linux=And add the bin directory to the PATH environment variable +onboarding.analysis.sq_scanner.text.win=And add the bin directory to the %PATH% environment variable +onboarding.analysis.sq_scanner.text.mac=And add the bin directory to the PATH environment variable +onboarding.analysis.sq_scanner.execute=Execute the SonarQube Scanner from your computer +onboarding.analysis.sq_scanner.execute.text=Running a SonarQube analysis is straighforward. You just need to execute the following commands in your project's folder. +onboarding.analysis.sq_scanner.docs=Please visit the official documentation of the SonarQube Scanner for more details.