]> source.dussan.org Git - sonarqube.git/commitdiff
SONARCLOUD-64 New onboarding with 3 cases for SonarCloud (#383)
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 15 Jun 2018 13:04:21 +0000 (15:04 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 21 Jun 2018 18:21:29 +0000 (20:21 +0200)
* SONARCLOUD-64 Move Onboarding to ProjectOnboarding
* SONARCLOUD-64 Migrate project onboarding to TS
* SONARCLOUD-64 Update ProjectOnboarding style
* SONARCLOUD-64 Add main onboarding page

128 files changed:
server/sonar-web/src/main/js/app/components/StartupModal.tsx
server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx
server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx
server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx
server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavPlus-test.tsx
server/sonar-web/src/main/js/app/utils/startReactApp.js
server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx
server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/onboarding/AnalysisStep.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/LanguageStep.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/NewOrganizationForm.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/NewProjectForm.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingContainer.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.d.ts [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/ProjectWatcher.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/Step.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/LanguageStep-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewOrganizationForm-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewProjectForm-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/Onboarding-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/ProjectWatcher-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/Step-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/LanguageStep-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewOrganizationForm-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OrganizationStep-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/ProjectWatcher-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Step-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/BuildWrapper.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/ClangGCC.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/DotNet.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/JavaGradle.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/JavaMaven.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/MSBuildScanner.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Msvc.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Other.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/SQScanner.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/BuildWrapper-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/ClangGCC-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/DotNet-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/JavaGradle-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/JavaMaven-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/MSBuildScanner-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/Msvc-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/Other-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/SQScanner-test.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/BuildWrapper-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/ClangGCC-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/DotNet-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/JavaGradle-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/JavaMaven-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/MSBuildScanner-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Msvc-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Other-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/SQScanner-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/styles.css [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/AnalysisStep.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/LanguageStep.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewOrganizationForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewProjectForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/OrganizationStep.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingModal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingPage.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectWatcher.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/Step.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/TokenStep.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/LanguageStep-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewOrganizationForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewProjectForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/OrganizationStep-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectOnboarding-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectWatcher-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/Step-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/TokenStep-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/LanguageStep-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewProjectForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/OrganizationStep-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectOnboarding-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectWatcher-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/Step-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/TokenStep-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/BuildWrapper.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/ClangGCC.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/DotNet.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaGradle.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaMaven.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/MSBuildScanner.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Msvc.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Other.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/SQScanner.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/BuildWrapper-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/ClangGCC-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/DotNet-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaGradle-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaMaven-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/MSBuildScanner-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Msvc-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Other-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/SQScanner-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/DotNet-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Msvc-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Other-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/styles.css [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/TeamOnboardingModal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/__tests__/TeamOnboardingModal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/__tests__/__snapshots__/TeamOnboardingModal-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/testUtils.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 101279628d142fec7843c4fdc16a1b40e9f7018d..914adc7b3ea58f1cfa0ed912c0429896c58ac9f7 100644 (file)
 import * as React from 'react';
 import * as PropTypes from 'prop-types';
 import { connect } from 'react-redux';
-import OnboardingModal from '../../apps/tutorials/onboarding/OnboardingModal';
+import Onboarding from '../../apps/tutorials/Onboarding';
+import CreateOrganizationForm from '../../apps/account/organizations/CreateOrganizationForm';
 import LicensePromptModal from '../../apps/marketplace/components/LicensePromptModal';
-import { CurrentUser, isLoggedIn } from '../types';
+import ProjectOnboardingModal from '../../apps/tutorials/projectOnboarding/ProjectOnboardingModal';
+import TeamOnboardingModal from '../../apps/tutorials/teamOnboarding/TeamOnboardingModal';
+import { CurrentUser, isLoggedIn, Organization } from '../types';
 import { differenceInDays, parseDate, toShortNotSoISOString } from '../../helpers/dates';
 import { EditionKey } from '../../apps/marketplace/utils';
 import { getCurrentUser, getAppState } from '../../store/rootReducer';
-import { skipOnboarding } from '../../store/users/actions';
+import { skipOnboarding as skipOnboardingAction } from '../../store/users/actions';
 import { showLicense } from '../../api/marketplace';
 import { hasMessage } from '../../helpers/l10n';
 import { save, get } from '../../helpers/storage';
+import { isSonarCloud } from '../../helpers/system';
+import { skipOnboarding } from '../../api/users';
 
 interface StateProps {
   canAdmin: boolean;
@@ -38,7 +43,7 @@ interface StateProps {
 }
 
 interface DispatchProps {
-  skipOnboarding: () => void;
+  skipOnboardingAction: () => void;
 }
 
 interface OwnProps {
@@ -50,7 +55,10 @@ type Props = StateProps & DispatchProps & OwnProps;
 
 enum ModalKey {
   license,
-  onboarding
+  onboarding,
+  organizationOnboarding,
+  projectOnboarding,
+  teamOnboarding
 }
 
 interface State {
@@ -61,14 +69,18 @@ interface State {
 const LICENSE_PROMPT = 'sonarqube.license.prompt';
 
 export class StartupModal extends React.PureComponent<Props, State> {
+  static contextTypes = {
+    router: PropTypes.object.isRequired
+  };
+
   static childContextTypes = {
-    openOnboardingTutorial: PropTypes.func
+    openProjectOnboarding: PropTypes.func
   };
 
   state: State = { automatic: false };
 
   getChildContext() {
-    return { openOnboardingTutorial: this.openOnboarding };
+    return { openProjectOnboarding: this.openProjectOnboarding };
   }
 
   componentDidMount() {
@@ -77,8 +89,9 @@ export class StartupModal extends React.PureComponent<Props, State> {
 
   closeOnboarding = () => {
     this.setState(state => {
-      if (state.modal === ModalKey.onboarding) {
-        this.props.skipOnboarding();
+      if (state.modal !== ModalKey.license) {
+        skipOnboarding();
+        this.props.skipOnboardingAction();
         return { automatic: false, modal: undefined };
       }
       return undefined;
@@ -94,10 +107,27 @@ export class StartupModal extends React.PureComponent<Props, State> {
     });
   };
 
+  closeOrganizationOnboarding = ({ key }: Pick<Organization, 'key'>) => {
+    this.closeOnboarding();
+    this.context.router.push(`/organizations/${key}`);
+  };
+
   openOnboarding = () => {
     this.setState({ modal: ModalKey.onboarding });
   };
 
+  openOrganizationOnboarding = () => {
+    this.setState({ modal: ModalKey.organizationOnboarding });
+  };
+
+  openProjectOnboarding = () => {
+    this.setState({ modal: ModalKey.projectOnboarding });
+  };
+
+  openTeamOnboarding = () => {
+    this.setState({ modal: ModalKey.teamOnboarding });
+  };
+
   tryAutoOpenLicense = () => {
     const { canAdmin, currentEdition, currentUser } = this.props;
     const hasLicenseManager = hasMessage('license.prompt.title');
@@ -124,7 +154,11 @@ export class StartupModal extends React.PureComponent<Props, State> {
     const { currentUser, location } = this.props;
     if (currentUser.showOnboardingTutorial && !location.pathname.startsWith('documentation')) {
       this.setState({ automatic: true });
-      this.openOnboarding();
+      if (isSonarCloud()) {
+        this.openOnboarding();
+      } else {
+        this.openProjectOnboarding();
+      }
     }
   };
 
@@ -135,7 +169,24 @@ export class StartupModal extends React.PureComponent<Props, State> {
         {this.props.children}
         {modal === ModalKey.license && <LicensePromptModal onClose={this.closeLicense} />}
         {modal === ModalKey.onboarding && (
-          <OnboardingModal automatic={automatic} onFinish={this.closeOnboarding} />
+          <Onboarding
+            onFinish={this.closeOnboarding}
+            onOpenOrganizationOnboarding={this.openOrganizationOnboarding}
+            onOpenProjectOnboarding={this.openProjectOnboarding}
+            onOpenTeamOnboarding={this.openTeamOnboarding}
+          />
+        )}
+        {modal === ModalKey.projectOnboarding && (
+          <ProjectOnboardingModal automatic={automatic} onFinish={this.closeOnboarding} />
+        )}
+        {modal === ModalKey.organizationOnboarding && (
+          <CreateOrganizationForm
+            onClose={this.closeOnboarding}
+            onCreate={this.closeOrganizationOnboarding}
+          />
+        )}
+        {modal === ModalKey.teamOnboarding && (
+          <TeamOnboardingModal onFinish={this.closeOnboarding} />
         )}
       </>
     );
@@ -148,7 +199,7 @@ const mapStateToProps = (state: any): StateProps => ({
   currentUser: getCurrentUser(state)
 });
 
-const mapDispatchToProps: DispatchProps = { skipOnboarding };
+const mapDispatchToProps: DispatchProps = { skipOnboardingAction };
 
 export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)(
   StartupModal
index d2d48ee7d51731acff850334aa2106bac0eb39a6..7a1e2bf2d3d440097f23fb66ebdee4a4a24163e2 100644 (file)
@@ -119,12 +119,12 @@ it('should render onboarding modal', async () => {
 async function shouldNotHaveModals(wrapper: ShallowWrapper) {
   await waitAndUpdate(wrapper);
   expect(wrapper.find('LicensePromptModal').exists()).toBeFalsy();
-  expect(wrapper.find('OnboardingModal').exists()).toBeFalsy();
+  expect(wrapper.find('ProjectOnboardingModal').exists()).toBeFalsy();
 }
 
 async function shouldDisplayOnboarding(wrapper: ShallowWrapper) {
   await waitAndUpdate(wrapper);
-  expect(wrapper.find('OnboardingModal').exists()).toBeTruthy();
+  expect(wrapper.find('ProjectOnboardingModal').exists()).toBeTruthy();
 }
 
 async function shouldDisplayLicense(wrapper: ShallowWrapper) {
@@ -139,9 +139,10 @@ function getWrapper(props = {}) {
       currentEdition={EditionKey.enterprise}
       currentUser={LOGGED_IN_USER}
       location={{ pathname: 'foo/bar' }}
-      skipOnboarding={jest.fn()}
+      skipOnboardingAction={jest.fn()}
       {...props}>
       <div />
-    </StartupModal>
+    </StartupModal>,
+    { context: { router: { push: jest.fn() } } }
   );
 }
index 061a3af556b611debc20a18565c1a0c9a394a785..5cb06388326d3c027b4d3a25dc80dd72b2a20961 100644 (file)
@@ -36,13 +36,13 @@ interface Props {
 
 export default class EmbedDocsPopup extends React.PureComponent<Props> {
   static contextTypes = {
-    openOnboardingTutorial: PropTypes.func
+    openProjectOnboarding: PropTypes.func
   };
 
   onAnalyzeProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
     event.preventDefault();
     event.currentTarget.blur();
-    this.context.openOnboardingTutorial();
+    this.context.openProjectOnboarding();
   };
 
   renderTitle(text: string) {
index 6d3cdcece688b0b4f83549eb9acdf022d2fecc85..0f596735299d9b6fa0b19c9d2fb300005ea0c08a 100644 (file)
@@ -50,7 +50,7 @@ interface OwnProps {
 type Props = StateProps & OwnProps;
 
 class GlobalNav extends React.PureComponent<Props> {
-  static contextTypes = { openOnboardingTutorial: PropTypes.func };
+  static contextTypes = { openProjectOnboarding: PropTypes.func };
 
   render() {
     return (
@@ -69,7 +69,7 @@ class GlobalNav extends React.PureComponent<Props> {
           <Search appState={this.props.appState} currentUser={this.props.currentUser} />
           {isLoggedIn(this.props.currentUser) &&
             isSonarCloud() && (
-              <GlobalNavPlus openOnboardingTutorial={this.context.openOnboardingTutorial} />
+              <GlobalNavPlus openProjectOnboarding={this.context.openProjectOnboarding} />
             )}
           <GlobalNavUserContainer {...this.props} />
         </ul>
index d58fb2379acf1a80e2757410111a8b806fb2f604..412846db4668592366cfe3f836e1fede9b7e9124 100644 (file)
@@ -25,7 +25,7 @@ import Dropdown from '../../../../components/controls/Dropdown';
 import { translate } from '../../../../helpers/l10n';
 
 interface Props {
-  openOnboardingTutorial: () => void;
+  openProjectOnboarding: () => void;
 }
 
 interface State {
@@ -44,7 +44,7 @@ export default class GlobalNavPlus extends React.PureComponent<Props, State> {
 
   handleNewProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
     event.preventDefault();
-    this.props.openOnboardingTutorial();
+    this.props.openProjectOnboarding();
   };
 
   openCreateOrganizationForm = () => this.setState({ createOrganization: true });
index 3e4110f5193bf33d1ea4adc3030f2f37a2bc0772..88aaf4f172dc1fd4e8e9342a5b6925b78b612d2b 100644 (file)
@@ -23,18 +23,18 @@ import GlobalNavPlus from '../GlobalNavPlus';
 import { click } from '../../../../../helpers/testUtils';
 
 it('render', () => {
-  const wrapper = shallow(<GlobalNavPlus openOnboardingTutorial={jest.fn()} />);
+  const wrapper = shallow(<GlobalNavPlus openProjectOnboarding={jest.fn()} />);
   expect(wrapper.is('Dropdown')).toBe(true);
   expect(wrapper.find('Dropdown')).toMatchSnapshot();
 });
 
 it('opens onboarding', () => {
-  const openOnboardingTutorial = jest.fn();
+  const openProjectOnboarding = jest.fn();
   const wrapper = shallow(
-    shallow(<GlobalNavPlus openOnboardingTutorial={openOnboardingTutorial} />)
+    shallow(<GlobalNavPlus openProjectOnboarding={openProjectOnboarding} />)
       .find('Dropdown')
       .prop('overlay')
   );
   click(wrapper.find('.js-new-project'));
-  expect(openOnboardingTutorial).toBeCalled();
+  expect(openProjectOnboarding).toBeCalled();
 });
index b4b79feec06a939ce527f201e935ff13064f49e1..e092a6c13eb33b11a0dfd195b4f43deeaab14635 100644 (file)
@@ -172,7 +172,7 @@ const startReactApp = () => {
                 <Route
                   path="onboarding"
                   component={lazyLoad(() =>
-                    import('../../apps/tutorials/onboarding/OnboardingPage')
+                    import('../../apps/tutorials/projectOnboarding/ProjectOnboardingPage')
                   )}
                 />
                 <Route path="organizations" childRoutes={organizationsRoutes} />
index 6c52d002bb1a79fe854b5bedf896088d8b47e099..4cd7b7d2164da90224a8003a90e862f246e22ede 100644 (file)
@@ -36,13 +36,13 @@ interface StateProps {
 
 export class NoFavoriteProjects extends React.PureComponent<StateProps> {
   static contextTypes = {
-    openOnboardingTutorial: PropTypes.func
+    openProjectOnboarding: PropTypes.func
   };
 
   onAnalyzeProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
     event.preventDefault();
     event.currentTarget.blur();
-    this.context.openOnboardingTutorial();
+    this.context.openProjectOnboarding();
   };
 
   render() {
diff --git a/server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx b/server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx
new file mode 100644 (file)
index 0000000..e6a6d88
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { connect } from 'react-redux';
+import handleRequiredAuthentication from '../../app/utils/handleRequiredAuthentication';
+import Modal from '../../components/controls/Modal';
+import { ResetButtonLink, Button } from '../../components/ui/buttons';
+import { translate } from '../../helpers/l10n';
+import { CurrentUser, isLoggedIn } from '../../app/types';
+import { getCurrentUser } from '../../store/rootReducer';
+import './styles.css';
+
+interface OwnProps {
+  onFinish: () => void;
+  onOpenOrganizationOnboarding: () => void;
+  onOpenProjectOnboarding: () => void;
+  onOpenTeamOnboarding: () => void;
+}
+
+interface StateProps {
+  currentUser: CurrentUser;
+}
+
+type Props = OwnProps & StateProps;
+
+export class Onboarding extends React.PureComponent<Props> {
+  componentDidMount() {
+    if (!isLoggedIn(this.props.currentUser)) {
+      handleRequiredAuthentication();
+    }
+  }
+
+  render() {
+    if (!isLoggedIn(this.props.currentUser)) {
+      return null;
+    }
+
+    const header = translate('onboarding.header');
+    return (
+      <Modal
+        contentLabel={header}
+        medium={true}
+        onRequestClose={this.props.onFinish}
+        shouldCloseOnOverlayClick={false}>
+        <header className="modal-head">
+          <h2>{header}</h2>
+        </header>
+        <div className="modal-body">
+          <p className="spacer-top big-spacer-bottom">
+            {translate('onboarding.header.description')}
+          </p>
+          <ul className="onboarding-choices">
+            <li className="text-center">
+              <p className="big-spacer-bottom">{translate('onboarding.analyze_public_code')}</p>
+              <Button onClick={this.props.onOpenProjectOnboarding}>
+                {translate('onboarding.analyze_public_code.button')}
+              </Button>
+            </li>
+            <li className="text-center">
+              <p className="big-spacer-bottom">{translate('onboarding.analyze_private_code')}</p>
+              <Button onClick={this.props.onOpenOrganizationOnboarding}>
+                {translate('onboarding.analyze_private_code.button')}
+              </Button>
+            </li>
+            <li className="text-center">
+              <p className="big-spacer-bottom">
+                {translate('onboarding.contribute_existing_project')}
+              </p>
+              <Button onClick={this.props.onOpenTeamOnboarding}>
+                {translate('onboarding.contribute_existing_project.button')}
+              </Button>
+            </li>
+          </ul>
+        </div>
+        <footer className="modal-foot">
+          <ResetButtonLink onClick={this.props.onFinish}>{translate('close')}</ResetButtonLink>
+        </footer>
+      </Modal>
+    );
+  }
+}
+
+const mapStateToProps = (state: any): StateProps => ({ currentUser: getCurrentUser(state) });
+
+export default connect<StateProps, {}, OwnProps>(mapStateToProps)(Onboarding);
diff --git a/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx
new file mode 100644 (file)
index 0000000..c9b3e77
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import { Onboarding } from '../Onboarding';
+import { click } from '../../../helpers/testUtils';
+
+it('renders correctly', () => {
+  expect(
+    shallow(
+      <Onboarding
+        currentUser={{ isLoggedIn: true }}
+        onFinish={jest.fn()}
+        onOpenOrganizationOnboarding={jest.fn()}
+        onOpenProjectOnboarding={jest.fn()}
+        onOpenTeamOnboarding={jest.fn()}
+      />
+    )
+  ).toMatchSnapshot();
+});
+
+it('should correctly open the different tutorials', () => {
+  const onFinish = jest.fn();
+  const onOpenOrganizationOnboarding = jest.fn();
+  const onOpenProjectOnboarding = jest.fn();
+  const onOpenTeamOnboarding = jest.fn();
+  const wrapper = shallow(
+    <Onboarding
+      currentUser={{ isLoggedIn: true }}
+      onFinish={onFinish}
+      onOpenOrganizationOnboarding={onOpenOrganizationOnboarding}
+      onOpenProjectOnboarding={onOpenProjectOnboarding}
+      onOpenTeamOnboarding={onOpenTeamOnboarding}
+    />
+  );
+
+  click(wrapper.find('ResetButtonLink'));
+  expect(onFinish).toHaveBeenCalled();
+
+  wrapper.find('Button').forEach(button => click(button));
+  expect(onOpenOrganizationOnboarding).toHaveBeenCalled();
+  expect(onOpenProjectOnboarding).toHaveBeenCalled();
+  expect(onOpenTeamOnboarding).toHaveBeenCalled();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap
new file mode 100644 (file)
index 0000000..e017f77
--- /dev/null
@@ -0,0 +1,82 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<Modal
+  contentLabel="onboarding.header"
+  medium={true}
+  onRequestClose={[MockFunction]}
+  shouldCloseOnOverlayClick={false}
+>
+  <header
+    className="modal-head"
+  >
+    <h2>
+      onboarding.header
+    </h2>
+  </header>
+  <div
+    className="modal-body"
+  >
+    <p
+      className="spacer-top big-spacer-bottom"
+    >
+      onboarding.header.description
+    </p>
+    <ul
+      className="onboarding-choices"
+    >
+      <li
+        className="text-center"
+      >
+        <p
+          className="big-spacer-bottom"
+        >
+          onboarding.analyze_public_code
+        </p>
+        <Button
+          onClick={[MockFunction]}
+        >
+          onboarding.analyze_public_code.button
+        </Button>
+      </li>
+      <li
+        className="text-center"
+      >
+        <p
+          className="big-spacer-bottom"
+        >
+          onboarding.analyze_private_code
+        </p>
+        <Button
+          onClick={[MockFunction]}
+        >
+          onboarding.analyze_private_code.button
+        </Button>
+      </li>
+      <li
+        className="text-center"
+      >
+        <p
+          className="big-spacer-bottom"
+        >
+          onboarding.contribute_existing_project
+        </p>
+        <Button
+          onClick={[MockFunction]}
+        >
+          onboarding.contribute_existing_project.button
+        </Button>
+      </li>
+    </ul>
+  </div>
+  <footer
+    className="modal-foot"
+  >
+    <ResetButtonLink
+      onClick={[MockFunction]}
+    >
+      close
+    </ResetButtonLink>
+  </footer>
+</Modal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/AnalysisStep.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/AnalysisStep.js
deleted file mode 100644 (file)
index afaa359..0000000
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import 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';
-import { getHostUrl } from '../../../helpers/urls';
-
-/*::
-type Props = {|
-  onFinish: (projectKey?: string) => void,
-  onReset: () => void,
-  open: boolean,
-  organization?: string,
-  stepNumber: number,
-  token: string
-|};
-*/
-
-/*::
-type State = {
-  result?: Result
-};
-*/
-
-export default class AnalysisStep extends React.PureComponent {
-  /*:: props: Props; */
-  state /*: State */ = {};
-
-  handleLanguageSelect = (result /*: Result | void */) => {
-    this.setState({ result });
-    const projectKey = result && result.language !== 'java' ? result.projectKey : undefined;
-    this.props.onFinish(projectKey);
-  };
-
-  handleLanguageReset = () => {
-    this.setState({ result: undefined });
-    this.props.onReset();
-  };
-
-  renderForm = () => {
-    return (
-      <div className="boxed-group-inner">
-        <div className="flex-columns">
-          <div className="flex-column flex-column-half bordered-right">
-            <LanguageStep
-              onDone={this.handleLanguageSelect}
-              onReset={this.handleLanguageReset}
-              organization={this.props.organization}
-            />
-          </div>
-          <div className="flex-column flex-column-half">{this.renderCommand()}</div>
-        </div>
-      </div>
-    );
-  };
-
-  renderFormattedCommand = (...lines /*: Array<string> */) => (
-    // keep this "useless" concatentation for the readability reason
-    // eslint-disable-next-line no-useless-concat
-    <pre>{lines.join(' ' + '\\' + '\n' + '  ')}</pre>
-  );
-
-  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 = () => (
-    <JavaMaven
-      host={getHostUrl()}
-      organization={this.props.organization}
-      token={this.props.token}
-    />
-  );
-
-  renderCommandForGradle = () => (
-    <JavaGradle
-      host={getHostUrl()}
-      organization={this.props.organization}
-      token={this.props.token}
-    />
-  );
-
-  renderCommandForDotNet = () => {
-    return (
-      <DotNet
-        host={getHostUrl()}
-        organization={this.props.organization}
-        // $FlowFixMe
-        projectKey={this.state.result.projectKey}
-        token={this.props.token}
-      />
-    );
-  };
-
-  renderCommandForMSVC = () => {
-    return (
-      <Msvc
-        host={getHostUrl()}
-        organization={this.props.organization}
-        // $FlowFixMe
-        projectKey={this.state.result.projectKey}
-        token={this.props.token}
-      />
-    );
-  };
-
-  renderCommandForClangGCC = () => (
-    <ClangGCC
-      host={getHostUrl()}
-      organization={this.props.organization}
-      // $FlowFixMe
-      os={this.state.result.os}
-      // $FlowFixMe
-      projectKey={this.state.result.projectKey}
-      token={this.props.token}
-    />
-  );
-
-  renderCommandForOther = () => (
-    <Other
-      host={getHostUrl()}
-      organization={this.props.organization}
-      // $FlowFixMe
-      os={this.state.result.os}
-      // $FlowFixMe
-      projectKey={this.state.result.projectKey}
-      token={this.props.token}
-    />
-  );
-
-  renderResult = () => null;
-
-  render() {
-    return (
-      <Step
-        finished={false}
-        onOpen={() => {}}
-        open={this.props.open}
-        renderForm={this.renderForm}
-        renderResult={this.renderResult}
-        stepNumber={this.props.stepNumber}
-        stepTitle={translate('onboarding.analysis.header')}
-      />
-    );
-  }
-}
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
deleted file mode 100644 (file)
index c9bd1c1..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import NewProjectForm from './NewProjectForm';
-import RadioToggle from '../../../components/controls/RadioToggle';
-import { translate } from '../../../helpers/l10n';
-import { isSonarCloud } from '../../../helpers/system';
-
-/*::
-type Props = {|
-  onDone: (result: Result) => void,
-  onReset: () => void,
-  organization?: string,
-|};
-*/
-
-/*::
-type State = {
-  language?: string,
-  javaBuild?: string,
-  cFamilyCompiler?: string,
-  os?: string,
-  projectKey?: string
-};
-*/
-
-/*::
-export type Result = State; */
-
-export default class LanguageStep extends React.PureComponent {
-  /*:: props: Props; */
-
-  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 = () => (
-    <div className="big-spacer-top">
-      <h4 className="spacer-bottom">{translate('onboarding.language.java.build_technology')}</h4>
-      <RadioToggle
-        name="java-build"
-        onCheck={this.handleJavaBuildChange}
-        options={['maven', 'gradle'].map(build => ({
-          label: translate('onboarding.language.java.build_technology', build),
-          value: build
-        }))}
-        value={this.state.javaBuild}
-      />
-    </div>
-  );
-
-  renderCFamilyCompiler = () => (
-    <div className="big-spacer-top">
-      <h4 className="spacer-bottom">{translate('onboarding.language.c-family.compiler')}</h4>
-      <RadioToggle
-        name="c-family-compiler"
-        onCheck={this.handleCFamilyCompilerChange}
-        options={['msvc', 'clang-gcc'].map(compiler => ({
-          label: translate('onboarding.language.c-family.compiler', compiler),
-          value: compiler
-        }))}
-        value={this.state.cFamilyCompiler}
-      />
-    </div>
-  );
-
-  renderOS = () => (
-    <div className="big-spacer-top">
-      <h4 className="spacer-bottom">{translate('onboarding.language.os')}</h4>
-      <RadioToggle
-        name="os"
-        onCheck={this.handleOSChange}
-        options={['linux', 'win', 'mac'].map(os => ({
-          label: translate('onboarding.language.os', os),
-          value: os
-        }))}
-        value={this.state.os}
-      />
-    </div>
-  );
-
-  renderProjectKey = () => (
-    <NewProjectForm
-      onDelete={this.handleProjectKeyDelete}
-      onDone={this.handleProjectKeyDone}
-      organization={this.props.organization}
-      projectKey={this.state.projectKey}
-    />
-  );
-
-  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 = isSonarCloud()
-      ? ['java', 'dotnet', 'c-family', 'other']
-      : ['java', 'dotnet', 'other'];
-
-    return (
-      <div>
-        <div>
-          <h4 className="spacer-bottom">{translate('onboarding.language')}</h4>
-          <RadioToggle
-            name="language"
-            onCheck={this.handleLanguageChange}
-            options={languages.map(language => ({
-              label: translate('onboarding.language', language),
-              value: language
-            }))}
-            value={this.state.language}
-          />
-        </div>
-        {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()}
-      </div>
-    );
-  }
-}
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
deleted file mode 100644 (file)
index 11be750..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { debounce } from 'lodash';
-import {
-  createOrganization,
-  deleteOrganization,
-  getOrganization
-} from '../../../api/organizations';
-import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon';
-import { DeleteButton, SubmitButton } from '../../../components/ui/buttons';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {|
-  onDelete: () => void,
-  onDone: (organization: string) => void,
-  organization?: string
-|};
-*/
-
-/*::
-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 = () => {
-    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 ? (
-      <div>
-        <span className="spacer-right text-middle">{organization}</span>
-        {loading ? (
-          <i className="spinner text-middle" />
-        ) : (
-          <DeleteButton className="button-small" onClick={this.handleOrganizationDelete} />
-        )}
-      </div>
-    ) : (
-      <form onSubmit={this.handleOrganizationCreate}>
-        <input
-          autoFocus={true}
-          className="input-super-large spacer-right text-middle"
-          maxLength={32}
-          minLength={2}
-          onChange={this.handleOrganizationChange}
-          placeholder={translate('onboarding.organization.placeholder')}
-          required={true}
-          type="text"
-          value={organization}
-        />
-        {loading ? (
-          <i className="spinner text-middle" />
-        ) : (
-          <SubmitButton className="text-middle" disabled={!valid}>
-            {translate('create')}
-          </SubmitButton>
-        )}
-        {!unique && (
-          <span className="big-spacer-left text-danger text-middle">
-            <AlertErrorIcon className="little-spacer-right text-text-top" />
-            {translate('this_name_is_already_taken')}
-          </span>
-        )}
-        <div className="note spacer-top abs-width-300">
-          {translate('onboarding.organization.key_requirement')}
-        </div>
-      </form>
-    );
-  }
-}
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
deleted file mode 100644 (file)
index cde4629..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { createProject, deleteProject } from '../../../api/components';
-import { DeleteButton, SubmitButton } from '../../../components/ui/buttons';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {|
-  onDelete: () => void,
-  onDone: (projectKey: string) => void,
-  organization?: string,
-  projectKey?: string
-|};
-*/
-
-/*::
-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 = () => {
-    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 ? (
-      <div>
-        <span className="spacer-right text-middle">{projectKey}</span>
-        {loading ? (
-          <i className="spinner text-middle" />
-        ) : (
-          <DeleteButton className="button-small text-middle" onClick={this.handleProjectDelete} />
-        )}
-      </div>
-    ) : (
-      <form onSubmit={this.handleProjectCreate}>
-        <input
-          autoFocus={true}
-          className="input-large spacer-right text-middle"
-          maxLength={400}
-          minLength={1}
-          onChange={this.handleProjectKeyChange}
-          required={true}
-          type="text"
-          value={projectKey}
-        />
-        {loading ? (
-          <i className="spinner text-middle" />
-        ) : (
-          <SubmitButton className="text-middle" disabled={!valid}>
-            {translate('Done')}
-          </SubmitButton>
-        )}
-        <div className="note spacer-top abs-width-300">
-          {translate('onboarding.project_key_requirement')}
-        </div>
-      </form>
-    );
-
-    return (
-      <div className="big-spacer-top">
-        <h4 className="spacer-bottom">{translate('onboarding.language.project_key')}</h4>
-        {form}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js
deleted file mode 100644 (file)
index afcf049..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import PropTypes from 'prop-types';
-import Helmet from 'react-helmet';
-import TokenStep from './TokenStep';
-import OrganizationStep from './OrganizationStep';
-import AnalysisStep from './AnalysisStep';
-import ProjectWatcher from './ProjectWatcher';
-import DeferredSpinner from '../../../components/common/DeferredSpinner';
-import InstanceMessage from '../../../components/common/InstanceMessage';
-import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
-import { skipOnboarding } from '../../../api/users';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { getProjectUrl } from '../../../helpers/urls';
-import { isSonarCloud } from '../../../helpers/system';
-import './styles.css';
-
-/*::
-type Props = {|
-  automatic?:boolean,
-  className?: string,
-  currentUser: { login: string, isLoggedIn: boolean },
-  onFinish: () => void,
-  organizationsEnabled: boolean
-|};
-*/
-
-/*::
-type State = {
-  finished: boolean,
-  organization?: string,
-  projectKey?: string,
-  skipping: boolean,
-  step: string,
-  token?: string
-};
-*/
-
-export default class Onboarding extends React.PureComponent {
-  /*:: mounted: boolean; */
-  /*:: props: Props; */
-  /*:: state: State; */
-
-  static contextTypes = {
-    router: PropTypes.object
-  };
-
-  constructor(props /*: Props */) {
-    super(props);
-    this.state = {
-      finished: false,
-      skipping: false,
-      step: props.organizationsEnabled ? 'organization' : 'token'
-    };
-  }
-
-  componentDidMount() {
-    this.mounted = true;
-
-    // useCapture = true to receive the event before inputs
-    window.addEventListener('keydown', this.onKeyDown, true);
-
-    if (!this.props.currentUser.isLoggedIn) {
-      handleRequiredAuthentication();
-    }
-  }
-
-  componentWillUnmount() {
-    window.removeEventListener('keydown', this.onKeyDown, true);
-    this.mounted = false;
-  }
-
-  onKeyDown = (event /*: KeyboardEvent */) => {
-    // ESC key
-    if (event.keyCode === 27) {
-      this.finishOnboarding();
-    }
-  };
-
-  finishOnboarding = () => {
-    this.setState({ skipping: true });
-    skipOnboarding().then(
-      () => {
-        if (this.mounted) {
-          this.props.onFinish();
-
-          if (this.state.projectKey) {
-            this.context.router.push(getProjectUrl(this.state.projectKey));
-          }
-        }
-      },
-      () => {
-        if (this.mounted) {
-          this.setState({ skipping: false });
-        }
-      }
-    );
-  };
-
-  handleTimeout = () => {
-    // unset `projectKey` to display a generic "Finish this tutorial" button
-    this.setState({ projectKey: undefined });
-  };
-
-  handleTokenDone = (token /*: string */) => {
-    this.setState({ step: 'analysis', token });
-  };
-
-  handleOrganizationDone = (organization /*: string */) => {
-    this.setState({ organization, step: 'token' });
-  };
-
-  handleTokenOpen = () => this.setState({ step: 'token' });
-
-  handleOrganizationOpen = () => this.setState({ step: 'organization' });
-
-  handleSkipClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.finishOnboarding();
-  };
-
-  handleFinish = (projectKey /*: string | void */) => this.setState({ finished: true, projectKey });
-
-  handleReset = () => this.setState({ finished: false, projectKey: undefined });
-
-  render() {
-    if (!this.props.currentUser.isLoggedIn) {
-      return null;
-    }
-
-    const { automatic, organizationsEnabled } = this.props;
-    const { step, token } = this.state;
-    let stepNumber = 1;
-
-    return (
-      <div className={this.props.className}>
-        <InstanceMessage message={translate('onboarding.header')}>
-          {transformedMessage => <Helmet title={transformedMessage} titleTemplate="%s" />}
-        </InstanceMessage>
-
-        <div className="page page-limited onboarding">
-          <header className="page-header">
-            <h1 className="page-title">
-              <InstanceMessage message={translate('onboarding.header')} />
-            </h1>
-            <div className="page-actions">
-              <DeferredSpinner loading={this.state.skipping}>
-                <a className="js-skip text-muted" href="#" onClick={this.handleSkipClick}>
-                  {automatic ? translate('tutorials.skip') : translate('close')}
-                </a>
-              </DeferredSpinner>
-
-              <p className="note">
-                {translate(
-                  isSonarCloud()
-                    ? 'tutorials.find_it_back_in_plus'
-                    : 'tutorials.find_it_back_in_help'
-                )}
-              </p>
-            </div>
-            <div className="page-description">
-              {translateWithParameters(
-                'onboarding.header.description',
-                organizationsEnabled ? 3 : 2
-              )}
-            </div>
-          </header>
-
-          {organizationsEnabled && (
-            <OrganizationStep
-              currentUser={this.props.currentUser}
-              finished={this.state.organization != null}
-              onContinue={this.handleOrganizationDone}
-              onOpen={this.handleOrganizationOpen}
-              open={step === 'organization'}
-              stepNumber={stepNumber++}
-            />
-          )}
-
-          <TokenStep
-            currentUser={this.props.currentUser}
-            finished={this.state.token != null}
-            onContinue={this.handleTokenDone}
-            onOpen={this.handleTokenOpen}
-            open={step === 'token'}
-            stepNumber={stepNumber++}
-          />
-
-          <AnalysisStep
-            onFinish={this.handleFinish}
-            onReset={this.handleReset}
-            open={step === 'analysis'}
-            organization={this.state.organization}
-            stepNumber={stepNumber}
-            token={token}
-          />
-
-          {this.state.finished &&
-            !this.state.skipping &&
-            (this.state.projectKey ? (
-              <ProjectWatcher
-                onFinish={this.finishOnboarding}
-                onTimeout={this.handleTimeout}
-                projectKey={this.state.projectKey}
-              />
-            ) : (
-              <footer className="text-right">
-                <a className="button" href="#" onClick={this.handleSkipClick}>
-                  {translate('tutorials.finish')}
-                </a>
-              </footer>
-            ))}
-        </div>
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingContainer.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingContainer.js
deleted file mode 100644 (file)
index cf27d5d..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import { connect } from 'react-redux';
-import Onboarding from './Onboarding';
-import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer';
-
-const mapStateToProps = state => {
-  return {
-    className: 'modal-container',
-    currentUser: getCurrentUser(state),
-    organizationsEnabled: areThereCustomOrganizations(state)
-  };
-};
-
-export default connect(mapStateToProps)(Onboarding);
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.d.ts b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.d.ts
deleted file mode 100644 (file)
index 4b70adc..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import * as React from 'react';
-
-export interface Props {
-  onFinish: () => void;
-}
-
-export default class OnboardingModal extends React.PureComponent<Props> {}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx
deleted file mode 100644 (file)
index 188e5cc..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import * as React from 'react';
-import Modal from '../../../components/controls/Modal';
-import { translate } from '../../../helpers/l10n';
-import { lazyLoad } from '../../../components/lazyLoad';
-
-interface Props {
-  automatic?: boolean;
-  onFinish: () => void;
-}
-
-const OnboardingContainer = lazyLoad(() => import('./OnboardingContainer'));
-
-export default function OnboardingModal(props: Props) {
-  return (
-    <Modal contentLabel={translate('tutorials.onboarding')} large={true}>
-      <OnboardingContainer {...props} />
-    </Modal>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx
deleted file mode 100644 (file)
index 971ae8b..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import * as React from 'react';
-import * as PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import OnboardingModal from './OnboardingModal';
-import { skipOnboarding } from '../../../store/users/actions';
-
-interface DispatchProps {
-  skipOnboarding: () => void;
-}
-
-export class OnboardingPage extends React.PureComponent<DispatchProps> {
-  static contextTypes = {
-    router: PropTypes.object.isRequired
-  };
-
-  onSkipOnboardingTutorial = () => {
-    this.props.skipOnboarding();
-    this.context.router.replace('/');
-  };
-
-  render() {
-    return <OnboardingModal onFinish={this.onSkipOnboardingTutorial} />;
-  }
-}
-
-const mapDispatchToProps: DispatchProps = { skipOnboarding };
-
-export default connect<{}, DispatchProps, {}>(null, mapDispatchToProps)(OnboardingPage);
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
deleted file mode 100644 (file)
index e55c34a..0000000
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import { sortBy } from 'lodash';
-import Step from './Step';
-import NewOrganizationForm from './NewOrganizationForm';
-import DocTooltip from '../../../components/docs/DocTooltip';
-import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon';
-import { getOrganizations } from '../../../api/organizations';
-import Select from '../../../components/controls/Select';
-import { translate } from '../../../helpers/l10n';
-import { Button } from '../../../components/ui/buttons';
-
-/*::
-type Props = {|
-  currentUser: { login: string, isLoggedIn: boolean },
-  finished: boolean,
-  onOpen: () => void,
-  onContinue: (organization: string) => void,
-  open: boolean,
-  stepNumber: number
-|};
-*/
-
-/*::
-type State = {
-  loading: boolean,
-  newOrganization?: string,
-  existingOrganization?: string,
-  existingOrganizations: Array<string>,
-  personalOrganization?: string,
-  selection: 'personal' | 'existing' | 'new'
-};
-*/
-
-export default class OrganizationStep extends React.PureComponent {
-  /*:: mounted: boolean; */
-  /*:: props: Props; */
-  state /*: State */ = {
-    loading: true,
-    existingOrganizations: [],
-    selection: 'personal'
-  };
-
-  componentDidMount() {
-    this.mounted = true;
-    this.fetchOrganizations();
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  fetchOrganizations = () => {
-    getOrganizations({ member: true }).then(
-      ({ organizations }) => {
-        if (this.mounted) {
-          const organizationKeys = organizations.filter(o => o.isAdmin).map(o => o.key);
-          // best guess: if there is only one organization, then it is personal
-          // otherwise, we can't guess, let's display them all as just "existing organizations"
-          const personalOrganization =
-            organizationKeys.length === 1 ? organizationKeys[0] : undefined;
-          const existingOrganizations = organizationKeys.length > 1 ? sortBy(organizationKeys) : [];
-          const selection = personalOrganization
-            ? 'personal'
-            : existingOrganizations.length > 0 ? 'existing' : 'new';
-          this.setState({
-            loading: false,
-            existingOrganizations,
-            personalOrganization,
-            selection
-          });
-        }
-      },
-      () => {
-        if (this.mounted) {
-          this.setState({ loading: false });
-        }
-      }
-    );
-  };
-
-  getSelectedOrganization = () => {
-    switch (this.state.selection) {
-      case 'personal':
-        return this.state.personalOrganization;
-      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 = () => {
-    const organization = this.getSelectedOrganization();
-    if (organization) {
-      this.props.onContinue(organization);
-    }
-  };
-
-  renderPersonalOrganizationOption = () => (
-    <div>
-      <a className="link-base-color link-no-underline" href="#" onClick={this.handlePersonalClick}>
-        <i
-          className={classNames('icon-radio', 'spacer-right', {
-            'is-checked': this.state.selection === 'personal'
-          })}
-        />
-        {translate('onboarding.organization.my_personal_organization')}
-        <span className="note spacer-left">{this.state.personalOrganization}</span>
-      </a>
-    </div>
-  );
-
-  renderExistingOrganizationOption = () => (
-    <div className="big-spacer-top">
-      <a
-        className="js-existing link-base-color link-no-underline"
-        href="#"
-        onClick={this.handleExistingClick}>
-        <i
-          className={classNames('icon-radio', 'spacer-right', {
-            'is-checked': this.state.selection === 'existing'
-          })}
-        />
-        {translate('onboarding.organization.exising_organization')}
-      </a>
-      {this.state.selection === 'existing' && (
-        <div className="big-spacer-top">
-          <Select
-            className="input-super-large"
-            clearable={false}
-            onChange={this.handleExistingOrganizationSelect}
-            options={this.state.existingOrganizations.map(organization => ({
-              label: organization,
-              value: organization
-            }))}
-            value={this.state.existingOrganization}
-          />
-        </div>
-      )}
-    </div>
-  );
-
-  renderNewOrganizationOption = () => (
-    <div className="big-spacer-top">
-      <a
-        className="js-new link-base-color link-no-underline"
-        href="#"
-        onClick={this.handleNewClick}>
-        <i
-          className={classNames('icon-radio', 'spacer-right', {
-            'is-checked': this.state.selection === 'new'
-          })}
-        />
-        {translate('onboarding.organization.create_another_organization')}
-      </a>
-      {this.state.selection === 'new' && (
-        <div className="big-spacer-top">
-          <NewOrganizationForm
-            onDelete={this.handleOrganizationDelete}
-            onDone={this.handleOrganizationCreate}
-            organization={this.state.newOrganization}
-          />
-        </div>
-      )}
-    </div>
-  );
-
-  renderForm = () => {
-    return (
-      <div className="boxed-group-inner">
-        <div className="big-spacer-bottom width-50">
-          {translate('onboarding.organization.text')}
-        </div>
-
-        {this.state.loading ? (
-          <i className="spinner" />
-        ) : (
-          <div>
-            {this.state.personalOrganization && this.renderPersonalOrganizationOption()}
-            {this.state.existingOrganizations.length > 0 && this.renderExistingOrganizationOption()}
-            {this.renderNewOrganizationOption()}
-          </div>
-        )}
-
-        {this.getSelectedOrganization() != null &&
-          !this.state.loading && (
-            <div className="big-spacer-top">
-              <Button className="js-continue" onClick={this.handleContinueClick}>
-                {translate('continue')}
-              </Button>
-            </div>
-          )}
-      </div>
-    );
-  };
-
-  renderResult = () => {
-    const result = this.getSelectedOrganization();
-
-    return result != null ? (
-      <div className="boxed-group-actions display-flex-center">
-        <AlertSuccessIcon className="spacer-right" />
-        <strong>{result}</strong>
-      </div>
-    ) : null;
-  };
-
-  render() {
-    return (
-      <Step
-        finished={this.props.finished}
-        onOpen={this.props.onOpen}
-        open={this.props.open}
-        renderForm={this.renderForm}
-        renderResult={this.renderResult}
-        stepNumber={this.props.stepNumber}
-        stepTitle={
-          <span>
-            {translate('onboarding.organization.header')}
-            <DocTooltip className="little-spacer-left" doc="organizations/organization" />
-          </span>
-        }
-      />
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/ProjectWatcher.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/ProjectWatcher.js
deleted file mode 100644 (file)
index e836e98..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon';
-import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon';
-import { getTasksForComponent } from '../../../api/ce';
-import { STATUSES } from '../../../apps/background-tasks/constants';
-import { translate } from '../../../helpers/l10n';
-
-const INTERVAL = 5000;
-const TIMEOUT = 10 * 60 * 1000; // 10 min
-
-/*::
-type Props = {
-  onFinish: () => void,
-  onTimeout: () => void,
-  projectKey: string
-};
-*/
-
-/*::
-type State = {
-  inQueue: boolean,
-  status: ?string
-};
-*/
-
-export default class ProjectWatcher extends React.PureComponent {
-  /*:: interval: number; */
-  /*:: mounted: boolean; */
-  /*:: props: Props; */
-  /*:: timeout: number; */
-  state /*: State */ = {
-    inQueue: false,
-    status: null
-  };
-
-  componentDidMount() {
-    this.mounted = true;
-    this.watch();
-    this.timeout = setTimeout(this.props.onTimeout, TIMEOUT);
-  }
-
-  componentWillUnmount() {
-    clearInterval(this.interval);
-    clearInterval(this.timeout);
-    this.mounted = false;
-  }
-
-  watch = () => (this.interval = setTimeout(this.checkProject, INTERVAL));
-
-  checkProject = () => {
-    const { projectKey } = this.props;
-    getTasksForComponent(projectKey).then(
-      response => {
-        if (response.queue.length > 0) {
-          this.setState({ inQueue: true });
-        }
-
-        if (response.current != null) {
-          const { status } = response.current;
-          this.setState({ status });
-          if (status === STATUSES.SUCCESS) {
-            this.props.onFinish();
-          } else if (status === STATUSES.PENDING || status === STATUSES.IN_PROGRESS) {
-            this.watch();
-          }
-        } else {
-          this.watch();
-        }
-      },
-      () => {}
-    );
-  };
-
-  render() {
-    const { inQueue, status } = this.state;
-
-    if (status === STATUSES.SUCCESS) {
-      return (
-        <div className="big-spacer-top note text-center">
-          <AlertSuccessIcon className="spacer-right" />
-          {translate('onboarding.project_watcher.finished')}
-        </div>
-      );
-    }
-
-    if (inQueue || status === STATUSES.PENDING || status === STATUSES.IN_PROGRESS) {
-      return (
-        <div className="big-spacer-top note text-center">
-          <i className="spinner spacer-right" />
-          {translate('onboarding.project_watcher.in_progress')}
-        </div>
-      );
-    }
-
-    if (status != null) {
-      return (
-        <div className="big-spacer-top note text-center">
-          <AlertErrorIcon className="spacer-right" />
-          {translate('onboarding.project_watcher.failed')}
-        </div>
-      );
-    }
-
-    return (
-      <div className="big-spacer-top note text-center">
-        {translate('onboarding.project_watcher.not_started')}
-      </div>
-    );
-  }
-}
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
deleted file mode 100644 (file)
index d9f6394..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-
-/*::
-type Props = {|
-  finished: boolean,
-  onOpen: () => void,
-  open: boolean,
-  renderForm: () => React.Element<*>,
-  renderResult: () => ?React.Element<*>,
-  stepNumber: number,
-  stepTitle: React.Element<*> | string
-|};
-*/
-
-export default function Step(props /*: Props */) {
-  const className = classNames('boxed-group', 'onboarding-step', {
-    'is-open': props.open,
-    'is-finished': props.finished
-  });
-
-  const clickable = !props.open && props.finished;
-
-  const handleClick = (event /*: Event */) => {
-    event.preventDefault();
-    props.onOpen();
-  };
-
-  return (
-    <div
-      className={className}
-      onClick={clickable ? handleClick : undefined}
-      role={clickable ? 'button' : undefined}
-      tabIndex={clickable ? 0 : undefined}>
-      <div className="onboarding-step-number">{props.stepNumber}</div>
-      {!props.open && props.renderResult()}
-      <div className="boxed-group-header">
-        <h2>{props.stepTitle}</h2>
-      </div>
-      {props.open ? props.renderForm() : <div className="boxed-group-inner" />}
-    </div>
-  );
-}
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
deleted file mode 100644 (file)
index 6ef86cd..0000000
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import Step from './Step';
-import { getTokens, generateToken, revokeToken } from '../../../api/user-tokens';
-import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon';
-import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon';
-import { DeleteButton, SubmitButton, Button } from '../../../components/ui/buttons';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {|
-  currentUser: { login: string },
-  finished: boolean,
-  open: boolean,
-  onContinue: (token: string) => void,
-  onOpen: () => void,
-  stepNumber: number
-|};
-*/
-
-/*::
-type State = {
-  canUseExisting: boolean,
-  existingToken: string,
-  loading: boolean,
-  selection: string,
-  tokenName?: string,
-  token?: string
-};
-*/
-
-export default class TokenStep extends React.PureComponent {
-  /*:: mounted: boolean; */
-  /*:: props: Props; */
-  state /*: State */ = {
-    canUseExisting: false,
-    existingToken: '',
-    loading: false,
-    selection: 'generate'
-  };
-
-  componentDidMount() {
-    this.mounted = true;
-    getTokens(this.props.currentUser.login).then(
-      tokens => {
-        if (this.mounted) {
-          this.setState({ canUseExisting: tokens.length > 0 });
-        }
-      },
-      () => {}
-    );
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  getToken = () =>
-    this.state.selection === 'generate' ? this.state.token : this.state.existingToken;
-
-  canContinue = () => {
-    const { existingToken, selection, token } = this.state;
-    const validExistingToken = existingToken.match(/^[a-z0-9]+$/) != null;
-    return (
-      (selection === 'generate' && token != null) ||
-      (selection === 'use-existing' && existingToken && validExistingToken)
-    );
-  };
-
-  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({ name: tokenName }).then(
-        ({ token }) => {
-          if (this.mounted) {
-            this.setState({ loading: false, token });
-          }
-        },
-        () => {
-          if (this.mounted) {
-            this.setState({ loading: false });
-          }
-        }
-      );
-    }
-  };
-
-  handleTokenRevoke = () => {
-    const { tokenName } = this.state;
-    if (tokenName) {
-      this.setState({ loading: true });
-      revokeToken({ name: tokenName }).then(
-        () => {
-          if (this.mounted) {
-            this.setState({ loading: false, token: undefined, tokenName: undefined });
-          }
-        },
-        () => {
-          if (this.mounted) {
-            this.setState({ loading: false });
-          }
-        }
-      );
-    }
-  };
-
-  handleContinueClick = () => {
-    const token = this.getToken();
-    if (token) {
-      this.props.onContinue(token);
-    }
-  };
-
-  handleGenerateClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.setState({ selection: 'generate' });
-  };
-
-  handleUseExistingClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.setState({ selection: 'use-existing' });
-  };
-
-  handleExisingTokenChange = (event /*: { currentTarget: HTMLInputElement } */) => {
-    this.setState({ existingToken: event.currentTarget.value });
-  };
-
-  renderGenerateOption = () => (
-    <div>
-      {this.state.canUseExisting ? (
-        <a
-          className="js-new link-base-color link-no-underline"
-          href="#"
-          onClick={this.handleGenerateClick}>
-          <i
-            className={classNames('icon-radio', 'spacer-right', {
-              'is-checked': this.state.selection === 'generate'
-            })}
-          />
-          {translate('onboading.token.generate_token')}
-        </a>
-      ) : (
-        translate('onboading.token.generate_token')
-      )}
-      {this.state.selection === 'generate' && (
-        <div className="big-spacer-top">
-          <form onSubmit={this.handleTokenGenerate}>
-            <input
-              autoFocus={true}
-              className="input-large spacer-right text-middle"
-              onChange={this.handleTokenNameChange}
-              placeholder={translate('onboading.token.generate_token.placeholder')}
-              required={true}
-              type="text"
-              value={this.state.tokenName || ''}
-            />
-            {this.state.loading ? (
-              <i className="spinner text-middle" />
-            ) : (
-              <SubmitButton className="text-middle" disabled={!this.state.tokenName}>
-                {translate('onboarding.token.generate')}
-              </SubmitButton>
-            )}
-          </form>
-        </div>
-      )}
-    </div>
-  );
-
-  renderUseExistingOption = () => {
-    const { existingToken } = this.state;
-    const validInput = !existingToken || existingToken.match(/^[a-z0-9]+$/) != null;
-
-    return (
-      <div className="big-spacer-top">
-        <a
-          className="js-new link-base-color link-no-underline"
-          href="#"
-          onClick={this.handleUseExistingClick}>
-          <i
-            className={classNames('icon-radio', 'spacer-right', {
-              'is-checked': this.state.selection === 'use-existing'
-            })}
-          />
-          {translate('onboarding.token.use_existing_token')}
-        </a>
-        {this.state.selection === 'use-existing' && (
-          <div className="big-spacer-top">
-            <input
-              autoFocus={true}
-              className="input-large spacer-right text-middle"
-              onChange={this.handleExisingTokenChange}
-              placeholder={translate('onboarding.token.use_existing_token.placeholder')}
-              required={true}
-              type="text"
-              value={this.state.existingToken}
-            />
-            {!validInput && (
-              <span className="text-danger">
-                <AlertErrorIcon className="little-spacer-right text-text-top" />
-                {translate('onboarding.token.invalid_format')}
-              </span>
-            )}
-          </div>
-        )}
-      </div>
-    );
-  };
-
-  renderForm = () => {
-    const { canUseExisting, loading, token, tokenName } = this.state;
-
-    return (
-      <div className="boxed-group-inner">
-        {token != null ? (
-          <form onSubmit={this.handleTokenRevoke}>
-            <span className="text-middle">
-              {tokenName}
-              {': '}
-            </span>
-            <strong className="spacer-right text-middle">{token}</strong>
-            {loading ? (
-              <i className="spinner text-middle" />
-            ) : (
-              <DeleteButton className="button-small text-middle" onClick={this.handleTokenRevoke} />
-            )}
-          </form>
-        ) : (
-          <div>
-            {this.renderGenerateOption()}
-            {canUseExisting && this.renderUseExistingOption()}
-          </div>
-        )}
-
-        <div className="note big-spacer-top width-50">{translate('onboarding.token.text')}</div>
-
-        {this.canContinue() && (
-          <div className="big-spacer-top">
-            <Button className="js-continue" onClick={this.handleContinueClick}>
-              {translate('continue')}
-            </Button>
-          </div>
-        )}
-      </div>
-    );
-  };
-
-  renderResult = () => {
-    const { selection, tokenName } = this.state;
-    const token = this.getToken();
-
-    if (!token) {
-      return null;
-    }
-
-    return (
-      <div className="boxed-group-actions display-flex-center">
-        <AlertSuccessIcon className="spacer-right" />
-        {selection === 'generate' && tokenName && `${tokenName}: `}
-        <strong>{token}</strong>
-      </div>
-    );
-  };
-
-  render() {
-    return (
-      <Step
-        finished={this.props.finished}
-        onOpen={this.props.onOpen}
-        open={this.props.open}
-        renderForm={this.renderForm}
-        renderResult={this.renderResult}
-        stepNumber={this.props.stepNumber}
-        stepTitle={translate('onboarding.token.header')}
-      />
-    );
-  }
-}
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
deleted file mode 100644 (file)
index 2609b55..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import LanguageStep from '../LanguageStep';
-import { isSonarCloud } from '../../../../helpers/system';
-
-jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
-
-beforeEach(() => {
-  isSonarCloud.mockImplementation(() => false);
-});
-
-it('selects java', () => {
-  const onDone = jest.fn();
-  const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />);
-
-  wrapper.find('RadioToggle').prop('onCheck')('java');
-  wrapper.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(<LanguageStep onDone={onDone} onReset={jest.fn()} />);
-
-  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', () => {
-  isSonarCloud.mockImplementation(() => true);
-  const onDone = jest.fn();
-  const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />);
-
-  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(<LanguageStep onDone={onDone} onReset={jest.fn()} />);
-
-  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
deleted file mode 100644 (file)
index fcfbf8d..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { mount } from 'enzyme';
-import NewOrganizationForm from '../NewOrganizationForm';
-import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils';
-
-jest.mock('../../../../api/organizations', () => ({
-  createOrganization: () => Promise.resolve(),
-  deleteOrganization: () => Promise.resolve(),
-  getOrganization: () => Promise.resolve(null)
-}));
-
-jest.mock('../../../../components/icons-components/ClearIcon');
-
-it('creates new organization', async () => {
-  const onDone = jest.fn();
-  const wrapper = mount(<NewOrganizationForm onDelete={jest.fn()} onDone={onDone} />);
-  expect(wrapper).toMatchSnapshot();
-  change(wrapper.find('input'), 'foo');
-  submit(wrapper.find('form'));
-  expect(wrapper).toMatchSnapshot(); // spinner
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-  expect(onDone).toBeCalledWith('foo');
-});
-
-it('deletes organization', async () => {
-  const onDelete = jest.fn();
-  const wrapper = mount(<NewOrganizationForm onDelete={onDelete} onDone={jest.fn()} />);
-  wrapper.setState({ done: true, loading: false, organization: 'foo' });
-  expect(wrapper).toMatchSnapshot();
-  wrapper.find('DeleteButton').prop('onClick')();
-  wrapper.update();
-  expect(wrapper).toMatchSnapshot(); // spinner
-  await waitAndUpdate(wrapper);
-  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
deleted file mode 100644 (file)
index 2d290f1..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { mount } from 'enzyme';
-import NewProjectForm from '../NewProjectForm';
-import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils';
-
-jest.mock('../../../../api/components', () => ({
-  createProject: () => Promise.resolve(),
-  deleteProject: () => Promise.resolve()
-}));
-
-jest.mock('../../../../components/icons-components/ClearIcon');
-
-it('creates new project', async () => {
-  const onDone = jest.fn();
-  const wrapper = mount(<NewProjectForm onDelete={jest.fn()} onDone={onDone} />);
-  expect(wrapper).toMatchSnapshot();
-  change(wrapper.find('input'), 'foo');
-  submit(wrapper.find('form'));
-  expect(wrapper).toMatchSnapshot(); // spinner
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-  expect(onDone).toBeCalledWith('foo');
-});
-
-it('deletes project', async () => {
-  const onDelete = jest.fn();
-  const wrapper = mount(<NewProjectForm onDelete={onDelete} onDone={jest.fn()} />);
-  wrapper.setState({ done: true, loading: false, projectKey: 'foo' });
-  expect(wrapper).toMatchSnapshot();
-  wrapper.find('DeleteButton').prop('onClick')();
-  wrapper.update();
-  expect(wrapper).toMatchSnapshot(); // spinner
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-  expect(onDelete).toBeCalled();
-});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/Onboarding-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/Onboarding-test.js
deleted file mode 100644 (file)
index e3acb2a..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow, mount } from 'enzyme';
-import Onboarding from '../Onboarding';
-import { click, doAsync } from '../../../../helpers/testUtils';
-import { getInstance, isSonarCloud } from '../../../../helpers/system';
-
-jest.mock('../../../../api/users', () => ({
-  skipOnboarding: () => Promise.resolve()
-}));
-
-jest.mock('../../../../helpers/system', () => ({
-  getInstance: jest.fn(),
-  isSonarCloud: jest.fn()
-}));
-
-const currentUser = { login: 'admin', isLoggedIn: true };
-
-it('guides for on-premise', () => {
-  getInstance.mockImplementation(() => 'SonarQube');
-  isSonarCloud.mockImplementation(() => false);
-  const wrapper = shallow(
-    <Onboarding
-      className="modal-container"
-      currentUser={currentUser}
-      onFinish={jest.fn()}
-      organizationsEnabled={false}
-    />
-  );
-  expect(wrapper).toMatchSnapshot();
-
-  // $FlowFixMe
-  wrapper.instance().handleTokenDone('abcd1234');
-  wrapper.update();
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('guides for sonarcloud', () => {
-  getInstance.mockImplementation(() => 'SonarCloud');
-  isSonarCloud.mockImplementation(() => true);
-  const wrapper = shallow(
-    <Onboarding currentUser={currentUser} onFinish={jest.fn()} organizationsEnabled={true} />
-  );
-  expect(wrapper).toMatchSnapshot();
-
-  // $FlowFixMe
-  wrapper.instance().handleOrganizationDone('my-org');
-  wrapper.update();
-  expect(wrapper).toMatchSnapshot();
-
-  // $FlowFixMe
-  wrapper.instance().handleTokenDone('abcd1234');
-  wrapper.update();
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('finishes', () => {
-  getInstance.mockImplementation(() => 'SonarQube');
-  isSonarCloud.mockImplementation(() => false);
-  const onFinish = jest.fn();
-  const wrapper = mount(
-    <Onboarding currentUser={currentUser} onFinish={onFinish} organizationsEnabled={false} />
-  );
-  click(wrapper.find('.js-skip'));
-  return doAsync(() => {
-    expect(onFinish).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
deleted file mode 100644 (file)
index 01a5b71..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { mount } from 'enzyme';
-import OrganizationStep from '../OrganizationStep';
-import { click, waitAndUpdate } from '../../../../helpers/testUtils';
-import { getOrganizations } from '../../../../api/organizations';
-
-jest.mock('../../../../api/organizations', () => ({
-  getOrganizations: jest.fn(() =>
-    Promise.resolve({
-      organizations: [{ isAdmin: true, key: 'user' }, { isAdmin: true, key: 'another' }]
-    })
-  )
-}));
-
-const currentUser = { isLoggedIn: true, login: 'user' };
-
-beforeEach(() => {
-  getOrganizations.mockClear();
-});
-
-// FIXME
-// - if `mount` is used, then it's not possible to correctly set the state,
-//   because the mocked api call is used
-// - if `shallow` is used, then the continue button is not rendered
-it.skip('works with personal organization', () => {
-  const onContinue = jest.fn();
-  const wrapper = mount(
-    <OrganizationStep
-      currentUser={currentUser}
-      finished={false}
-      onContinue={onContinue}
-      onOpen={jest.fn()}
-      open={true}
-      stepNumber={1}
-    />
-  );
-  click(wrapper.find('.js-continue'));
-  expect(onContinue).toBeCalledWith('user');
-});
-
-it('works with existing organization', async () => {
-  const onContinue = jest.fn();
-  const wrapper = mount(
-    <OrganizationStep
-      currentUser={currentUser}
-      finished={false}
-      onContinue={onContinue}
-      onOpen={jest.fn()}
-      open={true}
-      stepNumber={1}
-    />
-  );
-  await waitAndUpdate(wrapper);
-  click(wrapper.find('.js-existing'));
-  expect(wrapper).toMatchSnapshot();
-  wrapper
-    .find('Select')
-    .first()
-    .prop('onChange')({ value: 'another' });
-  wrapper.update();
-  click(wrapper.find('[className="js-continue"]'));
-  expect(onContinue).toBeCalledWith('another');
-});
-
-it('works with new organization', async () => {
-  const onContinue = jest.fn();
-  const wrapper = mount(
-    <OrganizationStep
-      currentUser={currentUser}
-      finished={false}
-      onContinue={onContinue}
-      onOpen={jest.fn()}
-      open={true}
-      stepNumber={1}
-    />
-  );
-  await waitAndUpdate(wrapper);
-  click(wrapper.find('.js-new'));
-  wrapper.find('NewOrganizationForm').prop('onDone')('new');
-  wrapper.update();
-  click(wrapper.find('[className="js-continue"]'));
-  expect(onContinue).toBeCalledWith('new');
-});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/ProjectWatcher-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/ProjectWatcher-test.js
deleted file mode 100644 (file)
index cc6699f..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow, mount } from 'enzyme';
-import ProjectWatcher from '../ProjectWatcher';
-
-jest.mock('../../../../api/ce', () => ({
-  getTasksForComponent: () => Promise.resolve({ current: { status: 'SUCCESS' }, queue: [] })
-}));
-
-jest.useFakeTimers();
-
-it('renders', () => {
-  const wrapper = shallow(
-    <ProjectWatcher onFinish={jest.fn()} onTimeout={jest.fn()} projectKey="foo" />
-  );
-  expect(wrapper).toMatchSnapshot();
-  wrapper.setState({ inQueue: true });
-  expect(wrapper).toMatchSnapshot();
-  wrapper.setState({ status: 'SUCCESS' });
-  expect(wrapper).toMatchSnapshot();
-  wrapper.setState({ status: 'FAILED' });
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('finishes', done => {
-  // checking `expect(onFinish).toBeCalled();` is not working, because it's called asynchronously
-  // instead let's finish the test as soon as `onFinish` callback is called
-  const onFinish = jest.fn(done);
-  mount(<ProjectWatcher onFinish={onFinish} onTimeout={jest.fn()} projectKey="foo" />);
-  expect(onFinish).not.toBeCalled();
-  jest.runTimersToTime(5000);
-});
-
-it('timeouts', () => {
-  const onTimeout = jest.fn();
-  mount(<ProjectWatcher onFinish={jest.fn()} onTimeout={onTimeout} projectKey="foo" />);
-  expect(onTimeout).not.toBeCalled();
-  jest.runTimersToTime(10 * 60 * 1000);
-  expect(onTimeout).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
deleted file mode 100644 (file)
index 396c4fa..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import Step from '../Step';
-import { click } from '../../../../helpers/testUtils';
-
-it('renders', () => {
-  const wrapper = shallow(
-    <Step
-      finished={true}
-      onOpen={jest.fn()}
-      open={true}
-      renderForm={() => <div>form</div>}
-      renderResult={() => <div>result</div>}
-      stepNumber={1}
-      stepTitle="First Step"
-    />
-  );
-  expect(wrapper).toMatchSnapshot();
-  wrapper.setProps({ open: false });
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('re-opens', () => {
-  const onOpen = jest.fn();
-  const wrapper = shallow(
-    <Step
-      finished={true}
-      onOpen={onOpen}
-      open={false}
-      renderForm={() => <div>form</div>}
-      renderResult={() => <div>result</div>}
-      stepNumber={1}
-      stepTitle="First Step"
-    />
-  );
-  click(wrapper);
-  expect(onOpen).toBeCalled();
-});
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
deleted file mode 100644 (file)
index 4f439b4..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { mount } from 'enzyme';
-import TokenStep from '../TokenStep';
-import { change, click, submit, waitAndUpdate } from '../../../../helpers/testUtils';
-
-jest.mock('../../../../api/user-tokens', () => ({
-  getTokens: () => Promise.resolve([{ name: 'foo' }]),
-  generateToken: () => Promise.resolve({ token: 'abcd1234' }),
-  revokeToken: () => Promise.resolve()
-}));
-
-jest.mock('../../../../components/icons-components/ClearIcon');
-
-const currentUser = { login: 'user' };
-
-it('generates token', async () => {
-  const wrapper = mount(
-    <TokenStep
-      currentUser={currentUser}
-      finished={false}
-      onContinue={jest.fn()}
-      onOpen={jest.fn()}
-      open={true}
-      stepNumber={1}
-    />
-  );
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-  change(wrapper.find('input'), 'my token');
-  submit(wrapper.find('form'));
-  expect(wrapper).toMatchSnapshot(); // spinner
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('revokes token', async () => {
-  const wrapper = mount(
-    <TokenStep
-      currentUser={currentUser}
-      finished={false}
-      onContinue={jest.fn()}
-      onOpen={jest.fn()}
-      open={true}
-      stepNumber={1}
-    />
-  );
-  await new Promise(setImmediate);
-  wrapper.setState({ token: 'abcd1234', tokenName: 'my token' });
-  expect(wrapper).toMatchSnapshot();
-  wrapper.find('DeleteButton').prop('onClick')();
-  wrapper.update();
-  expect(wrapper).toMatchSnapshot(); // spinner
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('continues', async () => {
-  const onContinue = jest.fn();
-  const wrapper = mount(
-    <TokenStep
-      currentUser={currentUser}
-      finished={false}
-      onContinue={onContinue}
-      onOpen={jest.fn()}
-      open={true}
-      stepNumber={1}
-    />
-  );
-  await new Promise(setImmediate);
-  wrapper.setState({ token: 'abcd1234', tokenName: 'my token' });
-  click(wrapper.find('[className="js-continue"]'));
-  expect(onContinue).toBeCalledWith('abcd1234');
-});
-
-it('uses existing token', async () => {
-  const onContinue = jest.fn();
-  const wrapper = mount(
-    <TokenStep
-      currentUser={currentUser}
-      finished={false}
-      onContinue={onContinue}
-      onOpen={jest.fn()}
-      open={true}
-      stepNumber={1}
-    />
-  );
-  await new Promise(setImmediate);
-  wrapper.setState({ existingToken: 'abcd1234', selection: 'use-existing' });
-  click(wrapper.find('[className="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
deleted file mode 100644 (file)
index 8e82d38..0000000
+++ /dev/null
@@ -1,687 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`selects c# 1`] = `
-<div>
-  <div>
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="language"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.java",
-            "value": "java",
-          },
-          Object {
-            "label": "onboarding.language.dotnet",
-            "value": "dotnet",
-          },
-          Object {
-            "label": "onboarding.language.other",
-            "value": "other",
-          },
-        ]
-      }
-      value="dotnet"
-    />
-  </div>
-  <NewProjectForm
-    onDelete={[Function]}
-    onDone={[Function]}
-  />
-</div>
-`;
-
-exports[`selects c-family 1`] = `
-<div>
-  <div>
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="language"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.java",
-            "value": "java",
-          },
-          Object {
-            "label": "onboarding.language.dotnet",
-            "value": "dotnet",
-          },
-          Object {
-            "label": "onboarding.language.c-family",
-            "value": "c-family",
-          },
-          Object {
-            "label": "onboarding.language.other",
-            "value": "other",
-          },
-        ]
-      }
-      value="c-family"
-    />
-  </div>
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.c-family.compiler
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="c-family-compiler"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.c-family.compiler.msvc",
-            "value": "msvc",
-          },
-          Object {
-            "label": "onboarding.language.c-family.compiler.clang-gcc",
-            "value": "clang-gcc",
-          },
-        ]
-      }
-      value={null}
-    />
-  </div>
-</div>
-`;
-
-exports[`selects c-family 2`] = `
-<div>
-  <div>
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="language"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.java",
-            "value": "java",
-          },
-          Object {
-            "label": "onboarding.language.dotnet",
-            "value": "dotnet",
-          },
-          Object {
-            "label": "onboarding.language.c-family",
-            "value": "c-family",
-          },
-          Object {
-            "label": "onboarding.language.other",
-            "value": "other",
-          },
-        ]
-      }
-      value="c-family"
-    />
-  </div>
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.c-family.compiler
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="c-family-compiler"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.c-family.compiler.msvc",
-            "value": "msvc",
-          },
-          Object {
-            "label": "onboarding.language.c-family.compiler.clang-gcc",
-            "value": "clang-gcc",
-          },
-        ]
-      }
-      value="msvc"
-    />
-  </div>
-  <NewProjectForm
-    onDelete={[Function]}
-    onDone={[Function]}
-  />
-</div>
-`;
-
-exports[`selects c-family 3`] = `
-<div>
-  <div>
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="language"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.java",
-            "value": "java",
-          },
-          Object {
-            "label": "onboarding.language.dotnet",
-            "value": "dotnet",
-          },
-          Object {
-            "label": "onboarding.language.c-family",
-            "value": "c-family",
-          },
-          Object {
-            "label": "onboarding.language.other",
-            "value": "other",
-          },
-        ]
-      }
-      value="c-family"
-    />
-  </div>
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.c-family.compiler
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="c-family-compiler"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.c-family.compiler.msvc",
-            "value": "msvc",
-          },
-          Object {
-            "label": "onboarding.language.c-family.compiler.clang-gcc",
-            "value": "clang-gcc",
-          },
-        ]
-      }
-      value="clang-gcc"
-    />
-  </div>
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.os
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="os"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.os.linux",
-            "value": "linux",
-          },
-          Object {
-            "label": "onboarding.language.os.win",
-            "value": "win",
-          },
-          Object {
-            "label": "onboarding.language.os.mac",
-            "value": "mac",
-          },
-        ]
-      }
-      value={null}
-    />
-  </div>
-</div>
-`;
-
-exports[`selects c-family 4`] = `
-<div>
-  <div>
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="language"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.java",
-            "value": "java",
-          },
-          Object {
-            "label": "onboarding.language.dotnet",
-            "value": "dotnet",
-          },
-          Object {
-            "label": "onboarding.language.c-family",
-            "value": "c-family",
-          },
-          Object {
-            "label": "onboarding.language.other",
-            "value": "other",
-          },
-        ]
-      }
-      value="c-family"
-    />
-  </div>
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.c-family.compiler
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="c-family-compiler"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.c-family.compiler.msvc",
-            "value": "msvc",
-          },
-          Object {
-            "label": "onboarding.language.c-family.compiler.clang-gcc",
-            "value": "clang-gcc",
-          },
-        ]
-      }
-      value="clang-gcc"
-    />
-  </div>
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.os
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="os"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.os.linux",
-            "value": "linux",
-          },
-          Object {
-            "label": "onboarding.language.os.win",
-            "value": "win",
-          },
-          Object {
-            "label": "onboarding.language.os.mac",
-            "value": "mac",
-          },
-        ]
-      }
-      value="linux"
-    />
-  </div>
-  <NewProjectForm
-    onDelete={[Function]}
-    onDone={[Function]}
-    projectKey="project-foo"
-  />
-</div>
-`;
-
-exports[`selects java 1`] = `
-<div>
-  <div>
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="language"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.java",
-            "value": "java",
-          },
-          Object {
-            "label": "onboarding.language.dotnet",
-            "value": "dotnet",
-          },
-          Object {
-            "label": "onboarding.language.other",
-            "value": "other",
-          },
-        ]
-      }
-      value="java"
-    />
-  </div>
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.java.build_technology
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="java-build"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.java.build_technology.maven",
-            "value": "maven",
-          },
-          Object {
-            "label": "onboarding.language.java.build_technology.gradle",
-            "value": "gradle",
-          },
-        ]
-      }
-      value={null}
-    />
-  </div>
-</div>
-`;
-
-exports[`selects java 2`] = `
-<div>
-  <div>
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="language"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.java",
-            "value": "java",
-          },
-          Object {
-            "label": "onboarding.language.dotnet",
-            "value": "dotnet",
-          },
-          Object {
-            "label": "onboarding.language.other",
-            "value": "other",
-          },
-        ]
-      }
-      value="java"
-    />
-  </div>
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.java.build_technology
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="java-build"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.java.build_technology.maven",
-            "value": "maven",
-          },
-          Object {
-            "label": "onboarding.language.java.build_technology.gradle",
-            "value": "gradle",
-          },
-        ]
-      }
-      value="maven"
-    />
-  </div>
-</div>
-`;
-
-exports[`selects java 3`] = `
-<div>
-  <div>
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="language"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.java",
-            "value": "java",
-          },
-          Object {
-            "label": "onboarding.language.dotnet",
-            "value": "dotnet",
-          },
-          Object {
-            "label": "onboarding.language.other",
-            "value": "other",
-          },
-        ]
-      }
-      value="java"
-    />
-  </div>
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.java.build_technology
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="java-build"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.java.build_technology.maven",
-            "value": "maven",
-          },
-          Object {
-            "label": "onboarding.language.java.build_technology.gradle",
-            "value": "gradle",
-          },
-        ]
-      }
-      value="gradle"
-    />
-  </div>
-</div>
-`;
-
-exports[`selects other 1`] = `
-<div>
-  <div>
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="language"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.java",
-            "value": "java",
-          },
-          Object {
-            "label": "onboarding.language.dotnet",
-            "value": "dotnet",
-          },
-          Object {
-            "label": "onboarding.language.other",
-            "value": "other",
-          },
-        ]
-      }
-      value="other"
-    />
-  </div>
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.os
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="os"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.os.linux",
-            "value": "linux",
-          },
-          Object {
-            "label": "onboarding.language.os.win",
-            "value": "win",
-          },
-          Object {
-            "label": "onboarding.language.os.mac",
-            "value": "mac",
-          },
-        ]
-      }
-      value={null}
-    />
-  </div>
-</div>
-`;
-
-exports[`selects other 2`] = `
-<div>
-  <div>
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="language"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.java",
-            "value": "java",
-          },
-          Object {
-            "label": "onboarding.language.dotnet",
-            "value": "dotnet",
-          },
-          Object {
-            "label": "onboarding.language.other",
-            "value": "other",
-          },
-        ]
-      }
-      value="other"
-    />
-  </div>
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.os
-    </h4>
-    <RadioToggle
-      disabled={false}
-      name="os"
-      onCheck={[Function]}
-      options={
-        Array [
-          Object {
-            "label": "onboarding.language.os.linux",
-            "value": "linux",
-          },
-          Object {
-            "label": "onboarding.language.os.win",
-            "value": "win",
-          },
-          Object {
-            "label": "onboarding.language.os.mac",
-            "value": "mac",
-          },
-        ]
-      }
-      value="mac"
-    />
-  </div>
-  <NewProjectForm
-    onDelete={[Function]}
-    onDone={[Function]}
-  />
-</div>
-`;
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
deleted file mode 100644 (file)
index eae8fcd..0000000
+++ /dev/null
@@ -1,270 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`creates new organization 1`] = `
-<NewOrganizationForm
-  onDelete={[MockFunction]}
-  onDone={[MockFunction]}
->
-  <form
-    onSubmit={[Function]}
-  >
-    <input
-      autoFocus={true}
-      className="input-super-large spacer-right text-middle"
-      maxLength={32}
-      minLength={2}
-      onChange={[Function]}
-      placeholder="onboarding.organization.placeholder"
-      required={true}
-      type="text"
-      value=""
-    />
-    <SubmitButton
-      className="text-middle"
-      disabled={true}
-    >
-      <Button
-        className="text-middle"
-        disabled={true}
-        preventDefault={false}
-        type="submit"
-      >
-        <button
-          className="button text-middle"
-          disabled={true}
-          onClick={[Function]}
-          type="submit"
-        >
-          create
-        </button>
-      </Button>
-    </SubmitButton>
-    <div
-      className="note spacer-top abs-width-300"
-    >
-      onboarding.organization.key_requirement
-    </div>
-  </form>
-</NewOrganizationForm>
-`;
-
-exports[`creates new organization 2`] = `
-<NewOrganizationForm
-  onDelete={[MockFunction]}
-  onDone={[MockFunction]}
->
-  <form
-    onSubmit={[Function]}
-  >
-    <input
-      autoFocus={true}
-      className="input-super-large spacer-right text-middle"
-      maxLength={32}
-      minLength={2}
-      onChange={[Function]}
-      placeholder="onboarding.organization.placeholder"
-      required={true}
-      type="text"
-      value="foo"
-    />
-    <i
-      className="spinner text-middle"
-    />
-    <div
-      className="note spacer-top abs-width-300"
-    >
-      onboarding.organization.key_requirement
-    </div>
-  </form>
-</NewOrganizationForm>
-`;
-
-exports[`creates new organization 3`] = `
-<NewOrganizationForm
-  onDelete={[MockFunction]}
-  onDone={
-    [MockFunction] {
-      "calls": Array [
-        Array [
-          "foo",
-        ],
-      ],
-      "results": Array [
-        Object {
-          "isThrow": false,
-          "value": undefined,
-        },
-      ],
-    }
-  }
->
-  <div>
-    <span
-      className="spacer-right text-middle"
-    >
-      foo
-    </span>
-    <DeleteButton
-      className="button-small"
-      onClick={[Function]}
-    >
-      <ButtonIcon
-        className="button-small"
-        color="#d4333f"
-        onClick={[Function]}
-      >
-        <Button
-          className="button-small button-icon"
-          onClick={[Function]}
-          stopPropagation={true}
-          style={
-            Object {
-              "color": "#d4333f",
-            }
-          }
-        >
-          <button
-            className="button button-small button-icon"
-            onClick={[Function]}
-            style={
-              Object {
-                "color": "#d4333f",
-              }
-            }
-            type="button"
-          >
-            <ClearIcon />
-          </button>
-        </Button>
-      </ButtonIcon>
-    </DeleteButton>
-  </div>
-</NewOrganizationForm>
-`;
-
-exports[`deletes organization 1`] = `
-<NewOrganizationForm
-  onDelete={[MockFunction]}
-  onDone={[MockFunction]}
->
-  <div>
-    <span
-      className="spacer-right text-middle"
-    >
-      foo
-    </span>
-    <DeleteButton
-      className="button-small"
-      onClick={[Function]}
-    >
-      <ButtonIcon
-        className="button-small"
-        color="#d4333f"
-        onClick={[Function]}
-      >
-        <Button
-          className="button-small button-icon"
-          onClick={[Function]}
-          stopPropagation={true}
-          style={
-            Object {
-              "color": "#d4333f",
-            }
-          }
-        >
-          <button
-            className="button button-small button-icon"
-            onClick={[Function]}
-            style={
-              Object {
-                "color": "#d4333f",
-              }
-            }
-            type="button"
-          >
-            <ClearIcon />
-          </button>
-        </Button>
-      </ButtonIcon>
-    </DeleteButton>
-  </div>
-</NewOrganizationForm>
-`;
-
-exports[`deletes organization 2`] = `
-<NewOrganizationForm
-  onDelete={[MockFunction]}
-  onDone={[MockFunction]}
->
-  <div>
-    <span
-      className="spacer-right text-middle"
-    >
-      foo
-    </span>
-    <i
-      className="spinner text-middle"
-    />
-  </div>
-</NewOrganizationForm>
-`;
-
-exports[`deletes organization 3`] = `
-<NewOrganizationForm
-  onDelete={
-    [MockFunction] {
-      "calls": Array [
-        Array [],
-      ],
-      "results": Array [
-        Object {
-          "isThrow": false,
-          "value": undefined,
-        },
-      ],
-    }
-  }
-  onDone={[MockFunction]}
->
-  <form
-    onSubmit={[Function]}
-  >
-    <input
-      autoFocus={true}
-      className="input-super-large spacer-right text-middle"
-      maxLength={32}
-      minLength={2}
-      onChange={[Function]}
-      placeholder="onboarding.organization.placeholder"
-      required={true}
-      type="text"
-      value=""
-    />
-    <SubmitButton
-      className="text-middle"
-      disabled={true}
-    >
-      <Button
-        className="text-middle"
-        disabled={true}
-        preventDefault={false}
-        type="submit"
-      >
-        <button
-          className="button text-middle"
-          disabled={true}
-          onClick={[Function]}
-          type="submit"
-        >
-          create
-        </button>
-      </Button>
-    </SubmitButton>
-    <div
-      className="note spacer-top abs-width-300"
-    >
-      onboarding.organization.key_requirement
-    </div>
-  </form>
-</NewOrganizationForm>
-`;
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
deleted file mode 100644 (file)
index f6722a2..0000000
+++ /dev/null
@@ -1,321 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`creates new project 1`] = `
-<NewProjectForm
-  onDelete={[MockFunction]}
-  onDone={[MockFunction]}
->
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.project_key
-    </h4>
-    <form
-      onSubmit={[Function]}
-    >
-      <input
-        autoFocus={true}
-        className="input-large spacer-right text-middle"
-        maxLength={400}
-        minLength={1}
-        onChange={[Function]}
-        required={true}
-        type="text"
-        value=""
-      />
-      <SubmitButton
-        className="text-middle"
-        disabled={true}
-      >
-        <Button
-          className="text-middle"
-          disabled={true}
-          preventDefault={false}
-          type="submit"
-        >
-          <button
-            className="button text-middle"
-            disabled={true}
-            onClick={[Function]}
-            type="submit"
-          >
-            Done
-          </button>
-        </Button>
-      </SubmitButton>
-      <div
-        className="note spacer-top abs-width-300"
-      >
-        onboarding.project_key_requirement
-      </div>
-    </form>
-  </div>
-</NewProjectForm>
-`;
-
-exports[`creates new project 2`] = `
-<NewProjectForm
-  onDelete={[MockFunction]}
-  onDone={[MockFunction]}
->
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.project_key
-    </h4>
-    <form
-      onSubmit={[Function]}
-    >
-      <input
-        autoFocus={true}
-        className="input-large spacer-right text-middle"
-        maxLength={400}
-        minLength={1}
-        onChange={[Function]}
-        required={true}
-        type="text"
-        value="foo"
-      />
-      <i
-        className="spinner text-middle"
-      />
-      <div
-        className="note spacer-top abs-width-300"
-      >
-        onboarding.project_key_requirement
-      </div>
-    </form>
-  </div>
-</NewProjectForm>
-`;
-
-exports[`creates new project 3`] = `
-<NewProjectForm
-  onDelete={[MockFunction]}
-  onDone={
-    [MockFunction] {
-      "calls": Array [
-        Array [
-          "foo",
-        ],
-      ],
-      "results": Array [
-        Object {
-          "isThrow": false,
-          "value": undefined,
-        },
-      ],
-    }
-  }
->
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.project_key
-    </h4>
-    <div>
-      <span
-        className="spacer-right text-middle"
-      >
-        foo
-      </span>
-      <DeleteButton
-        className="button-small text-middle"
-        onClick={[Function]}
-      >
-        <ButtonIcon
-          className="button-small text-middle"
-          color="#d4333f"
-          onClick={[Function]}
-        >
-          <Button
-            className="button-small text-middle button-icon"
-            onClick={[Function]}
-            stopPropagation={true}
-            style={
-              Object {
-                "color": "#d4333f",
-              }
-            }
-          >
-            <button
-              className="button button-small text-middle button-icon"
-              onClick={[Function]}
-              style={
-                Object {
-                  "color": "#d4333f",
-                }
-              }
-              type="button"
-            >
-              <ClearIcon />
-            </button>
-          </Button>
-        </ButtonIcon>
-      </DeleteButton>
-    </div>
-  </div>
-</NewProjectForm>
-`;
-
-exports[`deletes project 1`] = `
-<NewProjectForm
-  onDelete={[MockFunction]}
-  onDone={[MockFunction]}
->
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.project_key
-    </h4>
-    <div>
-      <span
-        className="spacer-right text-middle"
-      >
-        foo
-      </span>
-      <DeleteButton
-        className="button-small text-middle"
-        onClick={[Function]}
-      >
-        <ButtonIcon
-          className="button-small text-middle"
-          color="#d4333f"
-          onClick={[Function]}
-        >
-          <Button
-            className="button-small text-middle button-icon"
-            onClick={[Function]}
-            stopPropagation={true}
-            style={
-              Object {
-                "color": "#d4333f",
-              }
-            }
-          >
-            <button
-              className="button button-small text-middle button-icon"
-              onClick={[Function]}
-              style={
-                Object {
-                  "color": "#d4333f",
-                }
-              }
-              type="button"
-            >
-              <ClearIcon />
-            </button>
-          </Button>
-        </ButtonIcon>
-      </DeleteButton>
-    </div>
-  </div>
-</NewProjectForm>
-`;
-
-exports[`deletes project 2`] = `
-<NewProjectForm
-  onDelete={[MockFunction]}
-  onDone={[MockFunction]}
->
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.project_key
-    </h4>
-    <div>
-      <span
-        className="spacer-right text-middle"
-      >
-        foo
-      </span>
-      <i
-        className="spinner text-middle"
-      />
-    </div>
-  </div>
-</NewProjectForm>
-`;
-
-exports[`deletes project 3`] = `
-<NewProjectForm
-  onDelete={
-    [MockFunction] {
-      "calls": Array [
-        Array [],
-      ],
-      "results": Array [
-        Object {
-          "isThrow": false,
-          "value": undefined,
-        },
-      ],
-    }
-  }
-  onDone={[MockFunction]}
->
-  <div
-    className="big-spacer-top"
-  >
-    <h4
-      className="spacer-bottom"
-    >
-      onboarding.language.project_key
-    </h4>
-    <form
-      onSubmit={[Function]}
-    >
-      <input
-        autoFocus={true}
-        className="input-large spacer-right text-middle"
-        maxLength={400}
-        minLength={1}
-        onChange={[Function]}
-        required={true}
-        type="text"
-        value=""
-      />
-      <SubmitButton
-        className="text-middle"
-        disabled={true}
-      >
-        <Button
-          className="text-middle"
-          disabled={true}
-          preventDefault={false}
-          type="submit"
-        >
-          <button
-            className="button text-middle"
-            disabled={true}
-            onClick={[Function]}
-            type="submit"
-          >
-            Done
-          </button>
-        </Button>
-      </SubmitButton>
-      <div
-        className="note spacer-top abs-width-300"
-      >
-        onboarding.project_key_requirement
-      </div>
-    </form>
-  </div>
-</NewProjectForm>
-`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap
deleted file mode 100644 (file)
index 1635dc7..0000000
+++ /dev/null
@@ -1,388 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`guides for on-premise 1`] = `
-<div
-  className="modal-container"
->
-  <InstanceMessage
-    message="onboarding.header"
-  />
-  <div
-    className="page page-limited onboarding"
-  >
-    <header
-      className="page-header"
-    >
-      <h1
-        className="page-title"
-      >
-        <InstanceMessage
-          message="onboarding.header"
-        />
-      </h1>
-      <div
-        className="page-actions"
-      >
-        <DeferredSpinner
-          loading={false}
-          timeout={100}
-        >
-          <a
-            className="js-skip text-muted"
-            href="#"
-            onClick={[Function]}
-          >
-            close
-          </a>
-        </DeferredSpinner>
-        <p
-          className="note"
-        >
-          tutorials.find_it_back_in_help
-        </p>
-      </div>
-      <div
-        className="page-description"
-      >
-        onboarding.header.description.2
-      </div>
-    </header>
-    <TokenStep
-      currentUser={
-        Object {
-          "isLoggedIn": true,
-          "login": "admin",
-        }
-      }
-      finished={false}
-      onContinue={[Function]}
-      onOpen={[Function]}
-      open={true}
-      stepNumber={1}
-    />
-    <AnalysisStep
-      onFinish={[Function]}
-      onReset={[Function]}
-      open={false}
-      stepNumber={2}
-    />
-  </div>
-</div>
-`;
-
-exports[`guides for on-premise 2`] = `
-<div
-  className="modal-container"
->
-  <InstanceMessage
-    message="onboarding.header"
-  />
-  <div
-    className="page page-limited onboarding"
-  >
-    <header
-      className="page-header"
-    >
-      <h1
-        className="page-title"
-      >
-        <InstanceMessage
-          message="onboarding.header"
-        />
-      </h1>
-      <div
-        className="page-actions"
-      >
-        <DeferredSpinner
-          loading={false}
-          timeout={100}
-        >
-          <a
-            className="js-skip text-muted"
-            href="#"
-            onClick={[Function]}
-          >
-            close
-          </a>
-        </DeferredSpinner>
-        <p
-          className="note"
-        >
-          tutorials.find_it_back_in_help
-        </p>
-      </div>
-      <div
-        className="page-description"
-      >
-        onboarding.header.description.2
-      </div>
-    </header>
-    <TokenStep
-      currentUser={
-        Object {
-          "isLoggedIn": true,
-          "login": "admin",
-        }
-      }
-      finished={true}
-      onContinue={[Function]}
-      onOpen={[Function]}
-      open={false}
-      stepNumber={1}
-    />
-    <AnalysisStep
-      onFinish={[Function]}
-      onReset={[Function]}
-      open={true}
-      stepNumber={2}
-      token="abcd1234"
-    />
-  </div>
-</div>
-`;
-
-exports[`guides for sonarcloud 1`] = `
-<div>
-  <InstanceMessage
-    message="onboarding.header"
-  />
-  <div
-    className="page page-limited onboarding"
-  >
-    <header
-      className="page-header"
-    >
-      <h1
-        className="page-title"
-      >
-        <InstanceMessage
-          message="onboarding.header"
-        />
-      </h1>
-      <div
-        className="page-actions"
-      >
-        <DeferredSpinner
-          loading={false}
-          timeout={100}
-        >
-          <a
-            className="js-skip text-muted"
-            href="#"
-            onClick={[Function]}
-          >
-            close
-          </a>
-        </DeferredSpinner>
-        <p
-          className="note"
-        >
-          tutorials.find_it_back_in_plus
-        </p>
-      </div>
-      <div
-        className="page-description"
-      >
-        onboarding.header.description.3
-      </div>
-    </header>
-    <OrganizationStep
-      currentUser={
-        Object {
-          "isLoggedIn": true,
-          "login": "admin",
-        }
-      }
-      finished={false}
-      onContinue={[Function]}
-      onOpen={[Function]}
-      open={true}
-      stepNumber={1}
-    />
-    <TokenStep
-      currentUser={
-        Object {
-          "isLoggedIn": true,
-          "login": "admin",
-        }
-      }
-      finished={false}
-      onContinue={[Function]}
-      onOpen={[Function]}
-      open={false}
-      stepNumber={2}
-    />
-    <AnalysisStep
-      onFinish={[Function]}
-      onReset={[Function]}
-      open={false}
-      stepNumber={3}
-    />
-  </div>
-</div>
-`;
-
-exports[`guides for sonarcloud 2`] = `
-<div>
-  <InstanceMessage
-    message="onboarding.header"
-  />
-  <div
-    className="page page-limited onboarding"
-  >
-    <header
-      className="page-header"
-    >
-      <h1
-        className="page-title"
-      >
-        <InstanceMessage
-          message="onboarding.header"
-        />
-      </h1>
-      <div
-        className="page-actions"
-      >
-        <DeferredSpinner
-          loading={false}
-          timeout={100}
-        >
-          <a
-            className="js-skip text-muted"
-            href="#"
-            onClick={[Function]}
-          >
-            close
-          </a>
-        </DeferredSpinner>
-        <p
-          className="note"
-        >
-          tutorials.find_it_back_in_plus
-        </p>
-      </div>
-      <div
-        className="page-description"
-      >
-        onboarding.header.description.3
-      </div>
-    </header>
-    <OrganizationStep
-      currentUser={
-        Object {
-          "isLoggedIn": true,
-          "login": "admin",
-        }
-      }
-      finished={true}
-      onContinue={[Function]}
-      onOpen={[Function]}
-      open={false}
-      stepNumber={1}
-    />
-    <TokenStep
-      currentUser={
-        Object {
-          "isLoggedIn": true,
-          "login": "admin",
-        }
-      }
-      finished={false}
-      onContinue={[Function]}
-      onOpen={[Function]}
-      open={true}
-      stepNumber={2}
-    />
-    <AnalysisStep
-      onFinish={[Function]}
-      onReset={[Function]}
-      open={false}
-      organization="my-org"
-      stepNumber={3}
-    />
-  </div>
-</div>
-`;
-
-exports[`guides for sonarcloud 3`] = `
-<div>
-  <InstanceMessage
-    message="onboarding.header"
-  />
-  <div
-    className="page page-limited onboarding"
-  >
-    <header
-      className="page-header"
-    >
-      <h1
-        className="page-title"
-      >
-        <InstanceMessage
-          message="onboarding.header"
-        />
-      </h1>
-      <div
-        className="page-actions"
-      >
-        <DeferredSpinner
-          loading={false}
-          timeout={100}
-        >
-          <a
-            className="js-skip text-muted"
-            href="#"
-            onClick={[Function]}
-          >
-            close
-          </a>
-        </DeferredSpinner>
-        <p
-          className="note"
-        >
-          tutorials.find_it_back_in_plus
-        </p>
-      </div>
-      <div
-        className="page-description"
-      >
-        onboarding.header.description.3
-      </div>
-    </header>
-    <OrganizationStep
-      currentUser={
-        Object {
-          "isLoggedIn": true,
-          "login": "admin",
-        }
-      }
-      finished={true}
-      onContinue={[Function]}
-      onOpen={[Function]}
-      open={false}
-      stepNumber={1}
-    />
-    <TokenStep
-      currentUser={
-        Object {
-          "isLoggedIn": true,
-          "login": "admin",
-        }
-      }
-      finished={true}
-      onContinue={[Function]}
-      onOpen={[Function]}
-      open={false}
-      stepNumber={2}
-    />
-    <AnalysisStep
-      onFinish={[Function]}
-      onReset={[Function]}
-      open={true}
-      organization="my-org"
-      stepNumber={3}
-      token="abcd1234"
-    />
-  </div>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OrganizationStep-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OrganizationStep-test.js.snap
deleted file mode 100644 (file)
index 7e30ded..0000000
+++ /dev/null
@@ -1,381 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`works with existing organization 1`] = `
-<OrganizationStep
-  currentUser={
-    Object {
-      "isLoggedIn": true,
-      "login": "user",
-    }
-  }
-  finished={false}
-  onContinue={[MockFunction]}
-  onOpen={[MockFunction]}
-  open={true}
-  stepNumber={1}
->
-  <Step
-    finished={false}
-    onOpen={[MockFunction]}
-    open={true}
-    renderForm={[Function]}
-    renderResult={[Function]}
-    stepNumber={1}
-    stepTitle={
-      <span>
-        onboarding.organization.header
-        <DocTooltip
-          className="little-spacer-left"
-          doc="organizations/organization"
-        />
-      </span>
-    }
-  >
-    <div
-      className="boxed-group onboarding-step is-open"
-    >
-      <div
-        className="onboarding-step-number"
-      >
-        1
-      </div>
-      <div
-        className="boxed-group-header"
-      >
-        <h2>
-          <span>
-            onboarding.organization.header
-            <DocTooltip
-              className="little-spacer-left"
-              doc="organizations/organization"
-            >
-              <HelpTooltip
-                className="little-spacer-left"
-                onShow={[Function]}
-                overlay={
-                  <div
-                    className="abs-width-300"
-                  >
-                    <LazyLoader
-                      className="cut-margins"
-                      isTooltip={true}
-                    />
-                  </div>
-                }
-              >
-                <div
-                  className="help-tooltip little-spacer-left"
-                >
-                  <Tooltip
-                    mouseLeaveDelay={0.25}
-                    onShow={[Function]}
-                    overlay={
-                      <div
-                        className="abs-width-300"
-                      >
-                        <LazyLoader
-                          className="cut-margins"
-                          isTooltip={true}
-                        />
-                      </div>
-                    }
-                  >
-                    <TooltipInner
-                      mouseEnterDelay={0.1}
-                      mouseLeaveDelay={0.25}
-                      onShow={[Function]}
-                      overlay={
-                        <div
-                          className="abs-width-300"
-                        >
-                          <LazyLoader
-                            className="cut-margins"
-                            isTooltip={true}
-                          />
-                        </div>
-                      }
-                    >
-                      <span
-                        className="display-inline-flex-center"
-                        onMouseEnter={[Function]}
-                        onMouseLeave={[Function]}
-                      >
-                        <HelpIcon
-                          fill="#b4b4b4"
-                          size={12}
-                        >
-                          <Icon
-                            size={12}
-                          >
-                            <svg
-                              height={12}
-                              style={
-                                Object {
-                                  "clipRule": "evenodd",
-                                  "fillRule": "evenodd",
-                                  "strokeLinejoin": "round",
-                                  "strokeMiterlimit": "1.41421",
-                                }
-                              }
-                              version="1.1"
-                              viewBox="0 0 16 16"
-                              width={12}
-                              xmlSpace="preserve"
-                              xmlnsXlink="http://www.w3.org/1999/xlink"
-                            >
-                              <g
-                                transform="matrix(0.0364583,0,0,0.0364583,1,-0.166667)"
-                              >
-                                <path
-                                  d="M224,344L224,296C224,293.667 223.25,291.75 221.75,290.25C220.25,288.75 218.333,288 216,288L168,288C165.667,288 163.75,288.75 162.25,290.25C160.75,291.75 160,293.667 160,296L160,344C160,346.333 160.75,348.25 162.25,349.75C163.75,351.25 165.667,352 168,352L216,352C218.333,352 220.25,351.25 221.75,349.75C223.25,348.25 224,346.333 224,344ZM288,176C288,161.333 283.375,147.75 274.125,135.25C264.875,122.75 253.333,113.083 239.5,106.25C225.667,99.417 211.5,96 197,96C156.5,96 125.583,113.75 104.25,149.25C101.75,153.25 102.417,156.75 106.25,159.75L139.25,184.75C140.417,185.75 142,186.25 144,186.25C146.667,186.25 148.75,185.25 150.25,183.25C159.083,171.917 166.25,164.25 171.75,160.25C177.417,156.25 184.583,154.25 193.25,154.25C201.25,154.25 208.375,156.417 214.625,160.75C220.875,165.083 224,170 224,175.5C224,181.833 222.333,186.917 219,190.75C215.667,194.583 210,198.333 202,202C191.5,206.667 181.875,213.875 173.125,223.625C164.375,233.375 160,243.833 160,255L160,264C160,266.333 160.75,268.25 162.25,269.75C163.75,271.25 165.667,272 168,272L216,272C218.333,272 220.25,271.25 221.75,269.75C223.25,268.25 224,266.333 224,264C224,260.833 225.792,256.708 229.375,251.625C232.958,246.542 237.5,242.417 243,239.25C248.333,236.25 252.417,233.875 255.25,232.125C258.083,230.375 261.917,227.458 266.75,223.375C271.583,219.292 275.292,215.292 277.875,211.375C280.458,207.458 282.792,202.417 284.875,196.25C286.958,190.083 288,183.333 288,176ZM384,224C384,258.833 375.417,290.958 358.25,320.375C341.083,349.792 317.792,373.083 288.375,390.25C258.958,407.417 226.833,416 192,416C157.167,416 125.042,407.417 95.625,390.25C66.208,373.083 42.917,349.792 25.75,320.375C8.583,290.958 0,258.833 0,224C0,189.167 8.583,157.042 25.75,127.625C42.917,98.208 66.208,74.917 95.625,57.75C125.042,40.583 157.167,32 192,32C226.833,32 258.958,40.583 288.375,57.75C317.792,74.917 341.083,98.208 358.25,127.625C375.417,157.042 384,189.167 384,224Z"
-                                  style={
-                                    Object {
-                                      "fill": "#b4b4b4",
-                                    }
-                                  }
-                                />
-                              </g>
-                            </svg>
-                          </Icon>
-                        </HelpIcon>
-                      </span>
-                    </TooltipInner>
-                  </Tooltip>
-                </div>
-              </HelpTooltip>
-            </DocTooltip>
-          </span>
-        </h2>
-      </div>
-      <div
-        className="boxed-group-inner"
-      >
-        <div
-          className="big-spacer-bottom width-50"
-        >
-          onboarding.organization.text
-        </div>
-        <div>
-          <div
-            className="big-spacer-top"
-          >
-            <a
-              className="js-existing link-base-color link-no-underline"
-              href="#"
-              onClick={[Function]}
-            >
-              <i
-                className="icon-radio spacer-right is-checked"
-              />
-              onboarding.organization.exising_organization
-            </a>
-            <div
-              className="big-spacer-top"
-            >
-              <Select
-                className="input-super-large"
-                clearable={false}
-                onChange={[Function]}
-                options={
-                  Array [
-                    Object {
-                      "label": "another",
-                      "value": "another",
-                    },
-                    Object {
-                      "label": "user",
-                      "value": "user",
-                    },
-                  ]
-                }
-              >
-                <LazyLoader
-                  className="input-super-large"
-                  clearRenderer={[Function]}
-                  clearable={false}
-                  onChange={[Function]}
-                  options={
-                    Array [
-                      Object {
-                        "label": "another",
-                        "value": "another",
-                      },
-                      Object {
-                        "label": "user",
-                        "value": "user",
-                      },
-                    ]
-                  }
-                >
-                  <Select
-                    arrowRenderer={[Function]}
-                    autosize={true}
-                    backspaceRemoves={true}
-                    backspaceToRemoveMessage="Press backspace to remove {label}"
-                    className="input-super-large"
-                    clearAllText="Clear all"
-                    clearRenderer={[Function]}
-                    clearValueText="Clear value"
-                    clearable={false}
-                    closeOnSelect={true}
-                    deleteRemoves={true}
-                    delimiter=","
-                    disabled={false}
-                    escapeClearsValue={true}
-                    filterOptions={[Function]}
-                    ignoreAccents={true}
-                    ignoreCase={true}
-                    inputProps={Object {}}
-                    isLoading={false}
-                    joinValues={false}
-                    labelKey="label"
-                    matchPos="any"
-                    matchProp="any"
-                    menuBuffer={0}
-                    menuRenderer={[Function]}
-                    multi={false}
-                    noResultsText="No results found"
-                    onBlurResetsInput={true}
-                    onChange={[Function]}
-                    onCloseResetsInput={true}
-                    onSelectResetsInput={true}
-                    openOnClick={true}
-                    optionComponent={[Function]}
-                    options={
-                      Array [
-                        Object {
-                          "label": "another",
-                          "value": "another",
-                        },
-                        Object {
-                          "label": "user",
-                          "value": "user",
-                        },
-                      ]
-                    }
-                    pageSize={5}
-                    placeholder="Select..."
-                    removeSelected={true}
-                    required={false}
-                    rtl={false}
-                    scrollMenuIntoView={true}
-                    searchable={true}
-                    simpleValue={false}
-                    tabSelectsValue={true}
-                    trimFilter={true}
-                    valueComponent={[Function]}
-                    valueKey="value"
-                  >
-                    <div
-                      className="Select input-super-large is-searchable Select--single"
-                    >
-                      <div
-                        className="Select-control"
-                        onKeyDown={[Function]}
-                        onMouseDown={[Function]}
-                        onTouchEnd={[Function]}
-                        onTouchMove={[Function]}
-                        onTouchStart={[Function]}
-                      >
-                        <span
-                          className="Select-multi-value-wrapper"
-                          id="react-select-2--value"
-                        >
-                          <div
-                            className="Select-placeholder"
-                          >
-                            Select...
-                          </div>
-                          <AutosizeInput
-                            aria-activedescendant="react-select-2--value"
-                            aria-expanded="false"
-                            aria-haspopup="false"
-                            aria-owns=""
-                            className="Select-input"
-                            injectStyles={true}
-                            minWidth="5"
-                            onBlur={[Function]}
-                            onChange={[Function]}
-                            onFocus={[Function]}
-                            required={false}
-                            role="combobox"
-                            value=""
-                          >
-                            <div
-                              className="Select-input"
-                              style={
-                                Object {
-                                  "display": "inline-block",
-                                }
-                              }
-                            >
-                              <input
-                                aria-activedescendant="react-select-2--value"
-                                aria-expanded="false"
-                                aria-haspopup="false"
-                                aria-owns=""
-                                onBlur={[Function]}
-                                onChange={[Function]}
-                                onFocus={[Function]}
-                                required={false}
-                                role="combobox"
-                                style={
-                                  Object {
-                                    "boxSizing": "content-box",
-                                    "width": "5px",
-                                  }
-                                }
-                                value=""
-                              />
-                              <div
-                                style={
-                                  Object {
-                                    "height": 0,
-                                    "left": 0,
-                                    "overflow": "scroll",
-                                    "position": "absolute",
-                                    "top": 0,
-                                    "visibility": "hidden",
-                                    "whiteSpace": "pre",
-                                  }
-                                }
-                              />
-                            </div>
-                          </AutosizeInput>
-                        </span>
-                        <span
-                          className="Select-arrow-zone"
-                          onMouseDown={[Function]}
-                        >
-                          <span
-                            className="Select-arrow"
-                            onMouseDown={[Function]}
-                          />
-                        </span>
-                      </div>
-                    </div>
-                  </Select>
-                </LazyLoader>
-              </Select>
-            </div>
-          </div>
-          <div
-            className="big-spacer-top"
-          >
-            <a
-              className="js-new link-base-color link-no-underline"
-              href="#"
-              onClick={[Function]}
-            >
-              <i
-                className="icon-radio spacer-right"
-              />
-              onboarding.organization.create_another_organization
-            </a>
-          </div>
-        </div>
-      </div>
-    </div>
-  </Step>
-</OrganizationStep>
-`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/ProjectWatcher-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/ProjectWatcher-test.js.snap
deleted file mode 100644 (file)
index 4ecd80c..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders 1`] = `
-<div
-  className="big-spacer-top note text-center"
->
-  onboarding.project_watcher.not_started
-</div>
-`;
-
-exports[`renders 2`] = `
-<div
-  className="big-spacer-top note text-center"
->
-  <i
-    className="spinner spacer-right"
-  />
-  onboarding.project_watcher.in_progress
-</div>
-`;
-
-exports[`renders 3`] = `
-<div
-  className="big-spacer-top note text-center"
->
-  <AlertSuccessIcon
-    className="spacer-right"
-  />
-  onboarding.project_watcher.finished
-</div>
-`;
-
-exports[`renders 4`] = `
-<div
-  className="big-spacer-top note text-center"
->
-  <i
-    className="spinner spacer-right"
-  />
-  onboarding.project_watcher.in_progress
-</div>
-`;
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
deleted file mode 100644 (file)
index 4667ca7..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders 1`] = `
-<div
-  className="boxed-group onboarding-step is-open is-finished"
->
-  <div
-    className="onboarding-step-number"
-  >
-    1
-  </div>
-  <div
-    className="boxed-group-header"
-  >
-    <h2>
-      First Step
-    </h2>
-  </div>
-  <div>
-    form
-  </div>
-</div>
-`;
-
-exports[`renders 2`] = `
-<div
-  className="boxed-group onboarding-step is-finished"
-  onClick={[Function]}
-  role="button"
-  tabIndex={0}
->
-  <div
-    className="onboarding-step-number"
-  >
-    1
-  </div>
-  <div>
-    result
-  </div>
-  <div
-    className="boxed-group-header"
-  >
-    <h2>
-      First Step
-    </h2>
-  </div>
-  <div
-    className="boxed-group-inner"
-  />
-</div>
-`;
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
deleted file mode 100644 (file)
index 9b991a9..0000000
+++ /dev/null
@@ -1,649 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`generates token 1`] = `
-<TokenStep
-  currentUser={
-    Object {
-      "login": "user",
-    }
-  }
-  finished={false}
-  onContinue={[MockFunction]}
-  onOpen={[MockFunction]}
-  open={true}
-  stepNumber={1}
->
-  <Step
-    finished={false}
-    onOpen={[MockFunction]}
-    open={true}
-    renderForm={[Function]}
-    renderResult={[Function]}
-    stepNumber={1}
-    stepTitle="onboarding.token.header"
-  >
-    <div
-      className="boxed-group onboarding-step is-open"
-    >
-      <div
-        className="onboarding-step-number"
-      >
-        1
-      </div>
-      <div
-        className="boxed-group-header"
-      >
-        <h2>
-          onboarding.token.header
-        </h2>
-      </div>
-      <div
-        className="boxed-group-inner"
-      >
-        <div>
-          <div>
-            <a
-              className="js-new link-base-color link-no-underline"
-              href="#"
-              onClick={[Function]}
-            >
-              <i
-                className="icon-radio spacer-right is-checked"
-              />
-              onboading.token.generate_token
-            </a>
-            <div
-              className="big-spacer-top"
-            >
-              <form
-                onSubmit={[Function]}
-              >
-                <input
-                  autoFocus={true}
-                  className="input-large spacer-right text-middle"
-                  onChange={[Function]}
-                  placeholder="onboading.token.generate_token.placeholder"
-                  required={true}
-                  type="text"
-                  value=""
-                />
-                <SubmitButton
-                  className="text-middle"
-                  disabled={true}
-                >
-                  <Button
-                    className="text-middle"
-                    disabled={true}
-                    preventDefault={false}
-                    type="submit"
-                  >
-                    <button
-                      className="button text-middle"
-                      disabled={true}
-                      onClick={[Function]}
-                      type="submit"
-                    >
-                      onboarding.token.generate
-                    </button>
-                  </Button>
-                </SubmitButton>
-              </form>
-            </div>
-          </div>
-          <div
-            className="big-spacer-top"
-          >
-            <a
-              className="js-new link-base-color link-no-underline"
-              href="#"
-              onClick={[Function]}
-            >
-              <i
-                className="icon-radio spacer-right"
-              />
-              onboarding.token.use_existing_token
-            </a>
-          </div>
-        </div>
-        <div
-          className="note big-spacer-top width-50"
-        >
-          onboarding.token.text
-        </div>
-      </div>
-    </div>
-  </Step>
-</TokenStep>
-`;
-
-exports[`generates token 2`] = `
-<TokenStep
-  currentUser={
-    Object {
-      "login": "user",
-    }
-  }
-  finished={false}
-  onContinue={[MockFunction]}
-  onOpen={[MockFunction]}
-  open={true}
-  stepNumber={1}
->
-  <Step
-    finished={false}
-    onOpen={[MockFunction]}
-    open={true}
-    renderForm={[Function]}
-    renderResult={[Function]}
-    stepNumber={1}
-    stepTitle="onboarding.token.header"
-  >
-    <div
-      className="boxed-group onboarding-step is-open"
-    >
-      <div
-        className="onboarding-step-number"
-      >
-        1
-      </div>
-      <div
-        className="boxed-group-header"
-      >
-        <h2>
-          onboarding.token.header
-        </h2>
-      </div>
-      <div
-        className="boxed-group-inner"
-      >
-        <div>
-          <div>
-            <a
-              className="js-new link-base-color link-no-underline"
-              href="#"
-              onClick={[Function]}
-            >
-              <i
-                className="icon-radio spacer-right is-checked"
-              />
-              onboading.token.generate_token
-            </a>
-            <div
-              className="big-spacer-top"
-            >
-              <form
-                onSubmit={[Function]}
-              >
-                <input
-                  autoFocus={true}
-                  className="input-large spacer-right text-middle"
-                  onChange={[Function]}
-                  placeholder="onboading.token.generate_token.placeholder"
-                  required={true}
-                  type="text"
-                  value="my token"
-                />
-                <i
-                  className="spinner text-middle"
-                />
-              </form>
-            </div>
-          </div>
-          <div
-            className="big-spacer-top"
-          >
-            <a
-              className="js-new link-base-color link-no-underline"
-              href="#"
-              onClick={[Function]}
-            >
-              <i
-                className="icon-radio spacer-right"
-              />
-              onboarding.token.use_existing_token
-            </a>
-          </div>
-        </div>
-        <div
-          className="note big-spacer-top width-50"
-        >
-          onboarding.token.text
-        </div>
-      </div>
-    </div>
-  </Step>
-</TokenStep>
-`;
-
-exports[`generates token 3`] = `
-<TokenStep
-  currentUser={
-    Object {
-      "login": "user",
-    }
-  }
-  finished={false}
-  onContinue={[MockFunction]}
-  onOpen={[MockFunction]}
-  open={true}
-  stepNumber={1}
->
-  <Step
-    finished={false}
-    onOpen={[MockFunction]}
-    open={true}
-    renderForm={[Function]}
-    renderResult={[Function]}
-    stepNumber={1}
-    stepTitle="onboarding.token.header"
-  >
-    <div
-      className="boxed-group onboarding-step is-open"
-    >
-      <div
-        className="onboarding-step-number"
-      >
-        1
-      </div>
-      <div
-        className="boxed-group-header"
-      >
-        <h2>
-          onboarding.token.header
-        </h2>
-      </div>
-      <div
-        className="boxed-group-inner"
-      >
-        <form
-          onSubmit={[Function]}
-        >
-          <span
-            className="text-middle"
-          >
-            my token
-            : 
-          </span>
-          <strong
-            className="spacer-right text-middle"
-          >
-            abcd1234
-          </strong>
-          <DeleteButton
-            className="button-small text-middle"
-            onClick={[Function]}
-          >
-            <ButtonIcon
-              className="button-small text-middle"
-              color="#d4333f"
-              onClick={[Function]}
-            >
-              <Button
-                className="button-small text-middle button-icon"
-                onClick={[Function]}
-                stopPropagation={true}
-                style={
-                  Object {
-                    "color": "#d4333f",
-                  }
-                }
-              >
-                <button
-                  className="button button-small text-middle button-icon"
-                  onClick={[Function]}
-                  style={
-                    Object {
-                      "color": "#d4333f",
-                    }
-                  }
-                  type="button"
-                >
-                  <ClearIcon />
-                </button>
-              </Button>
-            </ButtonIcon>
-          </DeleteButton>
-        </form>
-        <div
-          className="note big-spacer-top width-50"
-        >
-          onboarding.token.text
-        </div>
-        <div
-          className="big-spacer-top"
-        >
-          <Button
-            className="js-continue"
-            onClick={[Function]}
-          >
-            <button
-              className="button js-continue"
-              onClick={[Function]}
-              type="button"
-            >
-              continue
-            </button>
-          </Button>
-        </div>
-      </div>
-    </div>
-  </Step>
-</TokenStep>
-`;
-
-exports[`revokes token 1`] = `
-<TokenStep
-  currentUser={
-    Object {
-      "login": "user",
-    }
-  }
-  finished={false}
-  onContinue={[MockFunction]}
-  onOpen={[MockFunction]}
-  open={true}
-  stepNumber={1}
->
-  <Step
-    finished={false}
-    onOpen={[MockFunction]}
-    open={true}
-    renderForm={[Function]}
-    renderResult={[Function]}
-    stepNumber={1}
-    stepTitle="onboarding.token.header"
-  >
-    <div
-      className="boxed-group onboarding-step is-open"
-    >
-      <div
-        className="onboarding-step-number"
-      >
-        1
-      </div>
-      <div
-        className="boxed-group-header"
-      >
-        <h2>
-          onboarding.token.header
-        </h2>
-      </div>
-      <div
-        className="boxed-group-inner"
-      >
-        <form
-          onSubmit={[Function]}
-        >
-          <span
-            className="text-middle"
-          >
-            my token
-            : 
-          </span>
-          <strong
-            className="spacer-right text-middle"
-          >
-            abcd1234
-          </strong>
-          <DeleteButton
-            className="button-small text-middle"
-            onClick={[Function]}
-          >
-            <ButtonIcon
-              className="button-small text-middle"
-              color="#d4333f"
-              onClick={[Function]}
-            >
-              <Button
-                className="button-small text-middle button-icon"
-                onClick={[Function]}
-                stopPropagation={true}
-                style={
-                  Object {
-                    "color": "#d4333f",
-                  }
-                }
-              >
-                <button
-                  className="button button-small text-middle button-icon"
-                  onClick={[Function]}
-                  style={
-                    Object {
-                      "color": "#d4333f",
-                    }
-                  }
-                  type="button"
-                >
-                  <ClearIcon />
-                </button>
-              </Button>
-            </ButtonIcon>
-          </DeleteButton>
-        </form>
-        <div
-          className="note big-spacer-top width-50"
-        >
-          onboarding.token.text
-        </div>
-        <div
-          className="big-spacer-top"
-        >
-          <Button
-            className="js-continue"
-            onClick={[Function]}
-          >
-            <button
-              className="button js-continue"
-              onClick={[Function]}
-              type="button"
-            >
-              continue
-            </button>
-          </Button>
-        </div>
-      </div>
-    </div>
-  </Step>
-</TokenStep>
-`;
-
-exports[`revokes token 2`] = `
-<TokenStep
-  currentUser={
-    Object {
-      "login": "user",
-    }
-  }
-  finished={false}
-  onContinue={[MockFunction]}
-  onOpen={[MockFunction]}
-  open={true}
-  stepNumber={1}
->
-  <Step
-    finished={false}
-    onOpen={[MockFunction]}
-    open={true}
-    renderForm={[Function]}
-    renderResult={[Function]}
-    stepNumber={1}
-    stepTitle="onboarding.token.header"
-  >
-    <div
-      className="boxed-group onboarding-step is-open"
-    >
-      <div
-        className="onboarding-step-number"
-      >
-        1
-      </div>
-      <div
-        className="boxed-group-header"
-      >
-        <h2>
-          onboarding.token.header
-        </h2>
-      </div>
-      <div
-        className="boxed-group-inner"
-      >
-        <form
-          onSubmit={[Function]}
-        >
-          <span
-            className="text-middle"
-          >
-            my token
-            : 
-          </span>
-          <strong
-            className="spacer-right text-middle"
-          >
-            abcd1234
-          </strong>
-          <i
-            className="spinner text-middle"
-          />
-        </form>
-        <div
-          className="note big-spacer-top width-50"
-        >
-          onboarding.token.text
-        </div>
-        <div
-          className="big-spacer-top"
-        >
-          <Button
-            className="js-continue"
-            onClick={[Function]}
-          >
-            <button
-              className="button js-continue"
-              onClick={[Function]}
-              type="button"
-            >
-              continue
-            </button>
-          </Button>
-        </div>
-      </div>
-    </div>
-  </Step>
-</TokenStep>
-`;
-
-exports[`revokes token 3`] = `
-<TokenStep
-  currentUser={
-    Object {
-      "login": "user",
-    }
-  }
-  finished={false}
-  onContinue={[MockFunction]}
-  onOpen={[MockFunction]}
-  open={true}
-  stepNumber={1}
->
-  <Step
-    finished={false}
-    onOpen={[MockFunction]}
-    open={true}
-    renderForm={[Function]}
-    renderResult={[Function]}
-    stepNumber={1}
-    stepTitle="onboarding.token.header"
-  >
-    <div
-      className="boxed-group onboarding-step is-open"
-    >
-      <div
-        className="onboarding-step-number"
-      >
-        1
-      </div>
-      <div
-        className="boxed-group-header"
-      >
-        <h2>
-          onboarding.token.header
-        </h2>
-      </div>
-      <div
-        className="boxed-group-inner"
-      >
-        <div>
-          <div>
-            <a
-              className="js-new link-base-color link-no-underline"
-              href="#"
-              onClick={[Function]}
-            >
-              <i
-                className="icon-radio spacer-right is-checked"
-              />
-              onboading.token.generate_token
-            </a>
-            <div
-              className="big-spacer-top"
-            >
-              <form
-                onSubmit={[Function]}
-              >
-                <input
-                  autoFocus={true}
-                  className="input-large spacer-right text-middle"
-                  onChange={[Function]}
-                  placeholder="onboading.token.generate_token.placeholder"
-                  required={true}
-                  type="text"
-                  value=""
-                />
-                <SubmitButton
-                  className="text-middle"
-                  disabled={true}
-                >
-                  <Button
-                    className="text-middle"
-                    disabled={true}
-                    preventDefault={false}
-                    type="submit"
-                  >
-                    <button
-                      className="button text-middle"
-                      disabled={true}
-                      onClick={[Function]}
-                      type="submit"
-                    >
-                      onboarding.token.generate
-                    </button>
-                  </Button>
-                </SubmitButton>
-              </form>
-            </div>
-          </div>
-          <div
-            className="big-spacer-top"
-          >
-            <a
-              className="js-new link-base-color link-no-underline"
-              href="#"
-              onClick={[Function]}
-            >
-              <i
-                className="icon-radio spacer-right"
-              />
-              onboarding.token.use_existing_token
-            </a>
-          </div>
-        </div>
-        <div
-          className="note big-spacer-top width-50"
-        >
-          onboarding.token.text
-        </div>
-      </div>
-    </div>
-  </Step>
-</TokenStep>
-`;
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
deleted file mode 100644 (file)
index 47fe520..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { translate } from '../../../../helpers/l10n';
-
-/*::
-type Props = {
-  className?: string,
-  os: string
-};
-*/
-
-const filenames = {
-  win: 'build-wrapper-win-x86.zip',
-  linux: 'build-wrapper-linux-x86.zip',
-  mac: 'build-wrapper-macosx-x86.zip'
-};
-
-export default function BuildWrapper(props /*: Props */) {
-  return (
-    <div className={props.className}>
-      <h4 className="spacer-bottom">
-        {translate('onboarding.analysis.build_wrapper.header', props.os)}
-      </h4>
-      <p
-        className="spacer-bottom markdown"
-        dangerouslySetInnerHTML={{
-          __html: translate('onboarding.analysis.build_wrapper.text', props.os)
-        }}
-      />
-      <p>
-        <a
-          className="button"
-          download={filenames[props.os]}
-          href={window.baseUrl + '/static/cpp/' + filenames[props.os]}
-          target="_blank">
-          {translate('download_verb')}
-        </a>
-      </p>
-    </div>
-  );
-}
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
deleted file mode 100644 (file)
index 0c1fdab..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import SQScanner from './SQScanner';
-import BuildWrapper from './BuildWrapper';
-import CodeSnippet from '../../../../components/common/CodeSnippet';
-import InstanceMessage from '../../../../components/common/InstanceMessage';
-import { translate } from '../../../../helpers/l10n';
-
-/*::
-type Props = {
-  host: string,
-  os: string,
-  organization?: string,
-  projectKey: string,
-  token: string
-};
-*/
-
-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 (
-    <div>
-      <SQScanner os={props.os} />
-      <BuildWrapper className="huge-spacer-top" os={props.os} />
-
-      <h4 className="huge-spacer-top spacer-bottom">
-        {translate('onboarding.analysis.sq_scanner.execute')}
-      </h4>
-      <InstanceMessage message={translate('onboarding.analysis.sq_scanner.execute.text')}>
-        {transformedMessage => (
-          <p
-            className="spacer-bottom markdown"
-            dangerouslySetInnerHTML={{ __html: transformedMessage }}
-          />
-        )}
-      </InstanceMessage>
-      <CodeSnippet isOneLine={true} snippet={command1} />
-      <CodeSnippet isOneLine={props.os === 'win'} snippet={command2} />
-      <p
-        className="big-spacer-top markdown"
-        dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.sq_scanner.docs') }}
-      />
-    </div>
-  );
-}
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
deleted file mode 100644 (file)
index a5bd3fd..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import MSBuildScanner from './MSBuildScanner';
-import CodeSnippet from '../../../../components/common/CodeSnippet';
-import InstanceMessage from '../../../../components/common/InstanceMessage';
-import { translate } from '../../../../helpers/l10n';
-
-/*::
-type Props = {|
-  host: string,
-  organization?: string,
-  projectKey: string,
-  token: string
-|};
-*/
-
-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 (
-    <div>
-      <MSBuildScanner />
-
-      <h4 className="huge-spacer-top spacer-bottom">
-        {translate('onboarding.analysis.msbuild.execute')}
-      </h4>
-      <InstanceMessage message={translate('onboarding.analysis.msbuild.execute.text')}>
-        {transformedMessage => (
-          <p
-            className="spacer-bottom markdown"
-            dangerouslySetInnerHTML={{ __html: transformedMessage }}
-          />
-        )}
-      </InstanceMessage>
-      <CodeSnippet isOneLine={true} snippet={command1} />
-      <CodeSnippet isOneLine={true} snippet={command2} />
-      <CodeSnippet isOneLine={true} snippet={command3} />
-      <p
-        className="big-spacer-top markdown"
-        dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.msbuild.docs') }}
-      />
-    </div>
-  );
-}
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
deleted file mode 100644 (file)
index d3cc1c8..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import CodeSnippet from '../../../../components/common/CodeSnippet';
-import InstanceMessage from '../../../../components/common/InstanceMessage';
-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.6"\n}';
-
-  const command = [
-    './gradlew sonarqube',
-    props.organization && `-Dsonar.organization=${props.organization}`,
-    `-Dsonar.host.url=${props.host}`,
-    `-Dsonar.login=${props.token}`
-  ];
-
-  return (
-    <div>
-      <h4 className="spacer-bottom">{translate('onboarding.analysis.java.gradle.header')}</h4>
-      <InstanceMessage message={translate('onboarding.analysis.java.gradle.text.1')}>
-        {transformedMessage => (
-          <p
-            className="spacer-bottom markdown"
-            dangerouslySetInnerHTML={{ __html: transformedMessage }}
-          />
-        )}
-      </InstanceMessage>
-      <CodeSnippet snippet={config} />
-      <p className="spacer-top spacer-bottom markdown">
-        {translate('onboarding.analysis.java.gradle.text.2')}
-      </p>
-      <CodeSnippet snippet={command} />
-      <p
-        className="big-spacer-top markdown"
-        dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.java.gradle.docs') }}
-      />
-      <p
-        className="big-spacer-top markdown"
-        dangerouslySetInnerHTML={{
-          __html: translate('onboarding.analysis.browse_url_after_analysis')
-        }}
-      />
-    </div>
-  );
-}
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
deleted file mode 100644 (file)
index 8a02fb7..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import CodeSnippet from '../../../../components/common/CodeSnippet';
-import InstanceMessage from '../../../../components/common/InstanceMessage';
-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 (
-    <div>
-      <h4 className="spacer-bottom">{translate('onboarding.analysis.java.maven.header')}</h4>
-      <p className="spacer-bottom markdown">
-        <InstanceMessage message={translate('onboarding.analysis.java.maven.text')} />
-      </p>
-      <CodeSnippet snippet={command} />
-      <p
-        className="big-spacer-top markdown"
-        dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.java.maven.docs') }}
-      />
-      <p
-        className="big-spacer-top markdown"
-        dangerouslySetInnerHTML={{
-          __html: translate('onboarding.analysis.browse_url_after_analysis')
-        }}
-      />
-    </div>
-  );
-}
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
deleted file mode 100644 (file)
index af0ade0..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { translate } from '../../../../helpers/l10n';
-
-/*::
-type Props = {
-  className?: string
-};
-*/
-
-export default function MSBuildScanner(props /*: Props */) {
-  return (
-    <div className={props.className}>
-      <h4 className="spacer-bottom">{translate('onboarding.analysis.msbuild.header')}</h4>
-      <p
-        className="spacer-bottom markdown"
-        dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.msbuild.text') }}
-      />
-      <p>
-        <a
-          className="button"
-          href="http://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html"
-          target="_blank">
-          {translate('download_verb')}
-        </a>
-      </p>
-    </div>
-  );
-}
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
deleted file mode 100644 (file)
index 03f5fe3..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import MSBuildScanner from './MSBuildScanner';
-import BuildWrapper from './BuildWrapper';
-import CodeSnippet from '../../../../components/common/CodeSnippet';
-import InstanceMessage from '../../../../components/common/InstanceMessage';
-import { translate } from '../../../../helpers/l10n';
-
-/*::
-type Props = {|
-  host: string,
-  organization?: string,
-  projectKey: string,
-  token: string
-|};
-*/
-
-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 (
-    <div>
-      <MSBuildScanner />
-      <BuildWrapper className="huge-spacer-top" os="win" />
-
-      <h4 className="huge-spacer-top spacer-bottom">
-        {translate('onboarding.analysis.msbuild.execute')}
-      </h4>
-      <InstanceMessage message={translate('onboarding.analysis.msbuild.execute.text')}>
-        {transformedMessage => (
-          <p
-            className="spacer-bottom markdown"
-            dangerouslySetInnerHTML={{ __html: transformedMessage }}
-          />
-        )}
-      </InstanceMessage>
-      <CodeSnippet isOneLine={true} snippet={command1} />
-      <CodeSnippet isOneLine={true} snippet={command2} />
-      <CodeSnippet isOneLine={true} snippet={command3} />
-      <p
-        className="big-spacer-top markdown"
-        dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.msbuild.docs') }}
-      />
-    </div>
-  );
-}
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
deleted file mode 100644 (file)
index 5bb0d0d..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import SQScanner from './SQScanner';
-import CodeSnippet from '../../../../components/common/CodeSnippet';
-import InstanceMessage from '../../../../components/common/InstanceMessage';
-import { translate } from '../../../../helpers/l10n';
-
-/*::
-type Props = {|
-  host: string,
-  organization?: string,
-  os: string,
-  projectKey: string,
-  token: string
-|};
-*/
-
-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 (
-    <div>
-      <SQScanner os={props.os} />
-
-      <h4 className="huge-spacer-top spacer-bottom">
-        {translate('onboarding.analysis.sq_scanner.execute')}
-      </h4>
-      <InstanceMessage message={translate('onboarding.analysis.sq_scanner.execute.text')}>
-        {transformedMessage => (
-          <p
-            className="spacer-bottom markdown"
-            dangerouslySetInnerHTML={{ __html: transformedMessage }}
-          />
-        )}
-      </InstanceMessage>
-      <CodeSnippet isOneLine={props.os === 'win'} snippet={command} />
-      <p
-        className="big-spacer-top markdown"
-        dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.sq_scanner.docs') }}
-      />
-    </div>
-  );
-}
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
deleted file mode 100644 (file)
index 3fd3e3e..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { translate } from '../../../../helpers/l10n';
-
-/*::
-type Props = {
-  className?: string,
-  os: string
-};
-*/
-
-export default function SQScanner(props /*: Props */) {
-  return (
-    <div className={props.className}>
-      <h4 className="spacer-bottom">
-        {translate('onboarding.analysis.sq_scanner.header', props.os)}
-      </h4>
-      <p
-        className="spacer-bottom markdown"
-        dangerouslySetInnerHTML={{
-          __html: translate('onboarding.analysis.sq_scanner.text', props.os)
-        }}
-      />
-      <p>
-        <a
-          className="button"
-          href="http://redirect.sonarsource.com/doc/install-configure-scanner.html"
-          target="_blank">
-          {translate('download_verb')}
-        </a>
-      </p>
-    </div>
-  );
-}
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
deleted file mode 100644 (file)
index 4d3f979..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import BuildWrapper from '../BuildWrapper';
-
-it('renders correctly', () => {
-  expect(shallow(<BuildWrapper os="win" />)).toMatchSnapshot();
-  expect(shallow(<BuildWrapper os="linux" />)).toMatchSnapshot();
-  expect(shallow(<BuildWrapper os="mac" />)).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
deleted file mode 100644 (file)
index 9f8f924..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import ClangGCC from '../ClangGCC';
-
-it('renders correctly', () => {
-  expect(
-    shallow(<ClangGCC host="host" os="win" projectKey="projectKey" token="token" />)
-  ).toMatchSnapshot();
-
-  expect(
-    shallow(<ClangGCC host="host" os="linux" projectKey="projectKey" token="token" />)
-  ).toMatchSnapshot();
-
-  expect(
-    shallow(
-      <ClangGCC
-        host="host"
-        os="linux"
-        organization="organization"
-        projectKey="projectKey"
-        token="token"
-      />
-    )
-  ).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
deleted file mode 100644 (file)
index b9dc347..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import DotNet from '../DotNet';
-
-it('renders correctly', () => {
-  expect(shallow(<DotNet host="host" projectKey="projectKey" token="token" />)).toMatchSnapshot();
-  expect(
-    shallow(
-      <DotNet host="host" organization="organization" projectKey="projectKey" token="token" />
-    )
-  ).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
deleted file mode 100644 (file)
index bfddcc9..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import JavaGradle from '../JavaGradle';
-
-it('renders correctly', () => {
-  expect(shallow(<JavaGradle host="host" token="token" />)).toMatchSnapshot();
-  expect(
-    shallow(<JavaGradle host="host" organization="organization" token="token" />)
-  ).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
deleted file mode 100644 (file)
index 592e578..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import JavaMaven from '../JavaMaven';
-
-it('renders correctly', () => {
-  expect(shallow(<JavaMaven host="host" token="token" />)).toMatchSnapshot();
-  expect(
-    shallow(<JavaMaven host="host" organization="organization" token="token" />)
-  ).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
deleted file mode 100644 (file)
index 5551fce..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import MSBuildScanner from '../MSBuildScanner';
-
-it('renders correctly', () => {
-  expect(shallow(<MSBuildScanner />)).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
deleted file mode 100644 (file)
index 62ccf80..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import Msvc from '../Msvc';
-
-it('renders correctly', () => {
-  expect(shallow(<Msvc host="host" projectKey="projectKey" token="token" />)).toMatchSnapshot();
-  expect(
-    shallow(<Msvc host="host" organization="organization" projectKey="projectKey" token="token" />)
-  ).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
deleted file mode 100644 (file)
index b717507..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import Other from '../Other';
-
-it('renders correctly', () => {
-  expect(
-    shallow(<Other host="host" os="win" projectKey="projectKey" token="token" />)
-  ).toMatchSnapshot();
-
-  expect(
-    shallow(<Other host="host" os="linux" projectKey="projectKey" token="token" />)
-  ).toMatchSnapshot();
-
-  expect(
-    shallow(
-      <Other
-        host="host"
-        os="linux"
-        organization="organization"
-        projectKey="projectKey"
-        token="token"
-      />
-    )
-  ).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
deleted file mode 100644 (file)
index 11a0ccb..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import SQScanner from '../SQScanner';
-
-it('renders correctly', () => {
-  expect(shallow(<SQScanner os="win" />)).toMatchSnapshot();
-  expect(shallow(<SQScanner os="linux" />)).toMatchSnapshot();
-  expect(shallow(<SQScanner os="mac" />)).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
deleted file mode 100644 (file)
index 07e8eb4..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly 1`] = `
-<div>
-  <h4
-    className="spacer-bottom"
-  >
-    onboarding.analysis.build_wrapper.header.win
-  </h4>
-  <p
-    className="spacer-bottom markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.build_wrapper.text.win",
-      }
-    }
-  />
-  <p>
-    <a
-      className="button"
-      download="build-wrapper-win-x86.zip"
-      href="/static/cpp/build-wrapper-win-x86.zip"
-      target="_blank"
-    >
-      download_verb
-    </a>
-  </p>
-</div>
-`;
-
-exports[`renders correctly 2`] = `
-<div>
-  <h4
-    className="spacer-bottom"
-  >
-    onboarding.analysis.build_wrapper.header.linux
-  </h4>
-  <p
-    className="spacer-bottom markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.build_wrapper.text.linux",
-      }
-    }
-  />
-  <p>
-    <a
-      className="button"
-      download="build-wrapper-linux-x86.zip"
-      href="/static/cpp/build-wrapper-linux-x86.zip"
-      target="_blank"
-    >
-      download_verb
-    </a>
-  </p>
-</div>
-`;
-
-exports[`renders correctly 3`] = `
-<div>
-  <h4
-    className="spacer-bottom"
-  >
-    onboarding.analysis.build_wrapper.header.mac
-  </h4>
-  <p
-    className="spacer-bottom markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.build_wrapper.text.mac",
-      }
-    }
-  />
-  <p>
-    <a
-      className="button"
-      download="build-wrapper-macosx-x86.zip"
-      href="/static/cpp/build-wrapper-macosx-x86.zip"
-      target="_blank"
-    >
-      download_verb
-    </a>
-  </p>
-</div>
-`;
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
deleted file mode 100644 (file)
index cdffd4c..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly 1`] = `
-<div>
-  <SQScanner
-    os="win"
-  />
-  <BuildWrapper
-    className="huge-spacer-top"
-    os="win"
-  />
-  <h4
-    className="huge-spacer-top spacer-bottom"
-  >
-    onboarding.analysis.sq_scanner.execute
-  </h4>
-  <InstanceMessage
-    message="onboarding.analysis.sq_scanner.execute.text"
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet="build-wrapper-win-x86-64.exe --out-dir bw-output make clean all"
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet={
-      Array [
-        "sonar-scanner.bat",
-        "-Dsonar.projectKey=projectKey",
-        undefined,
-        "-Dsonar.sources=.",
-        "-Dsonar.cfamily.build-wrapper-output=bw-output",
-        "-Dsonar.host.url=host",
-        "-Dsonar.login=token",
-      ]
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.sq_scanner.docs",
-      }
-    }
-  />
-</div>
-`;
-
-exports[`renders correctly 2`] = `
-<div>
-  <SQScanner
-    os="linux"
-  />
-  <BuildWrapper
-    className="huge-spacer-top"
-    os="linux"
-  />
-  <h4
-    className="huge-spacer-top spacer-bottom"
-  >
-    onboarding.analysis.sq_scanner.execute
-  </h4>
-  <InstanceMessage
-    message="onboarding.analysis.sq_scanner.execute.text"
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet="build-wrapper-linux-x86-64 --out-dir bw-output make clean all"
-  />
-  <CodeSnippet
-    isOneLine={false}
-    snippet={
-      Array [
-        "sonar-scanner",
-        "-Dsonar.projectKey=projectKey",
-        undefined,
-        "-Dsonar.sources=.",
-        "-Dsonar.cfamily.build-wrapper-output=bw-output",
-        "-Dsonar.host.url=host",
-        "-Dsonar.login=token",
-      ]
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.sq_scanner.docs",
-      }
-    }
-  />
-</div>
-`;
-
-exports[`renders correctly 3`] = `
-<div>
-  <SQScanner
-    os="linux"
-  />
-  <BuildWrapper
-    className="huge-spacer-top"
-    os="linux"
-  />
-  <h4
-    className="huge-spacer-top spacer-bottom"
-  >
-    onboarding.analysis.sq_scanner.execute
-  </h4>
-  <InstanceMessage
-    message="onboarding.analysis.sq_scanner.execute.text"
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet="build-wrapper-linux-x86-64 --out-dir bw-output make clean all"
-  />
-  <CodeSnippet
-    isOneLine={false}
-    snippet={
-      Array [
-        "sonar-scanner",
-        "-Dsonar.projectKey=projectKey",
-        "-Dsonar.organization=organization",
-        "-Dsonar.sources=.",
-        "-Dsonar.cfamily.build-wrapper-output=bw-output",
-        "-Dsonar.host.url=host",
-        "-Dsonar.login=token",
-      ]
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.sq_scanner.docs",
-      }
-    }
-  />
-</div>
-`;
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
deleted file mode 100644 (file)
index 89b57fa..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly 1`] = `
-<div>
-  <MSBuildScanner />
-  <h4
-    className="huge-spacer-top spacer-bottom"
-  >
-    onboarding.analysis.msbuild.execute
-  </h4>
-  <InstanceMessage
-    message="onboarding.analysis.msbuild.execute.text"
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet={
-      Array [
-        "SonarQube.Scanner.MSBuild.exe begin",
-        "/k:\\"projectKey\\"",
-        undefined,
-        "/d:sonar.host.url=\\"host\\"",
-        "/d:sonar.login=\\"token\\"",
-      ]
-    }
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet="MsBuild.exe /t:Rebuild"
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet={
-      Array [
-        "SonarQube.Scanner.MSBuild.exe end",
-        "/d:sonar.login=\\"token\\"",
-      ]
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.msbuild.docs",
-      }
-    }
-  />
-</div>
-`;
-
-exports[`renders correctly 2`] = `
-<div>
-  <MSBuildScanner />
-  <h4
-    className="huge-spacer-top spacer-bottom"
-  >
-    onboarding.analysis.msbuild.execute
-  </h4>
-  <InstanceMessage
-    message="onboarding.analysis.msbuild.execute.text"
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet={
-      Array [
-        "SonarQube.Scanner.MSBuild.exe begin",
-        "/k:\\"projectKey\\"",
-        "/d:sonar.organization=\\"organization\\"",
-        "/d:sonar.host.url=\\"host\\"",
-        "/d:sonar.login=\\"token\\"",
-      ]
-    }
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet="MsBuild.exe /t:Rebuild"
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet={
-      Array [
-        "SonarQube.Scanner.MSBuild.exe end",
-        "/d:sonar.login=\\"token\\"",
-      ]
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.msbuild.docs",
-      }
-    }
-  />
-</div>
-`;
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
deleted file mode 100644 (file)
index 401137a..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly 1`] = `
-<div>
-  <h4
-    className="spacer-bottom"
-  >
-    onboarding.analysis.java.gradle.header
-  </h4>
-  <InstanceMessage
-    message="onboarding.analysis.java.gradle.text.1"
-  />
-  <CodeSnippet
-    snippet="plugins {
-  id \\"org.sonarqube\\" version \\"2.6\\"
-}"
-  />
-  <p
-    className="spacer-top spacer-bottom markdown"
-  >
-    onboarding.analysis.java.gradle.text.2
-  </p>
-  <CodeSnippet
-    snippet={
-      Array [
-        "./gradlew sonarqube",
-        undefined,
-        "-Dsonar.host.url=host",
-        "-Dsonar.login=token",
-      ]
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.java.gradle.docs",
-      }
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.browse_url_after_analysis",
-      }
-    }
-  />
-</div>
-`;
-
-exports[`renders correctly 2`] = `
-<div>
-  <h4
-    className="spacer-bottom"
-  >
-    onboarding.analysis.java.gradle.header
-  </h4>
-  <InstanceMessage
-    message="onboarding.analysis.java.gradle.text.1"
-  />
-  <CodeSnippet
-    snippet="plugins {
-  id \\"org.sonarqube\\" version \\"2.6\\"
-}"
-  />
-  <p
-    className="spacer-top spacer-bottom markdown"
-  >
-    onboarding.analysis.java.gradle.text.2
-  </p>
-  <CodeSnippet
-    snippet={
-      Array [
-        "./gradlew sonarqube",
-        "-Dsonar.organization=organization",
-        "-Dsonar.host.url=host",
-        "-Dsonar.login=token",
-      ]
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.java.gradle.docs",
-      }
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.browse_url_after_analysis",
-      }
-    }
-  />
-</div>
-`;
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
deleted file mode 100644 (file)
index d4462b9..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly 1`] = `
-<div>
-  <h4
-    className="spacer-bottom"
-  >
-    onboarding.analysis.java.maven.header
-  </h4>
-  <p
-    className="spacer-bottom markdown"
-  >
-    <InstanceMessage
-      message="onboarding.analysis.java.maven.text"
-    />
-  </p>
-  <CodeSnippet
-    snippet={
-      Array [
-        "mvn sonar:sonar",
-        undefined,
-        "-Dsonar.host.url=host",
-        "-Dsonar.login=token",
-      ]
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.java.maven.docs",
-      }
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.browse_url_after_analysis",
-      }
-    }
-  />
-</div>
-`;
-
-exports[`renders correctly 2`] = `
-<div>
-  <h4
-    className="spacer-bottom"
-  >
-    onboarding.analysis.java.maven.header
-  </h4>
-  <p
-    className="spacer-bottom markdown"
-  >
-    <InstanceMessage
-      message="onboarding.analysis.java.maven.text"
-    />
-  </p>
-  <CodeSnippet
-    snippet={
-      Array [
-        "mvn sonar:sonar",
-        "-Dsonar.organization=organization",
-        "-Dsonar.host.url=host",
-        "-Dsonar.login=token",
-      ]
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.java.maven.docs",
-      }
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.browse_url_after_analysis",
-      }
-    }
-  />
-</div>
-`;
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
deleted file mode 100644 (file)
index c7f156b..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly 1`] = `
-<div>
-  <h4
-    className="spacer-bottom"
-  >
-    onboarding.analysis.msbuild.header
-  </h4>
-  <p
-    className="spacer-bottom markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.msbuild.text",
-      }
-    }
-  />
-  <p>
-    <a
-      className="button"
-      href="http://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html"
-      target="_blank"
-    >
-      download_verb
-    </a>
-  </p>
-</div>
-`;
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
deleted file mode 100644 (file)
index 2240a13..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly 1`] = `
-<div>
-  <MSBuildScanner />
-  <BuildWrapper
-    className="huge-spacer-top"
-    os="win"
-  />
-  <h4
-    className="huge-spacer-top spacer-bottom"
-  >
-    onboarding.analysis.msbuild.execute
-  </h4>
-  <InstanceMessage
-    message="onboarding.analysis.msbuild.execute.text"
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet={
-      Array [
-        "SonarQube.Scanner.MSBuild.exe begin",
-        "/k:\\"projectKey\\"",
-        undefined,
-        "/d:sonar.cfamily.build-wrapper-output=bw-output",
-        "/d:sonar.host.url=\\"host\\"",
-        "/d:sonar.login=\\"token\\"",
-      ]
-    }
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet="build-wrapper-win-x86-64.exe --out-dir bw-output MsBuild.exe /t:Rebuild"
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet={
-      Array [
-        "SonarQube.Scanner.MSBuild.exe end",
-        "/d:sonar.login=\\"token\\"",
-      ]
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.msbuild.docs",
-      }
-    }
-  />
-</div>
-`;
-
-exports[`renders correctly 2`] = `
-<div>
-  <MSBuildScanner />
-  <BuildWrapper
-    className="huge-spacer-top"
-    os="win"
-  />
-  <h4
-    className="huge-spacer-top spacer-bottom"
-  >
-    onboarding.analysis.msbuild.execute
-  </h4>
-  <InstanceMessage
-    message="onboarding.analysis.msbuild.execute.text"
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet={
-      Array [
-        "SonarQube.Scanner.MSBuild.exe begin",
-        "/k:\\"projectKey\\"",
-        "/d:sonar.organization=\\"organization\\"",
-        "/d:sonar.cfamily.build-wrapper-output=bw-output",
-        "/d:sonar.host.url=\\"host\\"",
-        "/d:sonar.login=\\"token\\"",
-      ]
-    }
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet="build-wrapper-win-x86-64.exe --out-dir bw-output MsBuild.exe /t:Rebuild"
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet={
-      Array [
-        "SonarQube.Scanner.MSBuild.exe end",
-        "/d:sonar.login=\\"token\\"",
-      ]
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.msbuild.docs",
-      }
-    }
-  />
-</div>
-`;
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
deleted file mode 100644 (file)
index 73aba35..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly 1`] = `
-<div>
-  <SQScanner
-    os="win"
-  />
-  <h4
-    className="huge-spacer-top spacer-bottom"
-  >
-    onboarding.analysis.sq_scanner.execute
-  </h4>
-  <InstanceMessage
-    message="onboarding.analysis.sq_scanner.execute.text"
-  />
-  <CodeSnippet
-    isOneLine={true}
-    snippet={
-      Array [
-        "sonar-scanner.bat",
-        "-Dsonar.projectKey=projectKey",
-        undefined,
-        "-Dsonar.sources=.",
-        "-Dsonar.host.url=host",
-        "-Dsonar.login=token",
-      ]
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.sq_scanner.docs",
-      }
-    }
-  />
-</div>
-`;
-
-exports[`renders correctly 2`] = `
-<div>
-  <SQScanner
-    os="linux"
-  />
-  <h4
-    className="huge-spacer-top spacer-bottom"
-  >
-    onboarding.analysis.sq_scanner.execute
-  </h4>
-  <InstanceMessage
-    message="onboarding.analysis.sq_scanner.execute.text"
-  />
-  <CodeSnippet
-    isOneLine={false}
-    snippet={
-      Array [
-        "sonar-scanner",
-        "-Dsonar.projectKey=projectKey",
-        undefined,
-        "-Dsonar.sources=.",
-        "-Dsonar.host.url=host",
-        "-Dsonar.login=token",
-      ]
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.sq_scanner.docs",
-      }
-    }
-  />
-</div>
-`;
-
-exports[`renders correctly 3`] = `
-<div>
-  <SQScanner
-    os="linux"
-  />
-  <h4
-    className="huge-spacer-top spacer-bottom"
-  >
-    onboarding.analysis.sq_scanner.execute
-  </h4>
-  <InstanceMessage
-    message="onboarding.analysis.sq_scanner.execute.text"
-  />
-  <CodeSnippet
-    isOneLine={false}
-    snippet={
-      Array [
-        "sonar-scanner",
-        "-Dsonar.projectKey=projectKey",
-        "-Dsonar.organization=organization",
-        "-Dsonar.sources=.",
-        "-Dsonar.host.url=host",
-        "-Dsonar.login=token",
-      ]
-    }
-  />
-  <p
-    className="big-spacer-top markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.sq_scanner.docs",
-      }
-    }
-  />
-</div>
-`;
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
deleted file mode 100644 (file)
index bcdac07..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly 1`] = `
-<div>
-  <h4
-    className="spacer-bottom"
-  >
-    onboarding.analysis.sq_scanner.header.win
-  </h4>
-  <p
-    className="spacer-bottom markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.sq_scanner.text.win",
-      }
-    }
-  />
-  <p>
-    <a
-      className="button"
-      href="http://redirect.sonarsource.com/doc/install-configure-scanner.html"
-      target="_blank"
-    >
-      download_verb
-    </a>
-  </p>
-</div>
-`;
-
-exports[`renders correctly 2`] = `
-<div>
-  <h4
-    className="spacer-bottom"
-  >
-    onboarding.analysis.sq_scanner.header.linux
-  </h4>
-  <p
-    className="spacer-bottom markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.sq_scanner.text.linux",
-      }
-    }
-  />
-  <p>
-    <a
-      className="button"
-      href="http://redirect.sonarsource.com/doc/install-configure-scanner.html"
-      target="_blank"
-    >
-      download_verb
-    </a>
-  </p>
-</div>
-`;
-
-exports[`renders correctly 3`] = `
-<div>
-  <h4
-    className="spacer-bottom"
-  >
-    onboarding.analysis.sq_scanner.header.mac
-  </h4>
-  <p
-    className="spacer-bottom markdown"
-    dangerouslySetInnerHTML={
-      Object {
-        "__html": "onboarding.analysis.sq_scanner.text.mac",
-      }
-    }
-  />
-  <p>
-    <a
-      className="button"
-      href="http://redirect.sonarsource.com/doc/install-configure-scanner.html"
-      target="_blank"
-    >
-      download_verb
-    </a>
-  </p>
-</div>
-`;
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
deleted file mode 100644 (file)
index 9c8dfc0..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-.onboarding {
-  min-height: calc(70vh - 60px);
-}
-
-.onboarding-step {
-  position: relative;
-  padding-left: 34px;
-}
-
-.onboarding-step:not(.is-open):not(.is-finished) {
-  opacity: 0.4;
-}
-
-.onboarding-step .boxed-group-actions {
-  height: var(--controlHeight);
-  line-height: var(--controlHeight);
-}
-
-.onboarding-step-number {
-  position: absolute;
-  top: 15px;
-  left: 15px;
-  width: 24px;
-  height: 24px;
-  line-height: 24px;
-  border-radius: 24px;
-  background-color: #b9b9b9;
-  color: #fff;
-  font-size: var(--mediumFontSize);
-  text-align: center;
-}
-
-.onboarding-step.is-open .onboarding-step-number {
-  background-color: var(--darkBlue);
-}
-
-.onboarding-step.is-finished {
-  cursor: pointer;
-  outline: none;
-}
-
-.onboarding .page-actions {
-  text-align: right;
-  margin-bottom: 0;
-}
-
-.onboarding .page-actions p {
-  line-height: 16px;
-  margin-top: 6px;
-}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/AnalysisStep.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/AnalysisStep.tsx
new file mode 100644 (file)
index 0000000..a91ff54
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import Step from './Step';
+import LanguageStep, { 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';
+import { getHostUrl } from '../../../helpers/urls';
+
+interface Props {
+  onFinish: (projectKey?: string) => void;
+  onReset: () => void;
+  open: boolean;
+  organization?: string;
+  stepNumber: number;
+  token?: string;
+}
+
+interface State {
+  result?: Result;
+}
+
+export default class AnalysisStep extends React.PureComponent<Props, State> {
+  state: State = {};
+
+  handleLanguageSelect = (result?: Result) => {
+    this.setState({ result });
+    const projectKey = result && result.language !== 'java' ? result.projectKey : undefined;
+    this.props.onFinish(projectKey);
+  };
+
+  handleLanguageReset = () => {
+    this.setState({ result: undefined });
+    this.props.onReset();
+  };
+
+  renderForm = () => {
+    return (
+      <div className="boxed-group-inner">
+        <div className="flex-columns">
+          <div className="flex-column flex-column-half bordered-right">
+            <LanguageStep
+              onDone={this.handleLanguageSelect}
+              onReset={this.handleLanguageReset}
+              organization={this.props.organization}
+            />
+          </div>
+          <div className="flex-column flex-column-half">{this.renderCommand()}</div>
+        </div>
+      </div>
+    );
+  };
+
+  renderFormattedCommand = (...lines: Array<string>) => (
+    // keep this "useless" concatentation for the readability reason
+    // eslint-disable-next-line no-useless-concat
+    <pre>{lines.join(' ' + '\\' + '\n' + '  ')}</pre>
+  );
+
+  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 = () => {
+    const { token } = this.props;
+    if (!token) {
+      return null;
+    }
+    return <JavaMaven host={getHostUrl()} organization={this.props.organization} token={token} />;
+  };
+
+  renderCommandForGradle = () => {
+    const { token } = this.props;
+    if (!token) {
+      return null;
+    }
+    return <JavaGradle host={getHostUrl()} organization={this.props.organization} token={token} />;
+  };
+
+  renderCommandForDotNet = () => {
+    const { token } = this.props;
+    const { result } = this.state;
+    if (!result || !result.projectKey || !token) {
+      return null;
+    }
+    return (
+      <DotNet
+        host={getHostUrl()}
+        organization={this.props.organization}
+        projectKey={result.projectKey}
+        token={token}
+      />
+    );
+  };
+
+  renderCommandForMSVC = () => {
+    const { token } = this.props;
+    const { result } = this.state;
+    if (!result || !result.projectKey || !token) {
+      return null;
+    }
+    return (
+      <Msvc
+        host={getHostUrl()}
+        organization={this.props.organization}
+        projectKey={result.projectKey}
+        token={token}
+      />
+    );
+  };
+
+  renderCommandForClangGCC = () => {
+    const { token } = this.props;
+    const { result } = this.state;
+    if (!result || !result.projectKey || !result.os || !token) {
+      return null;
+    }
+    return (
+      <ClangGCC
+        host={getHostUrl()}
+        organization={this.props.organization}
+        os={result.os}
+        projectKey={result.projectKey}
+        token={token}
+      />
+    );
+  };
+
+  renderCommandForOther = () => {
+    const { token } = this.props;
+    const { result } = this.state;
+    if (!result || !result.projectKey || !result.os || !token) {
+      return null;
+    }
+    return (
+      <Other
+        host={getHostUrl()}
+        organization={this.props.organization}
+        os={result.os}
+        projectKey={result.projectKey}
+        token={token}
+      />
+    );
+  };
+
+  renderResult = () => null;
+
+  render() {
+    return (
+      <Step
+        finished={false}
+        onOpen={() => {}}
+        open={this.props.open}
+        renderForm={this.renderForm}
+        renderResult={this.renderResult}
+        stepNumber={this.props.stepNumber}
+        stepTitle={translate('onboarding.analysis.header')}
+      />
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/LanguageStep.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/LanguageStep.tsx
new file mode 100644 (file)
index 0000000..25b7544
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import NewProjectForm from './NewProjectForm';
+import RadioToggle from '../../../components/controls/RadioToggle';
+import { translate } from '../../../helpers/l10n';
+import { isSonarCloud } from '../../../helpers/system';
+
+export interface Result {
+  language?: string;
+  javaBuild?: string;
+  cFamilyCompiler?: string;
+  os?: string;
+  projectKey?: string;
+}
+
+interface Props {
+  onDone: (result: Result) => void;
+  onReset: () => void;
+  organization?: string;
+}
+
+type State = Result;
+
+export default class LanguageStep extends React.PureComponent<Props, State> {
+  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 = () => (
+    <div className="big-spacer-top">
+      <h4 className="spacer-bottom">{translate('onboarding.language.java.build_technology')}</h4>
+      <RadioToggle
+        name="java-build"
+        onCheck={this.handleJavaBuildChange}
+        options={['maven', 'gradle'].map(build => ({
+          label: translate('onboarding.language.java.build_technology', build),
+          value: build
+        }))}
+        value={this.state.javaBuild}
+      />
+    </div>
+  );
+
+  renderCFamilyCompiler = () => (
+    <div className="big-spacer-top">
+      <h4 className="spacer-bottom">{translate('onboarding.language.c-family.compiler')}</h4>
+      <RadioToggle
+        name="c-family-compiler"
+        onCheck={this.handleCFamilyCompilerChange}
+        options={['msvc', 'clang-gcc'].map(compiler => ({
+          label: translate('onboarding.language.c-family.compiler', compiler),
+          value: compiler
+        }))}
+        value={this.state.cFamilyCompiler}
+      />
+    </div>
+  );
+
+  renderOS = () => (
+    <div className="big-spacer-top">
+      <h4 className="spacer-bottom">{translate('onboarding.language.os')}</h4>
+      <RadioToggle
+        name="os"
+        onCheck={this.handleOSChange}
+        options={['linux', 'win', 'mac'].map(os => ({
+          label: translate('onboarding.language.os', os),
+          value: os
+        }))}
+        value={this.state.os}
+      />
+    </div>
+  );
+
+  renderProjectKey = () => (
+    <NewProjectForm
+      onDelete={this.handleProjectKeyDelete}
+      onDone={this.handleProjectKeyDone}
+      organization={this.props.organization}
+      projectKey={this.state.projectKey}
+    />
+  );
+
+  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 = isSonarCloud()
+      ? ['java', 'dotnet', 'c-family', 'other']
+      : ['java', 'dotnet', 'other'];
+
+    return (
+      <div>
+        <div>
+          <h4 className="spacer-bottom">{translate('onboarding.language')}</h4>
+          <RadioToggle
+            name="language"
+            onCheck={this.handleLanguageChange}
+            options={languages.map(language => ({
+              label: translate('onboarding.language', language),
+              value: language
+            }))}
+            value={this.state.language}
+          />
+        </div>
+        {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()}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewOrganizationForm.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewOrganizationForm.tsx
new file mode 100644 (file)
index 0000000..1a0e550
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { debounce } from 'lodash';
+import {
+  createOrganization,
+  deleteOrganization,
+  getOrganization
+} from '../../../api/organizations';
+import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon';
+import { DeleteButton, SubmitButton } from '../../../components/ui/buttons';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  onDelete: () => void;
+  onDone: (organization: string) => void;
+  organization?: string;
+}
+
+interface State {
+  done: boolean;
+  loading: boolean;
+  organization: string;
+  unique: boolean;
+}
+
+export default class NewOrganizationForm extends React.PureComponent<Props, State> {
+  mounted = false;
+
+  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: React.ChangeEvent<HTMLInputElement>) => {
+    const organization = this.sanitizeOrganization(event.target.value);
+    this.setState({ organization });
+    this.validateOrganization(organization);
+  };
+
+  handleOrganizationCreate = (event: React.FormEvent<HTMLFormElement>) => {
+    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 = () => {
+    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 ? (
+      <div>
+        <span className="spacer-right text-middle">{organization}</span>
+        {loading ? (
+          <i className="spinner text-middle" />
+        ) : (
+          <DeleteButton className="button-small" onClick={this.handleOrganizationDelete} />
+        )}
+      </div>
+    ) : (
+      <form onSubmit={this.handleOrganizationCreate}>
+        <input
+          autoFocus={true}
+          className="input-super-large spacer-right text-middle"
+          maxLength={32}
+          minLength={2}
+          onChange={this.handleOrganizationChange}
+          placeholder={translate('onboarding.organization.placeholder')}
+          required={true}
+          type="text"
+          value={organization}
+        />
+        {loading ? (
+          <i className="spinner text-middle" />
+        ) : (
+          <SubmitButton className="text-middle" disabled={!valid}>
+            {translate('create')}
+          </SubmitButton>
+        )}
+        {!unique && (
+          <span className="big-spacer-left text-danger text-middle">
+            <AlertErrorIcon className="little-spacer-right text-text-top" />
+            {translate('this_name_is_already_taken')}
+          </span>
+        )}
+        <div className="note spacer-top abs-width-300">
+          {translate('onboarding.organization.key_requirement')}
+        </div>
+      </form>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewProjectForm.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewProjectForm.tsx
new file mode 100644 (file)
index 0000000..f09b4d1
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { createProject, deleteProject } from '../../../api/components';
+import { DeleteButton, SubmitButton } from '../../../components/ui/buttons';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  onDelete: () => void;
+  onDone: (projectKey: string) => void;
+  organization?: string;
+  projectKey?: string;
+}
+
+interface State {
+  done: boolean;
+  loading: boolean;
+  projectKey: string;
+}
+
+export default class NewProjectForm extends React.PureComponent<Props, State> {
+  mounted = false;
+
+  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: React.ChangeEvent<HTMLInputElement>) => {
+    this.setState({ projectKey: this.sanitizeProjectKey(event.target.value) });
+  };
+
+  handleProjectCreate = (event: React.FormEvent<HTMLFormElement>) => {
+    event.preventDefault();
+    const { projectKey } = this.state;
+    const data: {
+      name: string;
+      project: string;
+      organization?: 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 = () => {
+    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 ? (
+      <div>
+        <span className="spacer-right text-middle">{projectKey}</span>
+        {loading ? (
+          <i className="spinner text-middle" />
+        ) : (
+          <DeleteButton className="button-small text-middle" onClick={this.handleProjectDelete} />
+        )}
+      </div>
+    ) : (
+      <form onSubmit={this.handleProjectCreate}>
+        <input
+          autoFocus={true}
+          className="input-large spacer-right text-middle"
+          maxLength={400}
+          minLength={1}
+          onChange={this.handleProjectKeyChange}
+          required={true}
+          type="text"
+          value={projectKey}
+        />
+        {loading ? (
+          <i className="spinner text-middle" />
+        ) : (
+          <SubmitButton className="text-middle" disabled={!valid}>
+            {translate('Done')}
+          </SubmitButton>
+        )}
+        <div className="note spacer-top abs-width-300">
+          {translate('onboarding.project_key_requirement')}
+        </div>
+      </form>
+    );
+
+    return (
+      <div className="big-spacer-top">
+        <h4 className="spacer-bottom">{translate('onboarding.language.project_key')}</h4>
+        {form}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/OrganizationStep.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/OrganizationStep.tsx
new file mode 100644 (file)
index 0000000..0d155ef
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { sortBy } from 'lodash';
+import Step from './Step';
+import NewOrganizationForm from './NewOrganizationForm';
+import DocTooltip from '../../../components/docs/DocTooltip';
+import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon';
+import { getOrganizations } from '../../../api/organizations';
+import Select from '../../../components/controls/Select';
+import { translate } from '../../../helpers/l10n';
+import { Button } from '../../../components/ui/buttons';
+
+interface Props {
+  currentUser: { login: string; isLoggedIn: boolean };
+  finished: boolean;
+  onOpen: () => void;
+  onContinue: (organization: string) => void;
+  open: boolean;
+  stepNumber: number;
+}
+
+interface State {
+  loading: boolean;
+  newOrganization?: string;
+  existingOrganization?: string;
+  existingOrganizations: Array<string>;
+  personalOrganization?: string;
+  selection: 'personal' | 'existing' | 'new';
+}
+
+export default class OrganizationStep extends React.PureComponent<Props, State> {
+  mounted = false;
+  state: State = {
+    loading: true,
+    existingOrganizations: [],
+    selection: 'personal'
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.fetchOrganizations();
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  fetchOrganizations = () => {
+    getOrganizations({ member: true }).then(
+      ({ organizations }) => {
+        if (this.mounted) {
+          const organizationKeys = organizations.filter(o => o.isAdmin).map(o => o.key);
+          // best guess: if there is only one organization, then it is personal
+          // otherwise, we can't guess, let's display them all as just "existing organizations"
+          const personalOrganization =
+            organizationKeys.length === 1 ? organizationKeys[0] : undefined;
+          const existingOrganizations = organizationKeys.length > 1 ? sortBy(organizationKeys) : [];
+          const selection = personalOrganization
+            ? 'personal'
+            : existingOrganizations.length > 0 ? 'existing' : 'new';
+          this.setState({
+            loading: false,
+            existingOrganizations,
+            personalOrganization,
+            selection
+          });
+        }
+      },
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+      }
+    );
+  };
+
+  getSelectedOrganization = () => {
+    switch (this.state.selection) {
+      case 'personal':
+        return this.state.personalOrganization;
+      case 'existing':
+        return this.state.existingOrganization;
+      case 'new':
+        return this.state.newOrganization;
+      default:
+        return null;
+    }
+  };
+
+  handlePersonalClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+    event.preventDefault();
+    this.setState({ selection: 'personal' });
+  };
+
+  handleExistingClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+    event.preventDefault();
+    this.setState({ selection: 'existing' });
+  };
+
+  handleNewClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+    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 = () => {
+    const organization = this.getSelectedOrganization();
+    if (organization) {
+      this.props.onContinue(organization);
+    }
+  };
+
+  renderPersonalOrganizationOption = () => (
+    <div>
+      <a className="link-base-color link-no-underline" href="#" onClick={this.handlePersonalClick}>
+        <i
+          className={classNames('icon-radio', 'spacer-right', {
+            'is-checked': this.state.selection === 'personal'
+          })}
+        />
+        {translate('onboarding.organization.my_personal_organization')}
+        <span className="note spacer-left">{this.state.personalOrganization}</span>
+      </a>
+    </div>
+  );
+
+  renderExistingOrganizationOption = () => (
+    <div className="big-spacer-top">
+      <a
+        className="js-existing link-base-color link-no-underline"
+        href="#"
+        onClick={this.handleExistingClick}>
+        <i
+          className={classNames('icon-radio', 'spacer-right', {
+            'is-checked': this.state.selection === 'existing'
+          })}
+        />
+        {translate('onboarding.organization.exising_organization')}
+      </a>
+      {this.state.selection === 'existing' && (
+        <div className="big-spacer-top">
+          <Select
+            className="input-super-large"
+            clearable={false}
+            onChange={this.handleExistingOrganizationSelect}
+            options={this.state.existingOrganizations.map(organization => ({
+              label: organization,
+              value: organization
+            }))}
+            value={this.state.existingOrganization}
+          />
+        </div>
+      )}
+    </div>
+  );
+
+  renderNewOrganizationOption = () => (
+    <div className="big-spacer-top">
+      <a
+        className="js-new link-base-color link-no-underline"
+        href="#"
+        onClick={this.handleNewClick}>
+        <i
+          className={classNames('icon-radio', 'spacer-right', {
+            'is-checked': this.state.selection === 'new'
+          })}
+        />
+        {translate('onboarding.organization.create_another_organization')}
+      </a>
+      {this.state.selection === 'new' && (
+        <div className="big-spacer-top">
+          <NewOrganizationForm
+            onDelete={this.handleOrganizationDelete}
+            onDone={this.handleOrganizationCreate}
+            organization={this.state.newOrganization}
+          />
+        </div>
+      )}
+    </div>
+  );
+
+  renderForm = () => {
+    return (
+      <div className="boxed-group-inner">
+        <div className="big-spacer-bottom width-50">
+          {translate('onboarding.organization.text')}
+        </div>
+
+        {this.state.loading ? (
+          <i className="spinner" />
+        ) : (
+          <div>
+            {this.state.personalOrganization && this.renderPersonalOrganizationOption()}
+            {this.state.existingOrganizations.length > 0 && this.renderExistingOrganizationOption()}
+            {this.renderNewOrganizationOption()}
+          </div>
+        )}
+
+        {this.getSelectedOrganization() != null &&
+          !this.state.loading && (
+            <div className="big-spacer-top">
+              <Button className="js-continue" onClick={this.handleContinueClick}>
+                {translate('continue')}
+              </Button>
+            </div>
+          )}
+      </div>
+    );
+  };
+
+  renderResult = () => {
+    const result = this.getSelectedOrganization();
+
+    return result != null ? (
+      <div className="boxed-group-actions display-flex-center">
+        <AlertSuccessIcon className="spacer-right" />
+        <strong>{result}</strong>
+      </div>
+    ) : null;
+  };
+
+  render() {
+    return (
+      <Step
+        finished={this.props.finished}
+        onOpen={this.props.onOpen}
+        open={this.props.open}
+        renderForm={this.renderForm}
+        renderResult={this.renderResult}
+        stepNumber={this.props.stepNumber}
+        stepTitle={
+          <span>
+            {translate('onboarding.organization.header')}
+            <DocTooltip className="little-spacer-left" doc="organizations/organization" />
+          </span>
+        }
+      />
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx
new file mode 100644 (file)
index 0000000..384e172
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import Helmet from 'react-helmet';
+import { connect } from 'react-redux';
+import TokenStep from './TokenStep';
+import OrganizationStep from './OrganizationStep';
+import AnalysisStep from './AnalysisStep';
+import ProjectWatcher from './ProjectWatcher';
+import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
+import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer';
+import { CurrentUser, isLoggedIn } from '../../../app/types';
+import { ResetButtonLink } from '../../../components/ui/buttons';
+import { getProjectUrl } from '../../../helpers/urls';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { isSonarCloud } from '../../../helpers/system';
+import '../styles.css';
+
+interface OwnProps {
+  automatic?: boolean;
+  onFinish: () => void;
+}
+
+interface StateProps {
+  currentUser: CurrentUser;
+  organizationsEnabled: boolean;
+}
+
+type Props = OwnProps & StateProps;
+
+interface State {
+  finished: boolean;
+  organization?: string;
+  projectKey?: string;
+  step: string;
+  token?: string;
+}
+
+export class ProjectOnboarding extends React.PureComponent<Props, State> {
+  mounted = false;
+  static contextTypes = {
+    router: PropTypes.object
+  };
+
+  constructor(props: Props) {
+    super(props);
+    this.state = {
+      finished: false,
+      step: props.organizationsEnabled ? 'organization' : 'token'
+    };
+  }
+
+  componentDidMount() {
+    this.mounted = true;
+
+    // useCapture = true to receive the event before inputs
+    window.addEventListener('keydown', this.onKeyDown, true);
+
+    if (!isLoggedIn(this.props.currentUser)) {
+      handleRequiredAuthentication();
+    }
+  }
+
+  componentWillUnmount() {
+    window.removeEventListener('keydown', this.onKeyDown, true);
+    this.mounted = false;
+  }
+
+  onKeyDown = (event: KeyboardEvent) => {
+    if (event.key === 'Escape') {
+      this.finishOnboarding();
+    }
+  };
+
+  finishOnboarding = () => {
+    this.props.onFinish();
+    if (this.state.projectKey) {
+      this.context.router.push(getProjectUrl(this.state.projectKey));
+    }
+  };
+
+  handleTimeout = () => {
+    // unset `projectKey` to display a generic "Finish this tutorial" button
+    this.setState({ projectKey: undefined });
+  };
+
+  handleTokenDone = (token: string) => {
+    this.setState({ step: 'analysis', token });
+  };
+
+  handleOrganizationDone = (organization: string) => {
+    this.setState({ organization, step: 'token' });
+  };
+
+  handleTokenOpen = () => this.setState({ step: 'token' });
+
+  handleOrganizationOpen = () => this.setState({ step: 'organization' });
+
+  handleFinish = (projectKey?: string) => this.setState({ finished: true, projectKey });
+
+  handleReset = () => this.setState({ finished: false, projectKey: undefined });
+
+  render() {
+    const { automatic, currentUser, organizationsEnabled } = this.props;
+    if (!isLoggedIn(currentUser)) {
+      return null;
+    }
+
+    const { finished, projectKey, step, token } = this.state;
+    const header = translate('onboarding.project.header');
+    let stepNumber = 1;
+
+    return (
+      <>
+        <Helmet title={header} titleTemplate="%s" />
+        <header className="modal-head">
+          <h2>{header}</h2>
+        </header>
+        <div className="modal-body modal-container">
+          <p className="spacer-top big-spacer-bottom">
+            {translateWithParameters(
+              'onboarding.project.header.description',
+              organizationsEnabled ? 3 : 2
+            )}
+          </p>
+          {organizationsEnabled && (
+            <OrganizationStep
+              currentUser={currentUser}
+              finished={this.state.organization != null}
+              onContinue={this.handleOrganizationDone}
+              onOpen={this.handleOrganizationOpen}
+              open={step === 'organization'}
+              stepNumber={stepNumber++}
+            />
+          )}
+
+          <TokenStep
+            currentUser={currentUser}
+            finished={this.state.token != null}
+            onContinue={this.handleTokenDone}
+            onOpen={this.handleTokenOpen}
+            open={step === 'token'}
+            stepNumber={stepNumber++}
+          />
+
+          <AnalysisStep
+            onFinish={this.handleFinish}
+            onReset={this.handleReset}
+            open={step === 'analysis'}
+            organization={this.state.organization}
+            stepNumber={stepNumber}
+            token={token}
+          />
+        </div>
+        <footer className="modal-foot">
+          <ResetButtonLink className="js-skip" onClick={this.finishOnboarding}>
+            {(finished && translate('tutorials.finish')) ||
+              (automatic ? translate('tutorials.skip') : translate('close'))}
+          </ResetButtonLink>
+          {finished && projectKey ? (
+            <ProjectWatcher
+              onFinish={this.finishOnboarding}
+              onTimeout={this.handleTimeout}
+              projectKey={projectKey}
+            />
+          ) : (
+            <span className="pull-left note">
+              {translate(
+                isSonarCloud()
+                  ? 'tutorials.find_tutorial_back_in_plus'
+                  : 'tutorials.find_tutorial_back_in_help'
+              )}
+            </span>
+          )}
+        </footer>
+      </>
+    );
+  }
+}
+
+const mapStateToProps = (state: any): StateProps => {
+  return {
+    currentUser: getCurrentUser(state),
+    organizationsEnabled: areThereCustomOrganizations(state)
+  };
+};
+
+export default connect<StateProps, {}, OwnProps>(mapStateToProps)(ProjectOnboarding);
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingModal.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingModal.tsx
new file mode 100644 (file)
index 0000000..d91e788
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import Modal from '../../../components/controls/Modal';
+import { translate } from '../../../helpers/l10n';
+import { lazyLoad } from '../../../components/lazyLoad';
+
+interface Props {
+  automatic?: boolean;
+  onFinish: () => void;
+}
+
+const ProjectOnboarding = lazyLoad(() => import('./ProjectOnboarding'));
+
+export default function ProjectOnboardingModal(props: Props) {
+  return (
+    <Modal contentLabel={translate('tutorials.onboarding')} large={true}>
+      <ProjectOnboarding {...props} />
+    </Modal>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingPage.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingPage.tsx
new file mode 100644 (file)
index 0000000..5d0978e
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import ProjectOnboardingModal from './ProjectOnboardingModal';
+import { skipOnboarding } from '../../../store/users/actions';
+
+interface DispatchProps {
+  skipOnboarding: () => void;
+}
+
+export class ProjectOnboardingPage extends React.PureComponent<DispatchProps> {
+  static contextTypes = {
+    router: PropTypes.object.isRequired
+  };
+
+  onSkipOnboardingTutorial = () => {
+    this.props.skipOnboarding();
+    this.context.router.replace('/');
+  };
+
+  render() {
+    return <ProjectOnboardingModal onFinish={this.onSkipOnboardingTutorial} />;
+  }
+}
+
+const mapDispatchToProps: DispatchProps = { skipOnboarding };
+
+export default connect<{}, DispatchProps>(null, mapDispatchToProps)(ProjectOnboardingPage);
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectWatcher.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectWatcher.tsx
new file mode 100644 (file)
index 0000000..31683d2
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import * as classNames from 'classnames';
+import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon';
+import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon';
+import { getTasksForComponent } from '../../../api/ce';
+import { STATUSES } from '../../../apps/background-tasks/constants';
+import { translate } from '../../../helpers/l10n';
+
+const INTERVAL = 5000;
+const TIMEOUT = 10 * 60 * 1000; // 10 min
+
+interface Props {
+  onFinish: () => void;
+  onTimeout: () => void;
+  projectKey: string;
+}
+
+interface State {
+  inQueue: boolean;
+  status?: string;
+}
+
+export default class ProjectWatcher extends React.PureComponent<Props, State> {
+  interval?: number;
+  timeout?: number;
+  mounted = false;
+  state: State = { inQueue: false };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.watch();
+    this.timeout = window.setTimeout(this.props.onTimeout, TIMEOUT);
+  }
+
+  componentWillUnmount() {
+    clearInterval(this.interval);
+    clearInterval(this.timeout);
+    this.mounted = false;
+  }
+
+  watch = () => (this.interval = window.setTimeout(this.checkProject, INTERVAL));
+
+  checkProject = () => {
+    const { projectKey } = this.props;
+    getTasksForComponent(projectKey).then(
+      response => {
+        if (response.queue.length > 0) {
+          this.setState({ inQueue: true });
+        }
+
+        if (response.current != null) {
+          const { status } = response.current;
+          this.setState({ status });
+          if (status === STATUSES.SUCCESS) {
+            this.props.onFinish();
+          } else if (status === STATUSES.PENDING || status === STATUSES.IN_PROGRESS) {
+            this.watch();
+          }
+        } else {
+          this.watch();
+        }
+      },
+      () => {}
+    );
+  };
+
+  render() {
+    const { inQueue, status } = this.state;
+    const className = 'pull-left note';
+
+    if (status === STATUSES.SUCCESS) {
+      return (
+        <div className={classNames(className, 'display-inline-flex-center')}>
+          <AlertSuccessIcon className="spacer-right" />
+          {translate('onboarding.project_watcher.finished')}
+        </div>
+      );
+    }
+
+    if (inQueue || status === STATUSES.PENDING || status === STATUSES.IN_PROGRESS) {
+      return (
+        <div className={className}>
+          <i className="spinner spacer-right" />
+          {translate('onboarding.project_watcher.in_progress')}
+        </div>
+      );
+    }
+
+    if (status != null) {
+      return (
+        <div className={classNames(className, 'display-inline-flex-center')}>
+          <AlertErrorIcon className="spacer-right" />
+          {translate('onboarding.project_watcher.failed')}
+        </div>
+      );
+    }
+
+    return <div className={className}>{translate('onboarding.project_watcher.not_started')}</div>;
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/Step.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/Step.tsx
new file mode 100644 (file)
index 0000000..7fda387
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/no-noninteractive-tabindex */
+import * as React from 'react';
+import * as classNames from 'classnames';
+
+interface Props {
+  finished: boolean;
+  onOpen: () => void;
+  open: boolean;
+  renderForm: () => React.ReactNode;
+  renderResult: () => React.ReactNode;
+  stepNumber: number;
+  stepTitle: React.ReactNode;
+}
+
+export default function Step(props: Props) {
+  const className = classNames('boxed-group', 'onboarding-step', {
+    'is-open': props.open,
+    'is-finished': props.finished
+  });
+
+  const clickable = !props.open && props.finished;
+
+  const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
+    event.preventDefault();
+    props.onOpen();
+  };
+
+  return (
+    <div
+      className={className}
+      onClick={clickable ? handleClick : undefined}
+      role={clickable ? 'button' : undefined}
+      tabIndex={clickable ? 0 : undefined}>
+      <div className="onboarding-step-number">{props.stepNumber}</div>
+      {!props.open && props.renderResult()}
+      <div className="boxed-group-header">
+        <h2>{props.stepTitle}</h2>
+      </div>
+      {props.open ? props.renderForm() : <div className="boxed-group-inner" />}
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/TokenStep.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/TokenStep.tsx
new file mode 100644 (file)
index 0000000..e0cd2c5
--- /dev/null
@@ -0,0 +1,292 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import * as classNames from 'classnames';
+import Step from './Step';
+import { getTokens, generateToken, revokeToken } from '../../../api/user-tokens';
+import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon';
+import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon';
+import { DeleteButton, SubmitButton, Button } from '../../../components/ui/buttons';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  currentUser: { login: string };
+  finished: boolean;
+  open: boolean;
+  onContinue: (token: string) => void;
+  onOpen: () => void;
+  stepNumber: number;
+}
+
+interface State {
+  canUseExisting: boolean;
+  existingToken: string;
+  loading: boolean;
+  selection: string;
+  tokenName?: string;
+  token?: string;
+}
+
+export default class TokenStep extends React.PureComponent<Props, State> {
+  mounted = false;
+
+  state: State = {
+    canUseExisting: false,
+    existingToken: '',
+    loading: false,
+    selection: 'generate'
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+    getTokens(this.props.currentUser.login).then(
+      tokens => {
+        if (this.mounted) {
+          this.setState({ canUseExisting: tokens.length > 0 });
+        }
+      },
+      () => {}
+    );
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  getToken = () =>
+    this.state.selection === 'generate' ? this.state.token : this.state.existingToken;
+
+  canContinue = () => {
+    const { existingToken, selection, token } = this.state;
+    const validExistingToken = existingToken.match(/^[a-z0-9]+$/) != null;
+    return (
+      (selection === 'generate' && token != null) ||
+      (selection === 'use-existing' && existingToken && validExistingToken)
+    );
+  };
+
+  handleTokenNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    this.setState({ tokenName: event.target.value });
+  };
+
+  handleTokenGenerate = (event: React.FormEvent<HTMLFormElement>) => {
+    event.preventDefault();
+    const { tokenName } = this.state;
+    if (tokenName) {
+      this.setState({ loading: true });
+      generateToken({ name: tokenName }).then(({ token }) => {
+        if (this.mounted) {
+          this.setState({ loading: false, token });
+        }
+      }, this.stopLoading);
+    }
+  };
+
+  handleTokenRevoke = () => {
+    const { tokenName } = this.state;
+    if (tokenName) {
+      this.setState({ loading: true });
+      revokeToken({ name: tokenName }).then(() => {
+        if (this.mounted) {
+          this.setState({ loading: false, token: undefined, tokenName: undefined });
+        }
+      }, this.stopLoading);
+    }
+  };
+
+  handleContinueClick = () => {
+    const token = this.getToken();
+    if (token) {
+      this.props.onContinue(token);
+    }
+  };
+
+  handleGenerateClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+    event.preventDefault();
+    this.setState({ selection: 'generate' });
+  };
+
+  handleUseExistingClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+    event.preventDefault();
+    this.setState({ selection: 'use-existing' });
+  };
+
+  handleExisingTokenChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    this.setState({ existingToken: event.currentTarget.value });
+  };
+
+  stopLoading = () => {
+    if (this.mounted) {
+      this.setState({ loading: false });
+    }
+  };
+
+  renderGenerateOption = () => (
+    <div>
+      {this.state.canUseExisting ? (
+        <a
+          className="js-new link-base-color link-no-underline"
+          href="#"
+          onClick={this.handleGenerateClick}>
+          <i
+            className={classNames('icon-radio', 'spacer-right', {
+              'is-checked': this.state.selection === 'generate'
+            })}
+          />
+          {translate('onboarding.token.generate_token')}
+        </a>
+      ) : (
+        translate('onboarding.token.generate_token')
+      )}
+      {this.state.selection === 'generate' && (
+        <div className="big-spacer-top">
+          <form onSubmit={this.handleTokenGenerate}>
+            <input
+              autoFocus={true}
+              className="input-large spacer-right text-middle"
+              onChange={this.handleTokenNameChange}
+              placeholder={translate('onboarding.token.generate_token.placeholder')}
+              required={true}
+              type="text"
+              value={this.state.tokenName || ''}
+            />
+            {this.state.loading ? (
+              <i className="spinner text-middle" />
+            ) : (
+              <SubmitButton className="text-middle" disabled={!this.state.tokenName}>
+                {translate('onboarding.token.generate')}
+              </SubmitButton>
+            )}
+          </form>
+        </div>
+      )}
+    </div>
+  );
+
+  renderUseExistingOption = () => {
+    const { existingToken } = this.state;
+    const validInput = !existingToken || existingToken.match(/^[a-z0-9]+$/) != null;
+
+    return (
+      <div className="big-spacer-top">
+        <a
+          className="js-new link-base-color link-no-underline"
+          href="#"
+          onClick={this.handleUseExistingClick}>
+          <i
+            className={classNames('icon-radio', 'spacer-right', {
+              'is-checked': this.state.selection === 'use-existing'
+            })}
+          />
+          {translate('onboarding.token.use_existing_token')}
+        </a>
+        {this.state.selection === 'use-existing' && (
+          <div className="big-spacer-top">
+            <input
+              autoFocus={true}
+              className="input-large spacer-right text-middle"
+              onChange={this.handleExisingTokenChange}
+              placeholder={translate('onboarding.token.use_existing_token.placeholder')}
+              required={true}
+              type="text"
+              value={this.state.existingToken}
+            />
+            {!validInput && (
+              <span className="text-danger">
+                <AlertErrorIcon className="little-spacer-right text-text-top" />
+                {translate('onboarding.token.invalid_format')}
+              </span>
+            )}
+          </div>
+        )}
+      </div>
+    );
+  };
+
+  renderForm = () => {
+    const { canUseExisting, loading, token, tokenName } = this.state;
+
+    return (
+      <div className="boxed-group-inner">
+        {token != null ? (
+          <form onSubmit={this.handleTokenRevoke}>
+            <span className="text-middle">
+              {tokenName}
+              {': '}
+            </span>
+            <strong className="spacer-right text-middle">{token}</strong>
+            {loading ? (
+              <i className="spinner text-middle" />
+            ) : (
+              <DeleteButton className="button-small text-middle" onClick={this.handleTokenRevoke} />
+            )}
+          </form>
+        ) : (
+          <div>
+            {this.renderGenerateOption()}
+            {canUseExisting && this.renderUseExistingOption()}
+          </div>
+        )}
+
+        <div className="note big-spacer-top width-50">{translate('onboarding.token.text')}</div>
+
+        {this.canContinue() && (
+          <div className="big-spacer-top">
+            <Button className="js-continue" onClick={this.handleContinueClick}>
+              {translate('continue')}
+            </Button>
+          </div>
+        )}
+      </div>
+    );
+  };
+
+  renderResult = () => {
+    const { selection, tokenName } = this.state;
+    const token = this.getToken();
+
+    if (!token) {
+      return null;
+    }
+
+    return (
+      <div className="boxed-group-actions display-flex-center">
+        <AlertSuccessIcon className="spacer-right" />
+        {selection === 'generate' && tokenName && `${tokenName}: `}
+        <strong>{token}</strong>
+      </div>
+    );
+  };
+
+  render() {
+    return (
+      <Step
+        finished={this.props.finished}
+        onOpen={this.props.onOpen}
+        open={this.props.open}
+        renderForm={this.renderForm}
+        renderResult={this.renderResult}
+        stepNumber={this.props.stepNumber}
+        stepTitle={translate('onboarding.token.header')}
+      />
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/LanguageStep-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/LanguageStep-test.tsx
new file mode 100644 (file)
index 0000000..629044f
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import LanguageStep from '../LanguageStep';
+import { isSonarCloud } from '../../../../helpers/system';
+
+jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
+
+beforeEach(() => {
+  (isSonarCloud as jest.Mock<any>).mockImplementation(() => false);
+});
+
+it('selects java', () => {
+  const onDone = jest.fn();
+  const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />);
+
+  (wrapper.find('RadioToggle').prop('onCheck') as Function)('java');
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot();
+
+  (wrapper
+    .find('RadioToggle')
+    .at(1)
+    .prop('onCheck') as Function)('maven');
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot();
+  expect(onDone).lastCalledWith({ language: 'java', javaBuild: 'maven' });
+
+  (wrapper
+    .find('RadioToggle')
+    .at(1)
+    .prop('onCheck') as Function)('gradle');
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot();
+  expect(onDone).lastCalledWith({ language: 'java', javaBuild: 'gradle' });
+});
+
+it('selects c#', () => {
+  const onDone = jest.fn();
+  const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />);
+
+  (wrapper.find('RadioToggle').prop('onCheck') as Function)('dotnet');
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot();
+
+  (wrapper.find('NewProjectForm').prop('onDone') as Function)('project-foo');
+  expect(onDone).lastCalledWith({ language: 'dotnet', projectKey: 'project-foo' });
+});
+
+it('selects c-family', () => {
+  (isSonarCloud as jest.Mock<any>).mockImplementation(() => true);
+  const onDone = jest.fn();
+  const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />);
+
+  (wrapper.find('RadioToggle').prop('onCheck') as Function)('c-family');
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot();
+
+  (wrapper
+    .find('RadioToggle')
+    .at(1)
+    .prop('onCheck') as Function)('msvc');
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot();
+
+  (wrapper.find('NewProjectForm').prop('onDone') as Function)('project-foo');
+  expect(onDone).lastCalledWith({
+    language: 'c-family',
+    cFamilyCompiler: 'msvc',
+    projectKey: 'project-foo'
+  });
+
+  (wrapper
+    .find('RadioToggle')
+    .at(1)
+    .prop('onCheck') as Function)('clang-gcc');
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot();
+
+  (wrapper
+    .find('RadioToggle')
+    .at(2)
+    .prop('onCheck') as Function)('linux');
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot();
+
+  (wrapper.find('NewProjectForm').prop('onDone') as Function)('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(<LanguageStep onDone={onDone} onReset={jest.fn()} />);
+
+  (wrapper.find('RadioToggle').prop('onCheck') as Function)('other');
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot();
+
+  (wrapper
+    .find('RadioToggle')
+    .at(1)
+    .prop('onCheck') as Function)('mac');
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot();
+
+  (wrapper.find('NewProjectForm').prop('onDone') as Function)('project-foo');
+  expect(onDone).lastCalledWith({ language: 'other', os: 'mac', projectKey: 'project-foo' });
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewOrganizationForm-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewOrganizationForm-test.tsx
new file mode 100644 (file)
index 0000000..bb16f90
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { mount } from 'enzyme';
+import NewOrganizationForm from '../NewOrganizationForm';
+import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils';
+
+jest.mock('../../../../api/organizations', () => ({
+  createOrganization: () => Promise.resolve(),
+  deleteOrganization: () => Promise.resolve(),
+  getOrganization: () => Promise.resolve(null)
+}));
+
+jest.mock('../../../../components/icons-components/ClearIcon');
+
+it('creates new organization', async () => {
+  const onDone = jest.fn();
+  const wrapper = mount(<NewOrganizationForm onDelete={jest.fn()} onDone={onDone} />);
+  expect(wrapper).toMatchSnapshot();
+  change(wrapper.find('input'), 'foo');
+  submit(wrapper.find('form'));
+  expect(wrapper).toMatchSnapshot(); // spinner
+  await waitAndUpdate(wrapper);
+  expect(wrapper).toMatchSnapshot();
+  expect(onDone).toBeCalledWith('foo');
+});
+
+it('deletes organization', async () => {
+  const onDelete = jest.fn();
+  const wrapper = mount(<NewOrganizationForm onDelete={onDelete} onDone={jest.fn()} />);
+  wrapper.setState({ done: true, loading: false, organization: 'foo' });
+  expect(wrapper).toMatchSnapshot();
+  (wrapper.find('DeleteButton').prop('onClick') as Function)();
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot(); // spinner
+  await waitAndUpdate(wrapper);
+  expect(wrapper).toMatchSnapshot();
+  expect(onDelete).toBeCalled();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewProjectForm-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewProjectForm-test.tsx
new file mode 100644 (file)
index 0000000..2c8c18c
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { mount } from 'enzyme';
+import NewProjectForm from '../NewProjectForm';
+import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils';
+
+jest.mock('../../../../api/components', () => ({
+  createProject: () => Promise.resolve(),
+  deleteProject: () => Promise.resolve()
+}));
+
+jest.mock('../../../../components/icons-components/ClearIcon');
+
+it('creates new project', async () => {
+  const onDone = jest.fn();
+  const wrapper = mount(<NewProjectForm onDelete={jest.fn()} onDone={onDone} />);
+  expect(wrapper).toMatchSnapshot();
+  change(wrapper.find('input'), 'foo');
+  submit(wrapper.find('form'));
+  expect(wrapper).toMatchSnapshot(); // spinner
+  await waitAndUpdate(wrapper);
+  expect(wrapper).toMatchSnapshot();
+  expect(onDone).toBeCalledWith('foo');
+});
+
+it('deletes project', async () => {
+  const onDelete = jest.fn();
+  const wrapper = mount(<NewProjectForm onDelete={onDelete} onDone={jest.fn()} />);
+  wrapper.setState({ done: true, loading: false, projectKey: 'foo' });
+  expect(wrapper).toMatchSnapshot();
+  (wrapper.find('DeleteButton').prop('onClick') as Function)();
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot(); // spinner
+  await waitAndUpdate(wrapper);
+  expect(wrapper).toMatchSnapshot();
+  expect(onDelete).toBeCalled();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/OrganizationStep-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/OrganizationStep-test.tsx
new file mode 100644 (file)
index 0000000..b2192a0
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { mount } from 'enzyme';
+import OrganizationStep from '../OrganizationStep';
+import { click, waitAndUpdate } from '../../../../helpers/testUtils';
+import { getOrganizations } from '../../../../api/organizations';
+
+jest.mock('../../../../api/organizations', () => ({
+  getOrganizations: jest.fn(() =>
+    Promise.resolve({
+      organizations: [{ isAdmin: true, key: 'user' }, { isAdmin: true, key: 'another' }]
+    })
+  )
+}));
+
+const currentUser = { isLoggedIn: true, login: 'user' };
+
+beforeEach(() => {
+  (getOrganizations as jest.Mock<any>).mockClear();
+});
+
+// FIXME
+// - if `mount` is used, then it's not possible to correctly set the state,
+//   because the mocked api call is used
+// - if `shallow` is used, then the continue button is not rendered
+it.skip('works with personal organization', () => {
+  const onContinue = jest.fn();
+  const wrapper = mount(
+    <OrganizationStep
+      currentUser={currentUser}
+      finished={false}
+      onContinue={onContinue}
+      onOpen={jest.fn()}
+      open={true}
+      stepNumber={1}
+    />
+  );
+  click(wrapper.find('.js-continue'));
+  expect(onContinue).toBeCalledWith('user');
+});
+
+it('works with existing organization', async () => {
+  const onContinue = jest.fn();
+  const wrapper = mount(
+    <OrganizationStep
+      currentUser={currentUser}
+      finished={false}
+      onContinue={onContinue}
+      onOpen={jest.fn()}
+      open={true}
+      stepNumber={1}
+    />
+  );
+  await waitAndUpdate(wrapper);
+  click(wrapper.find('.js-existing'));
+  expect(wrapper).toMatchSnapshot();
+  (wrapper
+    .find('Select')
+    .first()
+    .prop('onChange') as Function)({ value: 'another' });
+  wrapper.update();
+  click(wrapper.find('[className="js-continue"]'));
+  expect(onContinue).toBeCalledWith('another');
+});
+
+it('works with new organization', async () => {
+  const onContinue = jest.fn();
+  const wrapper = mount(
+    <OrganizationStep
+      currentUser={currentUser}
+      finished={false}
+      onContinue={onContinue}
+      onOpen={jest.fn()}
+      open={true}
+      stepNumber={1}
+    />
+  );
+  await waitAndUpdate(wrapper);
+  click(wrapper.find('.js-new'));
+  (wrapper.find('NewOrganizationForm').prop('onDone') as Function)('new');
+  wrapper.update();
+  click(wrapper.find('[className="js-continue"]'));
+  expect(onContinue).toBeCalledWith('new');
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectOnboarding-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectOnboarding-test.tsx
new file mode 100644 (file)
index 0000000..078d670
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow, mount } from 'enzyme';
+import { ProjectOnboarding } from '../ProjectOnboarding';
+import { click, doAsync } from '../../../../helpers/testUtils';
+import { getInstance, isSonarCloud } from '../../../../helpers/system';
+
+jest.mock('../../../../api/users', () => ({
+  skipOnboarding: () => Promise.resolve()
+}));
+
+jest.mock('../../../../helpers/system', () => ({
+  getInstance: jest.fn(),
+  isSonarCloud: jest.fn()
+}));
+
+const currentUser = { login: 'admin', isLoggedIn: true };
+
+it('guides for on-premise', () => {
+  (getInstance as jest.Mock<any>).mockImplementation(() => 'SonarQube');
+  (isSonarCloud as jest.Mock<any>).mockImplementation(() => false);
+  const wrapper = shallow(
+    <ProjectOnboarding
+      currentUser={currentUser}
+      onFinish={jest.fn()}
+      organizationsEnabled={false}
+    />
+  );
+  expect(wrapper).toMatchSnapshot();
+
+  (wrapper.instance() as ProjectOnboarding).handleTokenDone('abcd1234');
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('guides for sonarcloud', () => {
+  (getInstance as jest.Mock<any>).mockImplementation(() => 'SonarCloud');
+  (isSonarCloud as jest.Mock<any>).mockImplementation(() => true);
+  const wrapper = shallow(
+    <ProjectOnboarding currentUser={currentUser} onFinish={jest.fn()} organizationsEnabled={true} />
+  );
+  expect(wrapper).toMatchSnapshot();
+
+  (wrapper.instance() as ProjectOnboarding).handleOrganizationDone('my-org');
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot();
+
+  (wrapper.instance() as ProjectOnboarding).handleTokenDone('abcd1234');
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('finishes', () => {
+  (getInstance as jest.Mock<any>).mockImplementation(() => 'SonarQube');
+  (isSonarCloud as jest.Mock<any>).mockImplementation(() => false);
+  const onFinish = jest.fn();
+  const wrapper = mount(
+    <ProjectOnboarding currentUser={currentUser} onFinish={onFinish} organizationsEnabled={false} />
+  );
+  click(wrapper.find('ResetButtonLink'));
+  return doAsync(() => {
+    expect(onFinish).toBeCalled();
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectWatcher-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectWatcher-test.tsx
new file mode 100644 (file)
index 0000000..1621525
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow, mount } from 'enzyme';
+import ProjectWatcher from '../ProjectWatcher';
+
+jest.mock('../../../../api/ce', () => ({
+  getTasksForComponent: () => Promise.resolve({ current: { status: 'SUCCESS' }, queue: [] })
+}));
+
+jest.useFakeTimers();
+
+it('renders', () => {
+  const wrapper = shallow(
+    <ProjectWatcher onFinish={jest.fn()} onTimeout={jest.fn()} projectKey="foo" />
+  );
+  expect(wrapper).toMatchSnapshot();
+  wrapper.setState({ inQueue: true });
+  expect(wrapper).toMatchSnapshot();
+  wrapper.setState({ status: 'SUCCESS' });
+  expect(wrapper).toMatchSnapshot();
+  wrapper.setState({ status: 'FAILED' });
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('finishes', done => {
+  // checking `expect(onFinish).toBeCalled();` is not working, because it's called asynchronously
+  // instead let's finish the test as soon as `onFinish` callback is called
+  const onFinish = jest.fn(done);
+  mount(<ProjectWatcher onFinish={onFinish} onTimeout={jest.fn()} projectKey="foo" />);
+  expect(onFinish).not.toBeCalled();
+  jest.runTimersToTime(5000);
+});
+
+it('timeouts', () => {
+  const onTimeout = jest.fn();
+  mount(<ProjectWatcher onFinish={jest.fn()} onTimeout={onTimeout} projectKey="foo" />);
+  expect(onTimeout).not.toBeCalled();
+  jest.runTimersToTime(10 * 60 * 1000);
+  expect(onTimeout).toBeCalled();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/Step-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/Step-test.tsx
new file mode 100644 (file)
index 0000000..8a9664f
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Step from '../Step';
+import { click } from '../../../../helpers/testUtils';
+
+it('renders', () => {
+  const wrapper = shallow(
+    <Step
+      finished={true}
+      onOpen={jest.fn()}
+      open={true}
+      renderForm={() => <div>form</div>}
+      renderResult={() => <div>result</div>}
+      stepNumber={1}
+      stepTitle="First Step"
+    />
+  );
+  expect(wrapper).toMatchSnapshot();
+  wrapper.setProps({ open: false });
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('re-opens', () => {
+  const onOpen = jest.fn();
+  const wrapper = shallow(
+    <Step
+      finished={true}
+      onOpen={onOpen}
+      open={false}
+      renderForm={() => <div>form</div>}
+      renderResult={() => <div>result</div>}
+      stepNumber={1}
+      stepTitle="First Step"
+    />
+  );
+  click(wrapper);
+  expect(onOpen).toBeCalled();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/TokenStep-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/TokenStep-test.tsx
new file mode 100644 (file)
index 0000000..25b5f0c
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { mount } from 'enzyme';
+import TokenStep from '../TokenStep';
+import { change, click, submit, waitAndUpdate } from '../../../../helpers/testUtils';
+
+jest.mock('../../../../api/user-tokens', () => ({
+  getTokens: () => Promise.resolve([{ name: 'foo' }]),
+  generateToken: () => Promise.resolve({ token: 'abcd1234' }),
+  revokeToken: () => Promise.resolve()
+}));
+
+jest.mock('../../../../components/icons-components/ClearIcon');
+
+const currentUser = { login: 'user' };
+
+it('generates token', async () => {
+  const wrapper = mount(
+    <TokenStep
+      currentUser={currentUser}
+      finished={false}
+      onContinue={jest.fn()}
+      onOpen={jest.fn()}
+      open={true}
+      stepNumber={1}
+    />
+  );
+  await waitAndUpdate(wrapper);
+  expect(wrapper).toMatchSnapshot();
+  change(wrapper.find('input'), 'my token');
+  submit(wrapper.find('form'));
+  expect(wrapper).toMatchSnapshot(); // spinner
+  await waitAndUpdate(wrapper);
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('revokes token', async () => {
+  const wrapper = mount(
+    <TokenStep
+      currentUser={currentUser}
+      finished={false}
+      onContinue={jest.fn()}
+      onOpen={jest.fn()}
+      open={true}
+      stepNumber={1}
+    />
+  );
+  await new Promise(setImmediate);
+  wrapper.setState({ token: 'abcd1234', tokenName: 'my token' });
+  expect(wrapper).toMatchSnapshot();
+  (wrapper.find('DeleteButton').prop('onClick') as Function)();
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot(); // spinner
+  await waitAndUpdate(wrapper);
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('continues', async () => {
+  const onContinue = jest.fn();
+  const wrapper = mount(
+    <TokenStep
+      currentUser={currentUser}
+      finished={false}
+      onContinue={onContinue}
+      onOpen={jest.fn()}
+      open={true}
+      stepNumber={1}
+    />
+  );
+  await new Promise(setImmediate);
+  wrapper.setState({ token: 'abcd1234', tokenName: 'my token' });
+  click(wrapper.find('[className="js-continue"]'));
+  expect(onContinue).toBeCalledWith('abcd1234');
+});
+
+it('uses existing token', async () => {
+  const onContinue = jest.fn();
+  const wrapper = mount(
+    <TokenStep
+      currentUser={currentUser}
+      finished={false}
+      onContinue={onContinue}
+      onOpen={jest.fn()}
+      open={true}
+      stepNumber={1}
+    />
+  );
+  await new Promise(setImmediate);
+  wrapper.setState({ existingToken: 'abcd1234', selection: 'use-existing' });
+  click(wrapper.find('[className="js-continue"]'));
+  expect(onContinue).toBeCalledWith('abcd1234');
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/LanguageStep-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/LanguageStep-test.tsx.snap
new file mode 100644 (file)
index 0000000..8e82d38
--- /dev/null
@@ -0,0 +1,687 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`selects c# 1`] = `
+<div>
+  <div>
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="language"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.java",
+            "value": "java",
+          },
+          Object {
+            "label": "onboarding.language.dotnet",
+            "value": "dotnet",
+          },
+          Object {
+            "label": "onboarding.language.other",
+            "value": "other",
+          },
+        ]
+      }
+      value="dotnet"
+    />
+  </div>
+  <NewProjectForm
+    onDelete={[Function]}
+    onDone={[Function]}
+  />
+</div>
+`;
+
+exports[`selects c-family 1`] = `
+<div>
+  <div>
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="language"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.java",
+            "value": "java",
+          },
+          Object {
+            "label": "onboarding.language.dotnet",
+            "value": "dotnet",
+          },
+          Object {
+            "label": "onboarding.language.c-family",
+            "value": "c-family",
+          },
+          Object {
+            "label": "onboarding.language.other",
+            "value": "other",
+          },
+        ]
+      }
+      value="c-family"
+    />
+  </div>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.c-family.compiler
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="c-family-compiler"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.c-family.compiler.msvc",
+            "value": "msvc",
+          },
+          Object {
+            "label": "onboarding.language.c-family.compiler.clang-gcc",
+            "value": "clang-gcc",
+          },
+        ]
+      }
+      value={null}
+    />
+  </div>
+</div>
+`;
+
+exports[`selects c-family 2`] = `
+<div>
+  <div>
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="language"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.java",
+            "value": "java",
+          },
+          Object {
+            "label": "onboarding.language.dotnet",
+            "value": "dotnet",
+          },
+          Object {
+            "label": "onboarding.language.c-family",
+            "value": "c-family",
+          },
+          Object {
+            "label": "onboarding.language.other",
+            "value": "other",
+          },
+        ]
+      }
+      value="c-family"
+    />
+  </div>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.c-family.compiler
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="c-family-compiler"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.c-family.compiler.msvc",
+            "value": "msvc",
+          },
+          Object {
+            "label": "onboarding.language.c-family.compiler.clang-gcc",
+            "value": "clang-gcc",
+          },
+        ]
+      }
+      value="msvc"
+    />
+  </div>
+  <NewProjectForm
+    onDelete={[Function]}
+    onDone={[Function]}
+  />
+</div>
+`;
+
+exports[`selects c-family 3`] = `
+<div>
+  <div>
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="language"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.java",
+            "value": "java",
+          },
+          Object {
+            "label": "onboarding.language.dotnet",
+            "value": "dotnet",
+          },
+          Object {
+            "label": "onboarding.language.c-family",
+            "value": "c-family",
+          },
+          Object {
+            "label": "onboarding.language.other",
+            "value": "other",
+          },
+        ]
+      }
+      value="c-family"
+    />
+  </div>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.c-family.compiler
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="c-family-compiler"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.c-family.compiler.msvc",
+            "value": "msvc",
+          },
+          Object {
+            "label": "onboarding.language.c-family.compiler.clang-gcc",
+            "value": "clang-gcc",
+          },
+        ]
+      }
+      value="clang-gcc"
+    />
+  </div>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.os
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="os"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.os.linux",
+            "value": "linux",
+          },
+          Object {
+            "label": "onboarding.language.os.win",
+            "value": "win",
+          },
+          Object {
+            "label": "onboarding.language.os.mac",
+            "value": "mac",
+          },
+        ]
+      }
+      value={null}
+    />
+  </div>
+</div>
+`;
+
+exports[`selects c-family 4`] = `
+<div>
+  <div>
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="language"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.java",
+            "value": "java",
+          },
+          Object {
+            "label": "onboarding.language.dotnet",
+            "value": "dotnet",
+          },
+          Object {
+            "label": "onboarding.language.c-family",
+            "value": "c-family",
+          },
+          Object {
+            "label": "onboarding.language.other",
+            "value": "other",
+          },
+        ]
+      }
+      value="c-family"
+    />
+  </div>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.c-family.compiler
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="c-family-compiler"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.c-family.compiler.msvc",
+            "value": "msvc",
+          },
+          Object {
+            "label": "onboarding.language.c-family.compiler.clang-gcc",
+            "value": "clang-gcc",
+          },
+        ]
+      }
+      value="clang-gcc"
+    />
+  </div>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.os
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="os"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.os.linux",
+            "value": "linux",
+          },
+          Object {
+            "label": "onboarding.language.os.win",
+            "value": "win",
+          },
+          Object {
+            "label": "onboarding.language.os.mac",
+            "value": "mac",
+          },
+        ]
+      }
+      value="linux"
+    />
+  </div>
+  <NewProjectForm
+    onDelete={[Function]}
+    onDone={[Function]}
+    projectKey="project-foo"
+  />
+</div>
+`;
+
+exports[`selects java 1`] = `
+<div>
+  <div>
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="language"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.java",
+            "value": "java",
+          },
+          Object {
+            "label": "onboarding.language.dotnet",
+            "value": "dotnet",
+          },
+          Object {
+            "label": "onboarding.language.other",
+            "value": "other",
+          },
+        ]
+      }
+      value="java"
+    />
+  </div>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.java.build_technology
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="java-build"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.java.build_technology.maven",
+            "value": "maven",
+          },
+          Object {
+            "label": "onboarding.language.java.build_technology.gradle",
+            "value": "gradle",
+          },
+        ]
+      }
+      value={null}
+    />
+  </div>
+</div>
+`;
+
+exports[`selects java 2`] = `
+<div>
+  <div>
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="language"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.java",
+            "value": "java",
+          },
+          Object {
+            "label": "onboarding.language.dotnet",
+            "value": "dotnet",
+          },
+          Object {
+            "label": "onboarding.language.other",
+            "value": "other",
+          },
+        ]
+      }
+      value="java"
+    />
+  </div>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.java.build_technology
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="java-build"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.java.build_technology.maven",
+            "value": "maven",
+          },
+          Object {
+            "label": "onboarding.language.java.build_technology.gradle",
+            "value": "gradle",
+          },
+        ]
+      }
+      value="maven"
+    />
+  </div>
+</div>
+`;
+
+exports[`selects java 3`] = `
+<div>
+  <div>
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="language"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.java",
+            "value": "java",
+          },
+          Object {
+            "label": "onboarding.language.dotnet",
+            "value": "dotnet",
+          },
+          Object {
+            "label": "onboarding.language.other",
+            "value": "other",
+          },
+        ]
+      }
+      value="java"
+    />
+  </div>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.java.build_technology
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="java-build"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.java.build_technology.maven",
+            "value": "maven",
+          },
+          Object {
+            "label": "onboarding.language.java.build_technology.gradle",
+            "value": "gradle",
+          },
+        ]
+      }
+      value="gradle"
+    />
+  </div>
+</div>
+`;
+
+exports[`selects other 1`] = `
+<div>
+  <div>
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="language"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.java",
+            "value": "java",
+          },
+          Object {
+            "label": "onboarding.language.dotnet",
+            "value": "dotnet",
+          },
+          Object {
+            "label": "onboarding.language.other",
+            "value": "other",
+          },
+        ]
+      }
+      value="other"
+    />
+  </div>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.os
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="os"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.os.linux",
+            "value": "linux",
+          },
+          Object {
+            "label": "onboarding.language.os.win",
+            "value": "win",
+          },
+          Object {
+            "label": "onboarding.language.os.mac",
+            "value": "mac",
+          },
+        ]
+      }
+      value={null}
+    />
+  </div>
+</div>
+`;
+
+exports[`selects other 2`] = `
+<div>
+  <div>
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="language"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.java",
+            "value": "java",
+          },
+          Object {
+            "label": "onboarding.language.dotnet",
+            "value": "dotnet",
+          },
+          Object {
+            "label": "onboarding.language.other",
+            "value": "other",
+          },
+        ]
+      }
+      value="other"
+    />
+  </div>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.os
+    </h4>
+    <RadioToggle
+      disabled={false}
+      name="os"
+      onCheck={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "onboarding.language.os.linux",
+            "value": "linux",
+          },
+          Object {
+            "label": "onboarding.language.os.win",
+            "value": "win",
+          },
+          Object {
+            "label": "onboarding.language.os.mac",
+            "value": "mac",
+          },
+        ]
+      }
+      value="mac"
+    />
+  </div>
+  <NewProjectForm
+    onDelete={[Function]}
+    onDone={[Function]}
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap
new file mode 100644 (file)
index 0000000..eae8fcd
--- /dev/null
@@ -0,0 +1,270 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`creates new organization 1`] = `
+<NewOrganizationForm
+  onDelete={[MockFunction]}
+  onDone={[MockFunction]}
+>
+  <form
+    onSubmit={[Function]}
+  >
+    <input
+      autoFocus={true}
+      className="input-super-large spacer-right text-middle"
+      maxLength={32}
+      minLength={2}
+      onChange={[Function]}
+      placeholder="onboarding.organization.placeholder"
+      required={true}
+      type="text"
+      value=""
+    />
+    <SubmitButton
+      className="text-middle"
+      disabled={true}
+    >
+      <Button
+        className="text-middle"
+        disabled={true}
+        preventDefault={false}
+        type="submit"
+      >
+        <button
+          className="button text-middle"
+          disabled={true}
+          onClick={[Function]}
+          type="submit"
+        >
+          create
+        </button>
+      </Button>
+    </SubmitButton>
+    <div
+      className="note spacer-top abs-width-300"
+    >
+      onboarding.organization.key_requirement
+    </div>
+  </form>
+</NewOrganizationForm>
+`;
+
+exports[`creates new organization 2`] = `
+<NewOrganizationForm
+  onDelete={[MockFunction]}
+  onDone={[MockFunction]}
+>
+  <form
+    onSubmit={[Function]}
+  >
+    <input
+      autoFocus={true}
+      className="input-super-large spacer-right text-middle"
+      maxLength={32}
+      minLength={2}
+      onChange={[Function]}
+      placeholder="onboarding.organization.placeholder"
+      required={true}
+      type="text"
+      value="foo"
+    />
+    <i
+      className="spinner text-middle"
+    />
+    <div
+      className="note spacer-top abs-width-300"
+    >
+      onboarding.organization.key_requirement
+    </div>
+  </form>
+</NewOrganizationForm>
+`;
+
+exports[`creates new organization 3`] = `
+<NewOrganizationForm
+  onDelete={[MockFunction]}
+  onDone={
+    [MockFunction] {
+      "calls": Array [
+        Array [
+          "foo",
+        ],
+      ],
+      "results": Array [
+        Object {
+          "isThrow": false,
+          "value": undefined,
+        },
+      ],
+    }
+  }
+>
+  <div>
+    <span
+      className="spacer-right text-middle"
+    >
+      foo
+    </span>
+    <DeleteButton
+      className="button-small"
+      onClick={[Function]}
+    >
+      <ButtonIcon
+        className="button-small"
+        color="#d4333f"
+        onClick={[Function]}
+      >
+        <Button
+          className="button-small button-icon"
+          onClick={[Function]}
+          stopPropagation={true}
+          style={
+            Object {
+              "color": "#d4333f",
+            }
+          }
+        >
+          <button
+            className="button button-small button-icon"
+            onClick={[Function]}
+            style={
+              Object {
+                "color": "#d4333f",
+              }
+            }
+            type="button"
+          >
+            <ClearIcon />
+          </button>
+        </Button>
+      </ButtonIcon>
+    </DeleteButton>
+  </div>
+</NewOrganizationForm>
+`;
+
+exports[`deletes organization 1`] = `
+<NewOrganizationForm
+  onDelete={[MockFunction]}
+  onDone={[MockFunction]}
+>
+  <div>
+    <span
+      className="spacer-right text-middle"
+    >
+      foo
+    </span>
+    <DeleteButton
+      className="button-small"
+      onClick={[Function]}
+    >
+      <ButtonIcon
+        className="button-small"
+        color="#d4333f"
+        onClick={[Function]}
+      >
+        <Button
+          className="button-small button-icon"
+          onClick={[Function]}
+          stopPropagation={true}
+          style={
+            Object {
+              "color": "#d4333f",
+            }
+          }
+        >
+          <button
+            className="button button-small button-icon"
+            onClick={[Function]}
+            style={
+              Object {
+                "color": "#d4333f",
+              }
+            }
+            type="button"
+          >
+            <ClearIcon />
+          </button>
+        </Button>
+      </ButtonIcon>
+    </DeleteButton>
+  </div>
+</NewOrganizationForm>
+`;
+
+exports[`deletes organization 2`] = `
+<NewOrganizationForm
+  onDelete={[MockFunction]}
+  onDone={[MockFunction]}
+>
+  <div>
+    <span
+      className="spacer-right text-middle"
+    >
+      foo
+    </span>
+    <i
+      className="spinner text-middle"
+    />
+  </div>
+</NewOrganizationForm>
+`;
+
+exports[`deletes organization 3`] = `
+<NewOrganizationForm
+  onDelete={
+    [MockFunction] {
+      "calls": Array [
+        Array [],
+      ],
+      "results": Array [
+        Object {
+          "isThrow": false,
+          "value": undefined,
+        },
+      ],
+    }
+  }
+  onDone={[MockFunction]}
+>
+  <form
+    onSubmit={[Function]}
+  >
+    <input
+      autoFocus={true}
+      className="input-super-large spacer-right text-middle"
+      maxLength={32}
+      minLength={2}
+      onChange={[Function]}
+      placeholder="onboarding.organization.placeholder"
+      required={true}
+      type="text"
+      value=""
+    />
+    <SubmitButton
+      className="text-middle"
+      disabled={true}
+    >
+      <Button
+        className="text-middle"
+        disabled={true}
+        preventDefault={false}
+        type="submit"
+      >
+        <button
+          className="button text-middle"
+          disabled={true}
+          onClick={[Function]}
+          type="submit"
+        >
+          create
+        </button>
+      </Button>
+    </SubmitButton>
+    <div
+      className="note spacer-top abs-width-300"
+    >
+      onboarding.organization.key_requirement
+    </div>
+  </form>
+</NewOrganizationForm>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewProjectForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewProjectForm-test.tsx.snap
new file mode 100644 (file)
index 0000000..f6722a2
--- /dev/null
@@ -0,0 +1,321 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`creates new project 1`] = `
+<NewProjectForm
+  onDelete={[MockFunction]}
+  onDone={[MockFunction]}
+>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.project_key
+    </h4>
+    <form
+      onSubmit={[Function]}
+    >
+      <input
+        autoFocus={true}
+        className="input-large spacer-right text-middle"
+        maxLength={400}
+        minLength={1}
+        onChange={[Function]}
+        required={true}
+        type="text"
+        value=""
+      />
+      <SubmitButton
+        className="text-middle"
+        disabled={true}
+      >
+        <Button
+          className="text-middle"
+          disabled={true}
+          preventDefault={false}
+          type="submit"
+        >
+          <button
+            className="button text-middle"
+            disabled={true}
+            onClick={[Function]}
+            type="submit"
+          >
+            Done
+          </button>
+        </Button>
+      </SubmitButton>
+      <div
+        className="note spacer-top abs-width-300"
+      >
+        onboarding.project_key_requirement
+      </div>
+    </form>
+  </div>
+</NewProjectForm>
+`;
+
+exports[`creates new project 2`] = `
+<NewProjectForm
+  onDelete={[MockFunction]}
+  onDone={[MockFunction]}
+>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.project_key
+    </h4>
+    <form
+      onSubmit={[Function]}
+    >
+      <input
+        autoFocus={true}
+        className="input-large spacer-right text-middle"
+        maxLength={400}
+        minLength={1}
+        onChange={[Function]}
+        required={true}
+        type="text"
+        value="foo"
+      />
+      <i
+        className="spinner text-middle"
+      />
+      <div
+        className="note spacer-top abs-width-300"
+      >
+        onboarding.project_key_requirement
+      </div>
+    </form>
+  </div>
+</NewProjectForm>
+`;
+
+exports[`creates new project 3`] = `
+<NewProjectForm
+  onDelete={[MockFunction]}
+  onDone={
+    [MockFunction] {
+      "calls": Array [
+        Array [
+          "foo",
+        ],
+      ],
+      "results": Array [
+        Object {
+          "isThrow": false,
+          "value": undefined,
+        },
+      ],
+    }
+  }
+>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.project_key
+    </h4>
+    <div>
+      <span
+        className="spacer-right text-middle"
+      >
+        foo
+      </span>
+      <DeleteButton
+        className="button-small text-middle"
+        onClick={[Function]}
+      >
+        <ButtonIcon
+          className="button-small text-middle"
+          color="#d4333f"
+          onClick={[Function]}
+        >
+          <Button
+            className="button-small text-middle button-icon"
+            onClick={[Function]}
+            stopPropagation={true}
+            style={
+              Object {
+                "color": "#d4333f",
+              }
+            }
+          >
+            <button
+              className="button button-small text-middle button-icon"
+              onClick={[Function]}
+              style={
+                Object {
+                  "color": "#d4333f",
+                }
+              }
+              type="button"
+            >
+              <ClearIcon />
+            </button>
+          </Button>
+        </ButtonIcon>
+      </DeleteButton>
+    </div>
+  </div>
+</NewProjectForm>
+`;
+
+exports[`deletes project 1`] = `
+<NewProjectForm
+  onDelete={[MockFunction]}
+  onDone={[MockFunction]}
+>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.project_key
+    </h4>
+    <div>
+      <span
+        className="spacer-right text-middle"
+      >
+        foo
+      </span>
+      <DeleteButton
+        className="button-small text-middle"
+        onClick={[Function]}
+      >
+        <ButtonIcon
+          className="button-small text-middle"
+          color="#d4333f"
+          onClick={[Function]}
+        >
+          <Button
+            className="button-small text-middle button-icon"
+            onClick={[Function]}
+            stopPropagation={true}
+            style={
+              Object {
+                "color": "#d4333f",
+              }
+            }
+          >
+            <button
+              className="button button-small text-middle button-icon"
+              onClick={[Function]}
+              style={
+                Object {
+                  "color": "#d4333f",
+                }
+              }
+              type="button"
+            >
+              <ClearIcon />
+            </button>
+          </Button>
+        </ButtonIcon>
+      </DeleteButton>
+    </div>
+  </div>
+</NewProjectForm>
+`;
+
+exports[`deletes project 2`] = `
+<NewProjectForm
+  onDelete={[MockFunction]}
+  onDone={[MockFunction]}
+>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.project_key
+    </h4>
+    <div>
+      <span
+        className="spacer-right text-middle"
+      >
+        foo
+      </span>
+      <i
+        className="spinner text-middle"
+      />
+    </div>
+  </div>
+</NewProjectForm>
+`;
+
+exports[`deletes project 3`] = `
+<NewProjectForm
+  onDelete={
+    [MockFunction] {
+      "calls": Array [
+        Array [],
+      ],
+      "results": Array [
+        Object {
+          "isThrow": false,
+          "value": undefined,
+        },
+      ],
+    }
+  }
+  onDone={[MockFunction]}
+>
+  <div
+    className="big-spacer-top"
+  >
+    <h4
+      className="spacer-bottom"
+    >
+      onboarding.language.project_key
+    </h4>
+    <form
+      onSubmit={[Function]}
+    >
+      <input
+        autoFocus={true}
+        className="input-large spacer-right text-middle"
+        maxLength={400}
+        minLength={1}
+        onChange={[Function]}
+        required={true}
+        type="text"
+        value=""
+      />
+      <SubmitButton
+        className="text-middle"
+        disabled={true}
+      >
+        <Button
+          className="text-middle"
+          disabled={true}
+          preventDefault={false}
+          type="submit"
+        >
+          <button
+            className="button text-middle"
+            disabled={true}
+            onClick={[Function]}
+            type="submit"
+          >
+            Done
+          </button>
+        </Button>
+      </SubmitButton>
+      <div
+        className="note spacer-top abs-width-300"
+      >
+        onboarding.project_key_requirement
+      </div>
+    </form>
+  </div>
+</NewProjectForm>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/OrganizationStep-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/OrganizationStep-test.tsx.snap
new file mode 100644 (file)
index 0000000..7e30ded
--- /dev/null
@@ -0,0 +1,381 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`works with existing organization 1`] = `
+<OrganizationStep
+  currentUser={
+    Object {
+      "isLoggedIn": true,
+      "login": "user",
+    }
+  }
+  finished={false}
+  onContinue={[MockFunction]}
+  onOpen={[MockFunction]}
+  open={true}
+  stepNumber={1}
+>
+  <Step
+    finished={false}
+    onOpen={[MockFunction]}
+    open={true}
+    renderForm={[Function]}
+    renderResult={[Function]}
+    stepNumber={1}
+    stepTitle={
+      <span>
+        onboarding.organization.header
+        <DocTooltip
+          className="little-spacer-left"
+          doc="organizations/organization"
+        />
+      </span>
+    }
+  >
+    <div
+      className="boxed-group onboarding-step is-open"
+    >
+      <div
+        className="onboarding-step-number"
+      >
+        1
+      </div>
+      <div
+        className="boxed-group-header"
+      >
+        <h2>
+          <span>
+            onboarding.organization.header
+            <DocTooltip
+              className="little-spacer-left"
+              doc="organizations/organization"
+            >
+              <HelpTooltip
+                className="little-spacer-left"
+                onShow={[Function]}
+                overlay={
+                  <div
+                    className="abs-width-300"
+                  >
+                    <LazyLoader
+                      className="cut-margins"
+                      isTooltip={true}
+                    />
+                  </div>
+                }
+              >
+                <div
+                  className="help-tooltip little-spacer-left"
+                >
+                  <Tooltip
+                    mouseLeaveDelay={0.25}
+                    onShow={[Function]}
+                    overlay={
+                      <div
+                        className="abs-width-300"
+                      >
+                        <LazyLoader
+                          className="cut-margins"
+                          isTooltip={true}
+                        />
+                      </div>
+                    }
+                  >
+                    <TooltipInner
+                      mouseEnterDelay={0.1}
+                      mouseLeaveDelay={0.25}
+                      onShow={[Function]}
+                      overlay={
+                        <div
+                          className="abs-width-300"
+                        >
+                          <LazyLoader
+                            className="cut-margins"
+                            isTooltip={true}
+                          />
+                        </div>
+                      }
+                    >
+                      <span
+                        className="display-inline-flex-center"
+                        onMouseEnter={[Function]}
+                        onMouseLeave={[Function]}
+                      >
+                        <HelpIcon
+                          fill="#b4b4b4"
+                          size={12}
+                        >
+                          <Icon
+                            size={12}
+                          >
+                            <svg
+                              height={12}
+                              style={
+                                Object {
+                                  "clipRule": "evenodd",
+                                  "fillRule": "evenodd",
+                                  "strokeLinejoin": "round",
+                                  "strokeMiterlimit": "1.41421",
+                                }
+                              }
+                              version="1.1"
+                              viewBox="0 0 16 16"
+                              width={12}
+                              xmlSpace="preserve"
+                              xmlnsXlink="http://www.w3.org/1999/xlink"
+                            >
+                              <g
+                                transform="matrix(0.0364583,0,0,0.0364583,1,-0.166667)"
+                              >
+                                <path
+                                  d="M224,344L224,296C224,293.667 223.25,291.75 221.75,290.25C220.25,288.75 218.333,288 216,288L168,288C165.667,288 163.75,288.75 162.25,290.25C160.75,291.75 160,293.667 160,296L160,344C160,346.333 160.75,348.25 162.25,349.75C163.75,351.25 165.667,352 168,352L216,352C218.333,352 220.25,351.25 221.75,349.75C223.25,348.25 224,346.333 224,344ZM288,176C288,161.333 283.375,147.75 274.125,135.25C264.875,122.75 253.333,113.083 239.5,106.25C225.667,99.417 211.5,96 197,96C156.5,96 125.583,113.75 104.25,149.25C101.75,153.25 102.417,156.75 106.25,159.75L139.25,184.75C140.417,185.75 142,186.25 144,186.25C146.667,186.25 148.75,185.25 150.25,183.25C159.083,171.917 166.25,164.25 171.75,160.25C177.417,156.25 184.583,154.25 193.25,154.25C201.25,154.25 208.375,156.417 214.625,160.75C220.875,165.083 224,170 224,175.5C224,181.833 222.333,186.917 219,190.75C215.667,194.583 210,198.333 202,202C191.5,206.667 181.875,213.875 173.125,223.625C164.375,233.375 160,243.833 160,255L160,264C160,266.333 160.75,268.25 162.25,269.75C163.75,271.25 165.667,272 168,272L216,272C218.333,272 220.25,271.25 221.75,269.75C223.25,268.25 224,266.333 224,264C224,260.833 225.792,256.708 229.375,251.625C232.958,246.542 237.5,242.417 243,239.25C248.333,236.25 252.417,233.875 255.25,232.125C258.083,230.375 261.917,227.458 266.75,223.375C271.583,219.292 275.292,215.292 277.875,211.375C280.458,207.458 282.792,202.417 284.875,196.25C286.958,190.083 288,183.333 288,176ZM384,224C384,258.833 375.417,290.958 358.25,320.375C341.083,349.792 317.792,373.083 288.375,390.25C258.958,407.417 226.833,416 192,416C157.167,416 125.042,407.417 95.625,390.25C66.208,373.083 42.917,349.792 25.75,320.375C8.583,290.958 0,258.833 0,224C0,189.167 8.583,157.042 25.75,127.625C42.917,98.208 66.208,74.917 95.625,57.75C125.042,40.583 157.167,32 192,32C226.833,32 258.958,40.583 288.375,57.75C317.792,74.917 341.083,98.208 358.25,127.625C375.417,157.042 384,189.167 384,224Z"
+                                  style={
+                                    Object {
+                                      "fill": "#b4b4b4",
+                                    }
+                                  }
+                                />
+                              </g>
+                            </svg>
+                          </Icon>
+                        </HelpIcon>
+                      </span>
+                    </TooltipInner>
+                  </Tooltip>
+                </div>
+              </HelpTooltip>
+            </DocTooltip>
+          </span>
+        </h2>
+      </div>
+      <div
+        className="boxed-group-inner"
+      >
+        <div
+          className="big-spacer-bottom width-50"
+        >
+          onboarding.organization.text
+        </div>
+        <div>
+          <div
+            className="big-spacer-top"
+          >
+            <a
+              className="js-existing link-base-color link-no-underline"
+              href="#"
+              onClick={[Function]}
+            >
+              <i
+                className="icon-radio spacer-right is-checked"
+              />
+              onboarding.organization.exising_organization
+            </a>
+            <div
+              className="big-spacer-top"
+            >
+              <Select
+                className="input-super-large"
+                clearable={false}
+                onChange={[Function]}
+                options={
+                  Array [
+                    Object {
+                      "label": "another",
+                      "value": "another",
+                    },
+                    Object {
+                      "label": "user",
+                      "value": "user",
+                    },
+                  ]
+                }
+              >
+                <LazyLoader
+                  className="input-super-large"
+                  clearRenderer={[Function]}
+                  clearable={false}
+                  onChange={[Function]}
+                  options={
+                    Array [
+                      Object {
+                        "label": "another",
+                        "value": "another",
+                      },
+                      Object {
+                        "label": "user",
+                        "value": "user",
+                      },
+                    ]
+                  }
+                >
+                  <Select
+                    arrowRenderer={[Function]}
+                    autosize={true}
+                    backspaceRemoves={true}
+                    backspaceToRemoveMessage="Press backspace to remove {label}"
+                    className="input-super-large"
+                    clearAllText="Clear all"
+                    clearRenderer={[Function]}
+                    clearValueText="Clear value"
+                    clearable={false}
+                    closeOnSelect={true}
+                    deleteRemoves={true}
+                    delimiter=","
+                    disabled={false}
+                    escapeClearsValue={true}
+                    filterOptions={[Function]}
+                    ignoreAccents={true}
+                    ignoreCase={true}
+                    inputProps={Object {}}
+                    isLoading={false}
+                    joinValues={false}
+                    labelKey="label"
+                    matchPos="any"
+                    matchProp="any"
+                    menuBuffer={0}
+                    menuRenderer={[Function]}
+                    multi={false}
+                    noResultsText="No results found"
+                    onBlurResetsInput={true}
+                    onChange={[Function]}
+                    onCloseResetsInput={true}
+                    onSelectResetsInput={true}
+                    openOnClick={true}
+                    optionComponent={[Function]}
+                    options={
+                      Array [
+                        Object {
+                          "label": "another",
+                          "value": "another",
+                        },
+                        Object {
+                          "label": "user",
+                          "value": "user",
+                        },
+                      ]
+                    }
+                    pageSize={5}
+                    placeholder="Select..."
+                    removeSelected={true}
+                    required={false}
+                    rtl={false}
+                    scrollMenuIntoView={true}
+                    searchable={true}
+                    simpleValue={false}
+                    tabSelectsValue={true}
+                    trimFilter={true}
+                    valueComponent={[Function]}
+                    valueKey="value"
+                  >
+                    <div
+                      className="Select input-super-large is-searchable Select--single"
+                    >
+                      <div
+                        className="Select-control"
+                        onKeyDown={[Function]}
+                        onMouseDown={[Function]}
+                        onTouchEnd={[Function]}
+                        onTouchMove={[Function]}
+                        onTouchStart={[Function]}
+                      >
+                        <span
+                          className="Select-multi-value-wrapper"
+                          id="react-select-2--value"
+                        >
+                          <div
+                            className="Select-placeholder"
+                          >
+                            Select...
+                          </div>
+                          <AutosizeInput
+                            aria-activedescendant="react-select-2--value"
+                            aria-expanded="false"
+                            aria-haspopup="false"
+                            aria-owns=""
+                            className="Select-input"
+                            injectStyles={true}
+                            minWidth="5"
+                            onBlur={[Function]}
+                            onChange={[Function]}
+                            onFocus={[Function]}
+                            required={false}
+                            role="combobox"
+                            value=""
+                          >
+                            <div
+                              className="Select-input"
+                              style={
+                                Object {
+                                  "display": "inline-block",
+                                }
+                              }
+                            >
+                              <input
+                                aria-activedescendant="react-select-2--value"
+                                aria-expanded="false"
+                                aria-haspopup="false"
+                                aria-owns=""
+                                onBlur={[Function]}
+                                onChange={[Function]}
+                                onFocus={[Function]}
+                                required={false}
+                                role="combobox"
+                                style={
+                                  Object {
+                                    "boxSizing": "content-box",
+                                    "width": "5px",
+                                  }
+                                }
+                                value=""
+                              />
+                              <div
+                                style={
+                                  Object {
+                                    "height": 0,
+                                    "left": 0,
+                                    "overflow": "scroll",
+                                    "position": "absolute",
+                                    "top": 0,
+                                    "visibility": "hidden",
+                                    "whiteSpace": "pre",
+                                  }
+                                }
+                              />
+                            </div>
+                          </AutosizeInput>
+                        </span>
+                        <span
+                          className="Select-arrow-zone"
+                          onMouseDown={[Function]}
+                        >
+                          <span
+                            className="Select-arrow"
+                            onMouseDown={[Function]}
+                          />
+                        </span>
+                      </div>
+                    </div>
+                  </Select>
+                </LazyLoader>
+              </Select>
+            </div>
+          </div>
+          <div
+            className="big-spacer-top"
+          >
+            <a
+              className="js-new link-base-color link-no-underline"
+              href="#"
+              onClick={[Function]}
+            >
+              <i
+                className="icon-radio spacer-right"
+              />
+              onboarding.organization.create_another_organization
+            </a>
+          </div>
+        </div>
+      </div>
+    </div>
+  </Step>
+</OrganizationStep>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectOnboarding-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectOnboarding-test.tsx.snap
new file mode 100644 (file)
index 0000000..0c5b93e
--- /dev/null
@@ -0,0 +1,349 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`guides for on-premise 1`] = `
+<React.Fragment>
+  <HelmetWrapper
+    defer={true}
+    encodeSpecialCharacters={true}
+    title="onboarding.project.header"
+    titleTemplate="%s"
+  />
+  <header
+    className="modal-head"
+  >
+    <h2>
+      onboarding.project.header
+    </h2>
+  </header>
+  <div
+    className="modal-body modal-container"
+  >
+    <p
+      className="spacer-top big-spacer-bottom"
+    >
+      onboarding.project.header.description.2
+    </p>
+    <TokenStep
+      currentUser={
+        Object {
+          "isLoggedIn": true,
+          "login": "admin",
+        }
+      }
+      finished={false}
+      onContinue={[Function]}
+      onOpen={[Function]}
+      open={true}
+      stepNumber={1}
+    />
+    <AnalysisStep
+      onFinish={[Function]}
+      onReset={[Function]}
+      open={false}
+      stepNumber={2}
+    />
+  </div>
+  <footer
+    className="modal-foot"
+  >
+    <ResetButtonLink
+      className="js-skip"
+      onClick={[Function]}
+    >
+      close
+    </ResetButtonLink>
+    <span
+      className="pull-left note"
+    >
+      tutorials.find_tutorial_back_in_help
+    </span>
+  </footer>
+</React.Fragment>
+`;
+
+exports[`guides for on-premise 2`] = `
+<React.Fragment>
+  <HelmetWrapper
+    defer={true}
+    encodeSpecialCharacters={true}
+    title="onboarding.project.header"
+    titleTemplate="%s"
+  />
+  <header
+    className="modal-head"
+  >
+    <h2>
+      onboarding.project.header
+    </h2>
+  </header>
+  <div
+    className="modal-body modal-container"
+  >
+    <p
+      className="spacer-top big-spacer-bottom"
+    >
+      onboarding.project.header.description.2
+    </p>
+    <TokenStep
+      currentUser={
+        Object {
+          "isLoggedIn": true,
+          "login": "admin",
+        }
+      }
+      finished={true}
+      onContinue={[Function]}
+      onOpen={[Function]}
+      open={false}
+      stepNumber={1}
+    />
+    <AnalysisStep
+      onFinish={[Function]}
+      onReset={[Function]}
+      open={true}
+      stepNumber={2}
+      token="abcd1234"
+    />
+  </div>
+  <footer
+    className="modal-foot"
+  >
+    <ResetButtonLink
+      className="js-skip"
+      onClick={[Function]}
+    >
+      close
+    </ResetButtonLink>
+    <span
+      className="pull-left note"
+    >
+      tutorials.find_tutorial_back_in_help
+    </span>
+  </footer>
+</React.Fragment>
+`;
+
+exports[`guides for sonarcloud 1`] = `
+<React.Fragment>
+  <HelmetWrapper
+    defer={true}
+    encodeSpecialCharacters={true}
+    title="onboarding.project.header"
+    titleTemplate="%s"
+  />
+  <header
+    className="modal-head"
+  >
+    <h2>
+      onboarding.project.header
+    </h2>
+  </header>
+  <div
+    className="modal-body modal-container"
+  >
+    <p
+      className="spacer-top big-spacer-bottom"
+    >
+      onboarding.project.header.description.3
+    </p>
+    <OrganizationStep
+      currentUser={
+        Object {
+          "isLoggedIn": true,
+          "login": "admin",
+        }
+      }
+      finished={false}
+      onContinue={[Function]}
+      onOpen={[Function]}
+      open={true}
+      stepNumber={1}
+    />
+    <TokenStep
+      currentUser={
+        Object {
+          "isLoggedIn": true,
+          "login": "admin",
+        }
+      }
+      finished={false}
+      onContinue={[Function]}
+      onOpen={[Function]}
+      open={false}
+      stepNumber={2}
+    />
+    <AnalysisStep
+      onFinish={[Function]}
+      onReset={[Function]}
+      open={false}
+      stepNumber={3}
+    />
+  </div>
+  <footer
+    className="modal-foot"
+  >
+    <ResetButtonLink
+      className="js-skip"
+      onClick={[Function]}
+    >
+      close
+    </ResetButtonLink>
+    <span
+      className="pull-left note"
+    >
+      tutorials.find_tutorial_back_in_plus
+    </span>
+  </footer>
+</React.Fragment>
+`;
+
+exports[`guides for sonarcloud 2`] = `
+<React.Fragment>
+  <HelmetWrapper
+    defer={true}
+    encodeSpecialCharacters={true}
+    title="onboarding.project.header"
+    titleTemplate="%s"
+  />
+  <header
+    className="modal-head"
+  >
+    <h2>
+      onboarding.project.header
+    </h2>
+  </header>
+  <div
+    className="modal-body modal-container"
+  >
+    <p
+      className="spacer-top big-spacer-bottom"
+    >
+      onboarding.project.header.description.3
+    </p>
+    <OrganizationStep
+      currentUser={
+        Object {
+          "isLoggedIn": true,
+          "login": "admin",
+        }
+      }
+      finished={true}
+      onContinue={[Function]}
+      onOpen={[Function]}
+      open={false}
+      stepNumber={1}
+    />
+    <TokenStep
+      currentUser={
+        Object {
+          "isLoggedIn": true,
+          "login": "admin",
+        }
+      }
+      finished={false}
+      onContinue={[Function]}
+      onOpen={[Function]}
+      open={true}
+      stepNumber={2}
+    />
+    <AnalysisStep
+      onFinish={[Function]}
+      onReset={[Function]}
+      open={false}
+      organization="my-org"
+      stepNumber={3}
+    />
+  </div>
+  <footer
+    className="modal-foot"
+  >
+    <ResetButtonLink
+      className="js-skip"
+      onClick={[Function]}
+    >
+      close
+    </ResetButtonLink>
+    <span
+      className="pull-left note"
+    >
+      tutorials.find_tutorial_back_in_plus
+    </span>
+  </footer>
+</React.Fragment>
+`;
+
+exports[`guides for sonarcloud 3`] = `
+<React.Fragment>
+  <HelmetWrapper
+    defer={true}
+    encodeSpecialCharacters={true}
+    title="onboarding.project.header"
+    titleTemplate="%s"
+  />
+  <header
+    className="modal-head"
+  >
+    <h2>
+      onboarding.project.header
+    </h2>
+  </header>
+  <div
+    className="modal-body modal-container"
+  >
+    <p
+      className="spacer-top big-spacer-bottom"
+    >
+      onboarding.project.header.description.3
+    </p>
+    <OrganizationStep
+      currentUser={
+        Object {
+          "isLoggedIn": true,
+          "login": "admin",
+        }
+      }
+      finished={true}
+      onContinue={[Function]}
+      onOpen={[Function]}
+      open={false}
+      stepNumber={1}
+    />
+    <TokenStep
+      currentUser={
+        Object {
+          "isLoggedIn": true,
+          "login": "admin",
+        }
+      }
+      finished={true}
+      onContinue={[Function]}
+      onOpen={[Function]}
+      open={false}
+      stepNumber={2}
+    />
+    <AnalysisStep
+      onFinish={[Function]}
+      onReset={[Function]}
+      open={true}
+      organization="my-org"
+      stepNumber={3}
+      token="abcd1234"
+    />
+  </div>
+  <footer
+    className="modal-foot"
+  >
+    <ResetButtonLink
+      className="js-skip"
+      onClick={[Function]}
+    >
+      close
+    </ResetButtonLink>
+    <span
+      className="pull-left note"
+    >
+      tutorials.find_tutorial_back_in_plus
+    </span>
+  </footer>
+</React.Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectWatcher-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectWatcher-test.tsx.snap
new file mode 100644 (file)
index 0000000..1cc4d99
--- /dev/null
@@ -0,0 +1,42 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<div
+  className="pull-left note"
+>
+  onboarding.project_watcher.not_started
+</div>
+`;
+
+exports[`renders 2`] = `
+<div
+  className="pull-left note"
+>
+  <i
+    className="spinner spacer-right"
+  />
+  onboarding.project_watcher.in_progress
+</div>
+`;
+
+exports[`renders 3`] = `
+<div
+  className="pull-left note display-inline-flex-center"
+>
+  <AlertSuccessIcon
+    className="spacer-right"
+  />
+  onboarding.project_watcher.finished
+</div>
+`;
+
+exports[`renders 4`] = `
+<div
+  className="pull-left note"
+>
+  <i
+    className="spinner spacer-right"
+  />
+  onboarding.project_watcher.in_progress
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/Step-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/Step-test.tsx.snap
new file mode 100644 (file)
index 0000000..4667ca7
--- /dev/null
@@ -0,0 +1,51 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<div
+  className="boxed-group onboarding-step is-open is-finished"
+>
+  <div
+    className="onboarding-step-number"
+  >
+    1
+  </div>
+  <div
+    className="boxed-group-header"
+  >
+    <h2>
+      First Step
+    </h2>
+  </div>
+  <div>
+    form
+  </div>
+</div>
+`;
+
+exports[`renders 2`] = `
+<div
+  className="boxed-group onboarding-step is-finished"
+  onClick={[Function]}
+  role="button"
+  tabIndex={0}
+>
+  <div
+    className="onboarding-step-number"
+  >
+    1
+  </div>
+  <div>
+    result
+  </div>
+  <div
+    className="boxed-group-header"
+  >
+    <h2>
+      First Step
+    </h2>
+  </div>
+  <div
+    className="boxed-group-inner"
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/TokenStep-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/TokenStep-test.tsx.snap
new file mode 100644 (file)
index 0000000..5484797
--- /dev/null
@@ -0,0 +1,649 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`generates token 1`] = `
+<TokenStep
+  currentUser={
+    Object {
+      "login": "user",
+    }
+  }
+  finished={false}
+  onContinue={[MockFunction]}
+  onOpen={[MockFunction]}
+  open={true}
+  stepNumber={1}
+>
+  <Step
+    finished={false}
+    onOpen={[MockFunction]}
+    open={true}
+    renderForm={[Function]}
+    renderResult={[Function]}
+    stepNumber={1}
+    stepTitle="onboarding.token.header"
+  >
+    <div
+      className="boxed-group onboarding-step is-open"
+    >
+      <div
+        className="onboarding-step-number"
+      >
+        1
+      </div>
+      <div
+        className="boxed-group-header"
+      >
+        <h2>
+          onboarding.token.header
+        </h2>
+      </div>
+      <div
+        className="boxed-group-inner"
+      >
+        <div>
+          <div>
+            <a
+              className="js-new link-base-color link-no-underline"
+              href="#"
+              onClick={[Function]}
+            >
+              <i
+                className="icon-radio spacer-right is-checked"
+              />
+              onboarding.token.generate_token
+            </a>
+            <div
+              className="big-spacer-top"
+            >
+              <form
+                onSubmit={[Function]}
+              >
+                <input
+                  autoFocus={true}
+                  className="input-large spacer-right text-middle"
+                  onChange={[Function]}
+                  placeholder="onboarding.token.generate_token.placeholder"
+                  required={true}
+                  type="text"
+                  value=""
+                />
+                <SubmitButton
+                  className="text-middle"
+                  disabled={true}
+                >
+                  <Button
+                    className="text-middle"
+                    disabled={true}
+                    preventDefault={false}
+                    type="submit"
+                  >
+                    <button
+                      className="button text-middle"
+                      disabled={true}
+                      onClick={[Function]}
+                      type="submit"
+                    >
+                      onboarding.token.generate
+                    </button>
+                  </Button>
+                </SubmitButton>
+              </form>
+            </div>
+          </div>
+          <div
+            className="big-spacer-top"
+          >
+            <a
+              className="js-new link-base-color link-no-underline"
+              href="#"
+              onClick={[Function]}
+            >
+              <i
+                className="icon-radio spacer-right"
+              />
+              onboarding.token.use_existing_token
+            </a>
+          </div>
+        </div>
+        <div
+          className="note big-spacer-top width-50"
+        >
+          onboarding.token.text
+        </div>
+      </div>
+    </div>
+  </Step>
+</TokenStep>
+`;
+
+exports[`generates token 2`] = `
+<TokenStep
+  currentUser={
+    Object {
+      "login": "user",
+    }
+  }
+  finished={false}
+  onContinue={[MockFunction]}
+  onOpen={[MockFunction]}
+  open={true}
+  stepNumber={1}
+>
+  <Step
+    finished={false}
+    onOpen={[MockFunction]}
+    open={true}
+    renderForm={[Function]}
+    renderResult={[Function]}
+    stepNumber={1}
+    stepTitle="onboarding.token.header"
+  >
+    <div
+      className="boxed-group onboarding-step is-open"
+    >
+      <div
+        className="onboarding-step-number"
+      >
+        1
+      </div>
+      <div
+        className="boxed-group-header"
+      >
+        <h2>
+          onboarding.token.header
+        </h2>
+      </div>
+      <div
+        className="boxed-group-inner"
+      >
+        <div>
+          <div>
+            <a
+              className="js-new link-base-color link-no-underline"
+              href="#"
+              onClick={[Function]}
+            >
+              <i
+                className="icon-radio spacer-right is-checked"
+              />
+              onboarding.token.generate_token
+            </a>
+            <div
+              className="big-spacer-top"
+            >
+              <form
+                onSubmit={[Function]}
+              >
+                <input
+                  autoFocus={true}
+                  className="input-large spacer-right text-middle"
+                  onChange={[Function]}
+                  placeholder="onboarding.token.generate_token.placeholder"
+                  required={true}
+                  type="text"
+                  value="my token"
+                />
+                <i
+                  className="spinner text-middle"
+                />
+              </form>
+            </div>
+          </div>
+          <div
+            className="big-spacer-top"
+          >
+            <a
+              className="js-new link-base-color link-no-underline"
+              href="#"
+              onClick={[Function]}
+            >
+              <i
+                className="icon-radio spacer-right"
+              />
+              onboarding.token.use_existing_token
+            </a>
+          </div>
+        </div>
+        <div
+          className="note big-spacer-top width-50"
+        >
+          onboarding.token.text
+        </div>
+      </div>
+    </div>
+  </Step>
+</TokenStep>
+`;
+
+exports[`generates token 3`] = `
+<TokenStep
+  currentUser={
+    Object {
+      "login": "user",
+    }
+  }
+  finished={false}
+  onContinue={[MockFunction]}
+  onOpen={[MockFunction]}
+  open={true}
+  stepNumber={1}
+>
+  <Step
+    finished={false}
+    onOpen={[MockFunction]}
+    open={true}
+    renderForm={[Function]}
+    renderResult={[Function]}
+    stepNumber={1}
+    stepTitle="onboarding.token.header"
+  >
+    <div
+      className="boxed-group onboarding-step is-open"
+    >
+      <div
+        className="onboarding-step-number"
+      >
+        1
+      </div>
+      <div
+        className="boxed-group-header"
+      >
+        <h2>
+          onboarding.token.header
+        </h2>
+      </div>
+      <div
+        className="boxed-group-inner"
+      >
+        <form
+          onSubmit={[Function]}
+        >
+          <span
+            className="text-middle"
+          >
+            my token
+            : 
+          </span>
+          <strong
+            className="spacer-right text-middle"
+          >
+            abcd1234
+          </strong>
+          <DeleteButton
+            className="button-small text-middle"
+            onClick={[Function]}
+          >
+            <ButtonIcon
+              className="button-small text-middle"
+              color="#d4333f"
+              onClick={[Function]}
+            >
+              <Button
+                className="button-small text-middle button-icon"
+                onClick={[Function]}
+                stopPropagation={true}
+                style={
+                  Object {
+                    "color": "#d4333f",
+                  }
+                }
+              >
+                <button
+                  className="button button-small text-middle button-icon"
+                  onClick={[Function]}
+                  style={
+                    Object {
+                      "color": "#d4333f",
+                    }
+                  }
+                  type="button"
+                >
+                  <ClearIcon />
+                </button>
+              </Button>
+            </ButtonIcon>
+          </DeleteButton>
+        </form>
+        <div
+          className="note big-spacer-top width-50"
+        >
+          onboarding.token.text
+        </div>
+        <div
+          className="big-spacer-top"
+        >
+          <Button
+            className="js-continue"
+            onClick={[Function]}
+          >
+            <button
+              className="button js-continue"
+              onClick={[Function]}
+              type="button"
+            >
+              continue
+            </button>
+          </Button>
+        </div>
+      </div>
+    </div>
+  </Step>
+</TokenStep>
+`;
+
+exports[`revokes token 1`] = `
+<TokenStep
+  currentUser={
+    Object {
+      "login": "user",
+    }
+  }
+  finished={false}
+  onContinue={[MockFunction]}
+  onOpen={[MockFunction]}
+  open={true}
+  stepNumber={1}
+>
+  <Step
+    finished={false}
+    onOpen={[MockFunction]}
+    open={true}
+    renderForm={[Function]}
+    renderResult={[Function]}
+    stepNumber={1}
+    stepTitle="onboarding.token.header"
+  >
+    <div
+      className="boxed-group onboarding-step is-open"
+    >
+      <div
+        className="onboarding-step-number"
+      >
+        1
+      </div>
+      <div
+        className="boxed-group-header"
+      >
+        <h2>
+          onboarding.token.header
+        </h2>
+      </div>
+      <div
+        className="boxed-group-inner"
+      >
+        <form
+          onSubmit={[Function]}
+        >
+          <span
+            className="text-middle"
+          >
+            my token
+            : 
+          </span>
+          <strong
+            className="spacer-right text-middle"
+          >
+            abcd1234
+          </strong>
+          <DeleteButton
+            className="button-small text-middle"
+            onClick={[Function]}
+          >
+            <ButtonIcon
+              className="button-small text-middle"
+              color="#d4333f"
+              onClick={[Function]}
+            >
+              <Button
+                className="button-small text-middle button-icon"
+                onClick={[Function]}
+                stopPropagation={true}
+                style={
+                  Object {
+                    "color": "#d4333f",
+                  }
+                }
+              >
+                <button
+                  className="button button-small text-middle button-icon"
+                  onClick={[Function]}
+                  style={
+                    Object {
+                      "color": "#d4333f",
+                    }
+                  }
+                  type="button"
+                >
+                  <ClearIcon />
+                </button>
+              </Button>
+            </ButtonIcon>
+          </DeleteButton>
+        </form>
+        <div
+          className="note big-spacer-top width-50"
+        >
+          onboarding.token.text
+        </div>
+        <div
+          className="big-spacer-top"
+        >
+          <Button
+            className="js-continue"
+            onClick={[Function]}
+          >
+            <button
+              className="button js-continue"
+              onClick={[Function]}
+              type="button"
+            >
+              continue
+            </button>
+          </Button>
+        </div>
+      </div>
+    </div>
+  </Step>
+</TokenStep>
+`;
+
+exports[`revokes token 2`] = `
+<TokenStep
+  currentUser={
+    Object {
+      "login": "user",
+    }
+  }
+  finished={false}
+  onContinue={[MockFunction]}
+  onOpen={[MockFunction]}
+  open={true}
+  stepNumber={1}
+>
+  <Step
+    finished={false}
+    onOpen={[MockFunction]}
+    open={true}
+    renderForm={[Function]}
+    renderResult={[Function]}
+    stepNumber={1}
+    stepTitle="onboarding.token.header"
+  >
+    <div
+      className="boxed-group onboarding-step is-open"
+    >
+      <div
+        className="onboarding-step-number"
+      >
+        1
+      </div>
+      <div
+        className="boxed-group-header"
+      >
+        <h2>
+          onboarding.token.header
+        </h2>
+      </div>
+      <div
+        className="boxed-group-inner"
+      >
+        <form
+          onSubmit={[Function]}
+        >
+          <span
+            className="text-middle"
+          >
+            my token
+            : 
+          </span>
+          <strong
+            className="spacer-right text-middle"
+          >
+            abcd1234
+          </strong>
+          <i
+            className="spinner text-middle"
+          />
+        </form>
+        <div
+          className="note big-spacer-top width-50"
+        >
+          onboarding.token.text
+        </div>
+        <div
+          className="big-spacer-top"
+        >
+          <Button
+            className="js-continue"
+            onClick={[Function]}
+          >
+            <button
+              className="button js-continue"
+              onClick={[Function]}
+              type="button"
+            >
+              continue
+            </button>
+          </Button>
+        </div>
+      </div>
+    </div>
+  </Step>
+</TokenStep>
+`;
+
+exports[`revokes token 3`] = `
+<TokenStep
+  currentUser={
+    Object {
+      "login": "user",
+    }
+  }
+  finished={false}
+  onContinue={[MockFunction]}
+  onOpen={[MockFunction]}
+  open={true}
+  stepNumber={1}
+>
+  <Step
+    finished={false}
+    onOpen={[MockFunction]}
+    open={true}
+    renderForm={[Function]}
+    renderResult={[Function]}
+    stepNumber={1}
+    stepTitle="onboarding.token.header"
+  >
+    <div
+      className="boxed-group onboarding-step is-open"
+    >
+      <div
+        className="onboarding-step-number"
+      >
+        1
+      </div>
+      <div
+        className="boxed-group-header"
+      >
+        <h2>
+          onboarding.token.header
+        </h2>
+      </div>
+      <div
+        className="boxed-group-inner"
+      >
+        <div>
+          <div>
+            <a
+              className="js-new link-base-color link-no-underline"
+              href="#"
+              onClick={[Function]}
+            >
+              <i
+                className="icon-radio spacer-right is-checked"
+              />
+              onboarding.token.generate_token
+            </a>
+            <div
+              className="big-spacer-top"
+            >
+              <form
+                onSubmit={[Function]}
+              >
+                <input
+                  autoFocus={true}
+                  className="input-large spacer-right text-middle"
+                  onChange={[Function]}
+                  placeholder="onboarding.token.generate_token.placeholder"
+                  required={true}
+                  type="text"
+                  value=""
+                />
+                <SubmitButton
+                  className="text-middle"
+                  disabled={true}
+                >
+                  <Button
+                    className="text-middle"
+                    disabled={true}
+                    preventDefault={false}
+                    type="submit"
+                  >
+                    <button
+                      className="button text-middle"
+                      disabled={true}
+                      onClick={[Function]}
+                      type="submit"
+                    >
+                      onboarding.token.generate
+                    </button>
+                  </Button>
+                </SubmitButton>
+              </form>
+            </div>
+          </div>
+          <div
+            className="big-spacer-top"
+          >
+            <a
+              className="js-new link-base-color link-no-underline"
+              href="#"
+              onClick={[Function]}
+            >
+              <i
+                className="icon-radio spacer-right"
+              />
+              onboarding.token.use_existing_token
+            </a>
+          </div>
+        </div>
+        <div
+          className="note big-spacer-top width-50"
+        >
+          onboarding.token.text
+        </div>
+      </div>
+    </div>
+  </Step>
+</TokenStep>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/BuildWrapper.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/BuildWrapper.tsx
new file mode 100644 (file)
index 0000000..ed03adb
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { translate } from '../../../../helpers/l10n';
+import { getBaseUrl } from '../../../../helpers/urls';
+
+interface Props {
+  className?: string;
+  os: string;
+}
+
+const filenames: { [key: string]: string } = {
+  win: 'build-wrapper-win-x86.zip',
+  linux: 'build-wrapper-linux-x86.zip',
+  mac: 'build-wrapper-macosx-x86.zip'
+};
+
+export default function BuildWrapper(props: Props) {
+  return (
+    <div className={props.className}>
+      <h4 className="spacer-bottom">
+        {translate('onboarding.analysis.build_wrapper.header', props.os)}
+      </h4>
+      <p
+        className="spacer-bottom markdown"
+        dangerouslySetInnerHTML={{
+          __html: translate('onboarding.analysis.build_wrapper.text', props.os)
+        }}
+      />
+      <p>
+        <a
+          className="button"
+          download={filenames[props.os]}
+          href={`${getBaseUrl()}/static/cpp/${filenames[props.os]}`}
+          target="_blank">
+          {translate('download_verb')}
+        </a>
+      </p>
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/ClangGCC.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/ClangGCC.tsx
new file mode 100644 (file)
index 0000000..6ebe42a
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import SQScanner from './SQScanner';
+import BuildWrapper from './BuildWrapper';
+import CodeSnippet from '../../../../components/common/CodeSnippet';
+import InstanceMessage from '../../../../components/common/InstanceMessage';
+import { translate } from '../../../../helpers/l10n';
+
+interface Props {
+  host: string;
+  os: string;
+  organization?: string;
+  projectKey: string;
+  token: string;
+}
+
+const executables: { [key: string]: string } = {
+  linux: 'build-wrapper-linux-x86-64',
+  win: 'build-wrapper-win-x86-64.exe',
+  mac: 'build-wrapper-macosx-x86'
+};
+
+export default function ClangGCC(props: Props) {
+  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 (
+    <div>
+      <SQScanner os={props.os} />
+      <BuildWrapper className="huge-spacer-top" os={props.os} />
+
+      <h4 className="huge-spacer-top spacer-bottom">
+        {translate('onboarding.analysis.sq_scanner.execute')}
+      </h4>
+      <InstanceMessage message={translate('onboarding.analysis.sq_scanner.execute.text')}>
+        {transformedMessage => (
+          <p
+            className="spacer-bottom markdown"
+            dangerouslySetInnerHTML={{ __html: transformedMessage }}
+          />
+        )}
+      </InstanceMessage>
+      <CodeSnippet isOneLine={true} snippet={command1} />
+      <CodeSnippet isOneLine={props.os === 'win'} snippet={command2} />
+      <p
+        className="big-spacer-top markdown"
+        dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.sq_scanner.docs') }}
+      />
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/DotNet.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/DotNet.tsx
new file mode 100644 (file)
index 0000000..347ea01
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import MSBuildScanner from './MSBuildScanner';
+import CodeSnippet from '../../../../components/common/CodeSnippet';
+import InstanceMessage from '../../../../components/common/InstanceMessage';
+import { translate } from '../../../../helpers/l10n';
+
+interface 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 (
+    <div>
+      <MSBuildScanner />
+
+      <h4 className="huge-spacer-top spacer-bottom">
+        {translate('onboarding.analysis.msbuild.execute')}
+      </h4>
+      <InstanceMessage message={translate('onboarding.analysis.msbuild.execute.text')}>
+        {transformedMessage => (
+          <p
+            className="spacer-bottom markdown"
+            dangerouslySetInnerHTML={{ __html: transformedMessage }}
+          />
+        )}
+      </InstanceMessage>
+      <CodeSnippet isOneLine={true} snippet={command1} />
+      <CodeSnippet isOneLine={true} snippet={command2} />
+      <CodeSnippet isOneLine={true} snippet={command3} />
+      <p
+        className="big-spacer-top markdown"
+        dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.msbuild.docs') }}
+      />
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaGradle.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaGradle.tsx
new file mode 100644 (file)
index 0000000..02da9fd
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import CodeSnippet from '../../../../components/common/CodeSnippet';
+import InstanceMessage from '../../../../components/common/InstanceMessage';
+import { translate } from '../../../../helpers/l10n';
+
+interface Props {
+  host: string;
+  organization?: string;
+  token: string;
+}
+
+export default function JavaGradle(props: Props) {
+  const config = 'plugins {\n  id "org.sonarqube" version "2.6"\n}';
+
+  const command = [
+    './gradlew sonarqube',
+    props.organization && `-Dsonar.organization=${props.organization}`,
+    `-Dsonar.host.url=${props.host}`,
+    `-Dsonar.login=${props.token}`
+  ];
+
+  return (
+    <div>
+      <h4 className="spacer-bottom">{translate('onboarding.analysis.java.gradle.header')}</h4>
+      <InstanceMessage message={translate('onboarding.analysis.java.gradle.text.1')}>
+        {transformedMessage => (
+          <p
+            className="spacer-bottom markdown"
+            dangerouslySetInnerHTML={{ __html: transformedMessage }}
+          />
+        )}
+      </InstanceMessage>
+      <CodeSnippet snippet={config} />
+      <p className="spacer-top spacer-bottom markdown">
+        {translate('onboarding.analysis.java.gradle.text.2')}
+      </p>
+      <CodeSnippet snippet={command} />
+      <p
+        className="big-spacer-top markdown"
+        dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.java.gradle.docs') }}
+      />
+      <p
+        className="big-spacer-top markdown"
+        dangerouslySetInnerHTML={{
+          __html: translate('onboarding.analysis.browse_url_after_analysis')
+        }}
+      />
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaMaven.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaMaven.tsx
new file mode 100644 (file)
index 0000000..5663b1f
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import CodeSnippet from '../../../../components/common/CodeSnippet';
+import InstanceMessage from '../../../../components/common/InstanceMessage';
+import { translate } from '../../../../helpers/l10n';
+
+interface 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 (
+    <div>
+      <h4 className="spacer-bottom">{translate('onboarding.analysis.java.maven.header')}</h4>
+      <p className="spacer-bottom markdown">
+        <InstanceMessage message={translate('onboarding.analysis.java.maven.text')} />
+      </p>
+      <CodeSnippet snippet={command} />
+      <p
+        className="big-spacer-top markdown"
+        dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.java.maven.docs') }}
+      />
+      <p
+        className="big-spacer-top markdown"
+        dangerouslySetInnerHTML={{
+          __html: translate('onboarding.analysis.browse_url_after_analysis')
+        }}
+      />
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/MSBuildScanner.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/MSBuildScanner.tsx
new file mode 100644 (file)
index 0000000..65c4005
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import { translate } from '../../../../helpers/l10n';
+
+interface Props {
+  className?: string;
+}
+
+export default function MSBuildScanner(props: Props) {
+  return (
+    <div className={props.className}>
+      <h4 className="spacer-bottom">{translate('onboarding.analysis.msbuild.header')}</h4>
+      <p
+        className="spacer-bottom markdown"
+        dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.msbuild.text') }}
+      />
+      <p>
+        <a
+          className="button"
+          href="http://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html"
+          rel="noopener noreferrer"
+          target="_blank">
+          {translate('download_verb')}
+        </a>
+      </p>
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Msvc.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Msvc.tsx
new file mode 100644 (file)
index 0000000..cf6120a
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import MSBuildScanner from './MSBuildScanner';
+import BuildWrapper from './BuildWrapper';
+import CodeSnippet from '../../../../components/common/CodeSnippet';
+import InstanceMessage from '../../../../components/common/InstanceMessage';
+import { translate } from '../../../../helpers/l10n';
+
+interface 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 (
+    <div>
+      <MSBuildScanner />
+      <BuildWrapper className="huge-spacer-top" os="win" />
+
+      <h4 className="huge-spacer-top spacer-bottom">
+        {translate('onboarding.analysis.msbuild.execute')}
+      </h4>
+      <InstanceMessage message={translate('onboarding.analysis.msbuild.execute.text')}>
+        {transformedMessage => (
+          <p
+            className="spacer-bottom markdown"
+            dangerouslySetInnerHTML={{ __html: transformedMessage }}
+          />
+        )}
+      </InstanceMessage>
+      <CodeSnippet isOneLine={true} snippet={command1} />
+      <CodeSnippet isOneLine={true} snippet={command2} />
+      <CodeSnippet isOneLine={true} snippet={command3} />
+      <p
+        className="big-spacer-top markdown"
+        dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.msbuild.docs') }}
+      />
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Other.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Other.tsx
new file mode 100644 (file)
index 0000000..3d0b3b2
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import SQScanner from './SQScanner';
+import CodeSnippet from '../../../../components/common/CodeSnippet';
+import InstanceMessage from '../../../../components/common/InstanceMessage';
+import { translate } from '../../../../helpers/l10n';
+
+interface 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 (
+    <div>
+      <SQScanner os={props.os} />
+
+      <h4 className="huge-spacer-top spacer-bottom">
+        {translate('onboarding.analysis.sq_scanner.execute')}
+      </h4>
+      <InstanceMessage message={translate('onboarding.analysis.sq_scanner.execute.text')}>
+        {transformedMessage => (
+          <p
+            className="spacer-bottom markdown"
+            dangerouslySetInnerHTML={{ __html: transformedMessage }}
+          />
+        )}
+      </InstanceMessage>
+      <CodeSnippet isOneLine={props.os === 'win'} snippet={command} />
+      <p
+        className="big-spacer-top markdown"
+        dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.sq_scanner.docs') }}
+      />
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/SQScanner.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/SQScanner.tsx
new file mode 100644 (file)
index 0000000..78b90f8
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import { translate } from '../../../../helpers/l10n';
+
+interface Props {
+  className?: string;
+  os: string;
+}
+
+export default function SQScanner(props: Props) {
+  return (
+    <div className={props.className}>
+      <h4 className="spacer-bottom">
+        {translate('onboarding.analysis.sq_scanner.header', props.os)}
+      </h4>
+      <p
+        className="spacer-bottom markdown"
+        dangerouslySetInnerHTML={{
+          __html: translate('onboarding.analysis.sq_scanner.text', props.os)
+        }}
+      />
+      <p>
+        <a
+          className="button"
+          href="http://redirect.sonarsource.com/doc/install-configure-scanner.html"
+          rel="noopener noreferrer"
+          target="_blank">
+          {translate('download_verb')}
+        </a>
+      </p>
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/BuildWrapper-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/BuildWrapper-test.tsx
new file mode 100644 (file)
index 0000000..b19e334
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import BuildWrapper from '../BuildWrapper';
+
+it('renders correctly', () => {
+  expect(shallow(<BuildWrapper os="win" />)).toMatchSnapshot();
+  expect(shallow(<BuildWrapper os="linux" />)).toMatchSnapshot();
+  expect(shallow(<BuildWrapper os="mac" />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/ClangGCC-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/ClangGCC-test.tsx
new file mode 100644 (file)
index 0000000..a6f7c4f
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ClangGCC from '../ClangGCC';
+
+it('renders correctly', () => {
+  expect(
+    shallow(<ClangGCC host="host" os="win" projectKey="projectKey" token="token" />)
+  ).toMatchSnapshot();
+
+  expect(
+    shallow(<ClangGCC host="host" os="linux" projectKey="projectKey" token="token" />)
+  ).toMatchSnapshot();
+
+  expect(
+    shallow(
+      <ClangGCC
+        host="host"
+        organization="organization"
+        os="linux"
+        projectKey="projectKey"
+        token="token"
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/DotNet-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/DotNet-test.tsx
new file mode 100644 (file)
index 0000000..91ecab8
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import DotNet from '../DotNet';
+
+it('renders correctly', () => {
+  expect(shallow(<DotNet host="host" projectKey="projectKey" token="token" />)).toMatchSnapshot();
+  expect(
+    shallow(
+      <DotNet host="host" organization="organization" projectKey="projectKey" token="token" />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaGradle-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaGradle-test.tsx
new file mode 100644 (file)
index 0000000..f0d130a
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import JavaGradle from '../JavaGradle';
+
+it('renders correctly', () => {
+  expect(shallow(<JavaGradle host="host" token="token" />)).toMatchSnapshot();
+  expect(
+    shallow(<JavaGradle host="host" organization="organization" token="token" />)
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaMaven-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaMaven-test.tsx
new file mode 100644 (file)
index 0000000..dbda16c
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import JavaMaven from '../JavaMaven';
+
+it('renders correctly', () => {
+  expect(shallow(<JavaMaven host="host" token="token" />)).toMatchSnapshot();
+  expect(
+    shallow(<JavaMaven host="host" organization="organization" token="token" />)
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/MSBuildScanner-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/MSBuildScanner-test.tsx
new file mode 100644 (file)
index 0000000..e541134
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import MSBuildScanner from '../MSBuildScanner';
+
+it('renders correctly', () => {
+  expect(shallow(<MSBuildScanner />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Msvc-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Msvc-test.tsx
new file mode 100644 (file)
index 0000000..78470bb
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Msvc from '../Msvc';
+
+it('renders correctly', () => {
+  expect(shallow(<Msvc host="host" projectKey="projectKey" token="token" />)).toMatchSnapshot();
+  expect(
+    shallow(<Msvc host="host" organization="organization" projectKey="projectKey" token="token" />)
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Other-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Other-test.tsx
new file mode 100644 (file)
index 0000000..16cc44a
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Other from '../Other';
+
+it('renders correctly', () => {
+  expect(
+    shallow(<Other host="host" os="win" projectKey="projectKey" token="token" />)
+  ).toMatchSnapshot();
+
+  expect(
+    shallow(<Other host="host" os="linux" projectKey="projectKey" token="token" />)
+  ).toMatchSnapshot();
+
+  expect(
+    shallow(
+      <Other
+        host="host"
+        organization="organization"
+        os="linux"
+        projectKey="projectKey"
+        token="token"
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/SQScanner-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/SQScanner-test.tsx
new file mode 100644 (file)
index 0000000..2be4e54
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import SQScanner from '../SQScanner';
+
+it('renders correctly', () => {
+  expect(shallow(<SQScanner os="win" />)).toMatchSnapshot();
+  expect(shallow(<SQScanner os="linux" />)).toMatchSnapshot();
+  expect(shallow(<SQScanner os="mac" />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap
new file mode 100644 (file)
index 0000000..07e8eb4
--- /dev/null
@@ -0,0 +1,85 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<div>
+  <h4
+    className="spacer-bottom"
+  >
+    onboarding.analysis.build_wrapper.header.win
+  </h4>
+  <p
+    className="spacer-bottom markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.build_wrapper.text.win",
+      }
+    }
+  />
+  <p>
+    <a
+      className="button"
+      download="build-wrapper-win-x86.zip"
+      href="/static/cpp/build-wrapper-win-x86.zip"
+      target="_blank"
+    >
+      download_verb
+    </a>
+  </p>
+</div>
+`;
+
+exports[`renders correctly 2`] = `
+<div>
+  <h4
+    className="spacer-bottom"
+  >
+    onboarding.analysis.build_wrapper.header.linux
+  </h4>
+  <p
+    className="spacer-bottom markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.build_wrapper.text.linux",
+      }
+    }
+  />
+  <p>
+    <a
+      className="button"
+      download="build-wrapper-linux-x86.zip"
+      href="/static/cpp/build-wrapper-linux-x86.zip"
+      target="_blank"
+    >
+      download_verb
+    </a>
+  </p>
+</div>
+`;
+
+exports[`renders correctly 3`] = `
+<div>
+  <h4
+    className="spacer-bottom"
+  >
+    onboarding.analysis.build_wrapper.header.mac
+  </h4>
+  <p
+    className="spacer-bottom markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.build_wrapper.text.mac",
+      }
+    }
+  />
+  <p>
+    <a
+      className="button"
+      download="build-wrapper-macosx-x86.zip"
+      href="/static/cpp/build-wrapper-macosx-x86.zip"
+      target="_blank"
+    >
+      download_verb
+    </a>
+  </p>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap
new file mode 100644 (file)
index 0000000..cdffd4c
--- /dev/null
@@ -0,0 +1,139 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<div>
+  <SQScanner
+    os="win"
+  />
+  <BuildWrapper
+    className="huge-spacer-top"
+    os="win"
+  />
+  <h4
+    className="huge-spacer-top spacer-bottom"
+  >
+    onboarding.analysis.sq_scanner.execute
+  </h4>
+  <InstanceMessage
+    message="onboarding.analysis.sq_scanner.execute.text"
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet="build-wrapper-win-x86-64.exe --out-dir bw-output make clean all"
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet={
+      Array [
+        "sonar-scanner.bat",
+        "-Dsonar.projectKey=projectKey",
+        undefined,
+        "-Dsonar.sources=.",
+        "-Dsonar.cfamily.build-wrapper-output=bw-output",
+        "-Dsonar.host.url=host",
+        "-Dsonar.login=token",
+      ]
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.sq_scanner.docs",
+      }
+    }
+  />
+</div>
+`;
+
+exports[`renders correctly 2`] = `
+<div>
+  <SQScanner
+    os="linux"
+  />
+  <BuildWrapper
+    className="huge-spacer-top"
+    os="linux"
+  />
+  <h4
+    className="huge-spacer-top spacer-bottom"
+  >
+    onboarding.analysis.sq_scanner.execute
+  </h4>
+  <InstanceMessage
+    message="onboarding.analysis.sq_scanner.execute.text"
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet="build-wrapper-linux-x86-64 --out-dir bw-output make clean all"
+  />
+  <CodeSnippet
+    isOneLine={false}
+    snippet={
+      Array [
+        "sonar-scanner",
+        "-Dsonar.projectKey=projectKey",
+        undefined,
+        "-Dsonar.sources=.",
+        "-Dsonar.cfamily.build-wrapper-output=bw-output",
+        "-Dsonar.host.url=host",
+        "-Dsonar.login=token",
+      ]
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.sq_scanner.docs",
+      }
+    }
+  />
+</div>
+`;
+
+exports[`renders correctly 3`] = `
+<div>
+  <SQScanner
+    os="linux"
+  />
+  <BuildWrapper
+    className="huge-spacer-top"
+    os="linux"
+  />
+  <h4
+    className="huge-spacer-top spacer-bottom"
+  >
+    onboarding.analysis.sq_scanner.execute
+  </h4>
+  <InstanceMessage
+    message="onboarding.analysis.sq_scanner.execute.text"
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet="build-wrapper-linux-x86-64 --out-dir bw-output make clean all"
+  />
+  <CodeSnippet
+    isOneLine={false}
+    snippet={
+      Array [
+        "sonar-scanner",
+        "-Dsonar.projectKey=projectKey",
+        "-Dsonar.organization=organization",
+        "-Dsonar.sources=.",
+        "-Dsonar.cfamily.build-wrapper-output=bw-output",
+        "-Dsonar.host.url=host",
+        "-Dsonar.login=token",
+      ]
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.sq_scanner.docs",
+      }
+    }
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/DotNet-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/DotNet-test.tsx.snap
new file mode 100644 (file)
index 0000000..89b57fa
--- /dev/null
@@ -0,0 +1,95 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<div>
+  <MSBuildScanner />
+  <h4
+    className="huge-spacer-top spacer-bottom"
+  >
+    onboarding.analysis.msbuild.execute
+  </h4>
+  <InstanceMessage
+    message="onboarding.analysis.msbuild.execute.text"
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet={
+      Array [
+        "SonarQube.Scanner.MSBuild.exe begin",
+        "/k:\\"projectKey\\"",
+        undefined,
+        "/d:sonar.host.url=\\"host\\"",
+        "/d:sonar.login=\\"token\\"",
+      ]
+    }
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet="MsBuild.exe /t:Rebuild"
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet={
+      Array [
+        "SonarQube.Scanner.MSBuild.exe end",
+        "/d:sonar.login=\\"token\\"",
+      ]
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.msbuild.docs",
+      }
+    }
+  />
+</div>
+`;
+
+exports[`renders correctly 2`] = `
+<div>
+  <MSBuildScanner />
+  <h4
+    className="huge-spacer-top spacer-bottom"
+  >
+    onboarding.analysis.msbuild.execute
+  </h4>
+  <InstanceMessage
+    message="onboarding.analysis.msbuild.execute.text"
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet={
+      Array [
+        "SonarQube.Scanner.MSBuild.exe begin",
+        "/k:\\"projectKey\\"",
+        "/d:sonar.organization=\\"organization\\"",
+        "/d:sonar.host.url=\\"host\\"",
+        "/d:sonar.login=\\"token\\"",
+      ]
+    }
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet="MsBuild.exe /t:Rebuild"
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet={
+      Array [
+        "SonarQube.Scanner.MSBuild.exe end",
+        "/d:sonar.login=\\"token\\"",
+      ]
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.msbuild.docs",
+      }
+    }
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap
new file mode 100644 (file)
index 0000000..401137a
--- /dev/null
@@ -0,0 +1,99 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<div>
+  <h4
+    className="spacer-bottom"
+  >
+    onboarding.analysis.java.gradle.header
+  </h4>
+  <InstanceMessage
+    message="onboarding.analysis.java.gradle.text.1"
+  />
+  <CodeSnippet
+    snippet="plugins {
+  id \\"org.sonarqube\\" version \\"2.6\\"
+}"
+  />
+  <p
+    className="spacer-top spacer-bottom markdown"
+  >
+    onboarding.analysis.java.gradle.text.2
+  </p>
+  <CodeSnippet
+    snippet={
+      Array [
+        "./gradlew sonarqube",
+        undefined,
+        "-Dsonar.host.url=host",
+        "-Dsonar.login=token",
+      ]
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.java.gradle.docs",
+      }
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.browse_url_after_analysis",
+      }
+    }
+  />
+</div>
+`;
+
+exports[`renders correctly 2`] = `
+<div>
+  <h4
+    className="spacer-bottom"
+  >
+    onboarding.analysis.java.gradle.header
+  </h4>
+  <InstanceMessage
+    message="onboarding.analysis.java.gradle.text.1"
+  />
+  <CodeSnippet
+    snippet="plugins {
+  id \\"org.sonarqube\\" version \\"2.6\\"
+}"
+  />
+  <p
+    className="spacer-top spacer-bottom markdown"
+  >
+    onboarding.analysis.java.gradle.text.2
+  </p>
+  <CodeSnippet
+    snippet={
+      Array [
+        "./gradlew sonarqube",
+        "-Dsonar.organization=organization",
+        "-Dsonar.host.url=host",
+        "-Dsonar.login=token",
+      ]
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.java.gradle.docs",
+      }
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.browse_url_after_analysis",
+      }
+    }
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap
new file mode 100644 (file)
index 0000000..d4462b9
--- /dev/null
@@ -0,0 +1,87 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<div>
+  <h4
+    className="spacer-bottom"
+  >
+    onboarding.analysis.java.maven.header
+  </h4>
+  <p
+    className="spacer-bottom markdown"
+  >
+    <InstanceMessage
+      message="onboarding.analysis.java.maven.text"
+    />
+  </p>
+  <CodeSnippet
+    snippet={
+      Array [
+        "mvn sonar:sonar",
+        undefined,
+        "-Dsonar.host.url=host",
+        "-Dsonar.login=token",
+      ]
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.java.maven.docs",
+      }
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.browse_url_after_analysis",
+      }
+    }
+  />
+</div>
+`;
+
+exports[`renders correctly 2`] = `
+<div>
+  <h4
+    className="spacer-bottom"
+  >
+    onboarding.analysis.java.maven.header
+  </h4>
+  <p
+    className="spacer-bottom markdown"
+  >
+    <InstanceMessage
+      message="onboarding.analysis.java.maven.text"
+    />
+  </p>
+  <CodeSnippet
+    snippet={
+      Array [
+        "mvn sonar:sonar",
+        "-Dsonar.organization=organization",
+        "-Dsonar.host.url=host",
+        "-Dsonar.login=token",
+      ]
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.java.maven.docs",
+      }
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.browse_url_after_analysis",
+      }
+    }
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap
new file mode 100644 (file)
index 0000000..740e26e
--- /dev/null
@@ -0,0 +1,29 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<div>
+  <h4
+    className="spacer-bottom"
+  >
+    onboarding.analysis.msbuild.header
+  </h4>
+  <p
+    className="spacer-bottom markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.msbuild.text",
+      }
+    }
+  />
+  <p>
+    <a
+      className="button"
+      href="http://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html"
+      rel="noopener noreferrer"
+      target="_blank"
+    >
+      download_verb
+    </a>
+  </p>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Msvc-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Msvc-test.tsx.snap
new file mode 100644 (file)
index 0000000..2240a13
--- /dev/null
@@ -0,0 +1,105 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<div>
+  <MSBuildScanner />
+  <BuildWrapper
+    className="huge-spacer-top"
+    os="win"
+  />
+  <h4
+    className="huge-spacer-top spacer-bottom"
+  >
+    onboarding.analysis.msbuild.execute
+  </h4>
+  <InstanceMessage
+    message="onboarding.analysis.msbuild.execute.text"
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet={
+      Array [
+        "SonarQube.Scanner.MSBuild.exe begin",
+        "/k:\\"projectKey\\"",
+        undefined,
+        "/d:sonar.cfamily.build-wrapper-output=bw-output",
+        "/d:sonar.host.url=\\"host\\"",
+        "/d:sonar.login=\\"token\\"",
+      ]
+    }
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet="build-wrapper-win-x86-64.exe --out-dir bw-output MsBuild.exe /t:Rebuild"
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet={
+      Array [
+        "SonarQube.Scanner.MSBuild.exe end",
+        "/d:sonar.login=\\"token\\"",
+      ]
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.msbuild.docs",
+      }
+    }
+  />
+</div>
+`;
+
+exports[`renders correctly 2`] = `
+<div>
+  <MSBuildScanner />
+  <BuildWrapper
+    className="huge-spacer-top"
+    os="win"
+  />
+  <h4
+    className="huge-spacer-top spacer-bottom"
+  >
+    onboarding.analysis.msbuild.execute
+  </h4>
+  <InstanceMessage
+    message="onboarding.analysis.msbuild.execute.text"
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet={
+      Array [
+        "SonarQube.Scanner.MSBuild.exe begin",
+        "/k:\\"projectKey\\"",
+        "/d:sonar.organization=\\"organization\\"",
+        "/d:sonar.cfamily.build-wrapper-output=bw-output",
+        "/d:sonar.host.url=\\"host\\"",
+        "/d:sonar.login=\\"token\\"",
+      ]
+    }
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet="build-wrapper-win-x86-64.exe --out-dir bw-output MsBuild.exe /t:Rebuild"
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet={
+      Array [
+        "SonarQube.Scanner.MSBuild.exe end",
+        "/d:sonar.login=\\"token\\"",
+      ]
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.msbuild.docs",
+      }
+    }
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Other-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Other-test.tsx.snap
new file mode 100644 (file)
index 0000000..73aba35
--- /dev/null
@@ -0,0 +1,112 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<div>
+  <SQScanner
+    os="win"
+  />
+  <h4
+    className="huge-spacer-top spacer-bottom"
+  >
+    onboarding.analysis.sq_scanner.execute
+  </h4>
+  <InstanceMessage
+    message="onboarding.analysis.sq_scanner.execute.text"
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet={
+      Array [
+        "sonar-scanner.bat",
+        "-Dsonar.projectKey=projectKey",
+        undefined,
+        "-Dsonar.sources=.",
+        "-Dsonar.host.url=host",
+        "-Dsonar.login=token",
+      ]
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.sq_scanner.docs",
+      }
+    }
+  />
+</div>
+`;
+
+exports[`renders correctly 2`] = `
+<div>
+  <SQScanner
+    os="linux"
+  />
+  <h4
+    className="huge-spacer-top spacer-bottom"
+  >
+    onboarding.analysis.sq_scanner.execute
+  </h4>
+  <InstanceMessage
+    message="onboarding.analysis.sq_scanner.execute.text"
+  />
+  <CodeSnippet
+    isOneLine={false}
+    snippet={
+      Array [
+        "sonar-scanner",
+        "-Dsonar.projectKey=projectKey",
+        undefined,
+        "-Dsonar.sources=.",
+        "-Dsonar.host.url=host",
+        "-Dsonar.login=token",
+      ]
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.sq_scanner.docs",
+      }
+    }
+  />
+</div>
+`;
+
+exports[`renders correctly 3`] = `
+<div>
+  <SQScanner
+    os="linux"
+  />
+  <h4
+    className="huge-spacer-top spacer-bottom"
+  >
+    onboarding.analysis.sq_scanner.execute
+  </h4>
+  <InstanceMessage
+    message="onboarding.analysis.sq_scanner.execute.text"
+  />
+  <CodeSnippet
+    isOneLine={false}
+    snippet={
+      Array [
+        "sonar-scanner",
+        "-Dsonar.projectKey=projectKey",
+        "-Dsonar.organization=organization",
+        "-Dsonar.sources=.",
+        "-Dsonar.host.url=host",
+        "-Dsonar.login=token",
+      ]
+    }
+  />
+  <p
+    className="big-spacer-top markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.sq_scanner.docs",
+      }
+    }
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap
new file mode 100644 (file)
index 0000000..cf6a8de
--- /dev/null
@@ -0,0 +1,85 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<div>
+  <h4
+    className="spacer-bottom"
+  >
+    onboarding.analysis.sq_scanner.header.win
+  </h4>
+  <p
+    className="spacer-bottom markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.sq_scanner.text.win",
+      }
+    }
+  />
+  <p>
+    <a
+      className="button"
+      href="http://redirect.sonarsource.com/doc/install-configure-scanner.html"
+      rel="noopener noreferrer"
+      target="_blank"
+    >
+      download_verb
+    </a>
+  </p>
+</div>
+`;
+
+exports[`renders correctly 2`] = `
+<div>
+  <h4
+    className="spacer-bottom"
+  >
+    onboarding.analysis.sq_scanner.header.linux
+  </h4>
+  <p
+    className="spacer-bottom markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.sq_scanner.text.linux",
+      }
+    }
+  />
+  <p>
+    <a
+      className="button"
+      href="http://redirect.sonarsource.com/doc/install-configure-scanner.html"
+      rel="noopener noreferrer"
+      target="_blank"
+    >
+      download_verb
+    </a>
+  </p>
+</div>
+`;
+
+exports[`renders correctly 3`] = `
+<div>
+  <h4
+    className="spacer-bottom"
+  >
+    onboarding.analysis.sq_scanner.header.mac
+  </h4>
+  <p
+    className="spacer-bottom markdown"
+    dangerouslySetInnerHTML={
+      Object {
+        "__html": "onboarding.analysis.sq_scanner.text.mac",
+      }
+    }
+  />
+  <p>
+    <a
+      className="button"
+      href="http://redirect.sonarsource.com/doc/install-configure-scanner.html"
+      rel="noopener noreferrer"
+      target="_blank"
+    >
+      download_verb
+    </a>
+  </p>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/styles.css b/server/sonar-web/src/main/js/apps/tutorials/styles.css
new file mode 100644 (file)
index 0000000..f73428e
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+.onboarding-step {
+  position: relative;
+  padding-left: 34px;
+}
+
+.onboarding-step:not(.is-open):not(.is-finished) {
+  opacity: 0.4;
+}
+
+.onboarding-step .boxed-group-actions {
+  height: var(--controlHeight);
+  line-height: var(--controlHeight);
+}
+
+.onboarding-step-number {
+  position: absolute;
+  top: 15px;
+  left: 15px;
+  width: 24px;
+  height: 24px;
+  line-height: 24px;
+  border-radius: 24px;
+  background-color: #b9b9b9;
+  color: #fff;
+  font-size: var(--mediumFontSize);
+  text-align: center;
+}
+
+.onboarding-step.is-open .onboarding-step-number {
+  background-color: var(--darkBlue);
+}
+
+.onboarding-step.is-finished {
+  cursor: pointer;
+  outline: none;
+}
+
+.onboarding-choices {
+  display: flex;
+  justify-content: space-around;
+  padding: 24px 0 44px;
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/TeamOnboardingModal.tsx b/server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/TeamOnboardingModal.tsx
new file mode 100644 (file)
index 0000000..0763e82
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { Link } from 'react-router';
+import Modal from '../../../components/controls/Modal';
+import { translate } from '../../../helpers/l10n';
+import { ResetButtonLink } from '../../../components/ui/buttons';
+
+interface Props {
+  onFinish: () => void;
+}
+
+export default class TeamOnboardingModal extends React.PureComponent<Props> {
+  render() {
+    const header = translate('onboarding.team.header');
+    return (
+      <Modal
+        contentLabel={header}
+        medium={true}
+        onRequestClose={this.props.onFinish}
+        shouldCloseOnOverlayClick={false}>
+        <header className="modal-head">
+          <h2>{header}</h2>
+        </header>
+        <div className="modal-body">
+          <div className="alert alert-info modal-alert">
+            {translate('onboarding.team.work_in_progress')}
+          </div>
+          <p className="spacer-top big-spacer-bottom">{translate('onboarding.team.first_step')}</p>
+          <p className="spacer-top big-spacer-bottom">
+            <FormattedMessage
+              defaultMessage={translate('onboarding.team.how_to_join')}
+              id="onboarding.team.how_to_join"
+              values={{
+                link: (
+                  <Link onClick={this.props.onFinish} to="/documentation/organizations/manage-team">
+                    {translate('as_explained_here')}
+                  </Link>
+                )
+              }}
+            />
+          </p>
+        </div>
+        <footer className="modal-foot">
+          <ResetButtonLink onClick={this.props.onFinish}>{translate('close')}</ResetButtonLink>
+        </footer>
+      </Modal>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/__tests__/TeamOnboardingModal-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/__tests__/TeamOnboardingModal-test.tsx
new file mode 100644 (file)
index 0000000..0bd903f
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import TeamOnboardingModal from '../TeamOnboardingModal';
+
+it('renders correctly', () => {
+  expect(shallow(<TeamOnboardingModal onFinish={jest.fn()} />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/__tests__/__snapshots__/TeamOnboardingModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/teamOnboarding/__tests__/__snapshots__/TeamOnboardingModal-test.tsx.snap
new file mode 100644 (file)
index 0000000..a510b0e
--- /dev/null
@@ -0,0 +1,61 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<Modal
+  contentLabel="onboarding.team.header"
+  medium={true}
+  onRequestClose={[MockFunction]}
+  shouldCloseOnOverlayClick={false}
+>
+  <header
+    className="modal-head"
+  >
+    <h2>
+      onboarding.team.header
+    </h2>
+  </header>
+  <div
+    className="modal-body"
+  >
+    <div
+      className="alert alert-info modal-alert"
+    >
+      onboarding.team.work_in_progress
+    </div>
+    <p
+      className="spacer-top big-spacer-bottom"
+    >
+      onboarding.team.first_step
+    </p>
+    <p
+      className="spacer-top big-spacer-bottom"
+    >
+      <FormattedMessage
+        defaultMessage="onboarding.team.how_to_join"
+        id="onboarding.team.how_to_join"
+        values={
+          Object {
+            "link": <Link
+              onClick={[MockFunction]}
+              onlyActiveOnIndex={false}
+              style={Object {}}
+              to="/documentation/organizations/manage-team"
+            >
+              as_explained_here
+            </Link>,
+          }
+        }
+      />
+    </p>
+  </div>
+  <footer
+    className="modal-foot"
+  >
+    <ResetButtonLink
+      onClick={[MockFunction]}
+    >
+      close
+    </ResetButtonLink>
+  </footer>
+</Modal>
+`;
index d650182b93266a28d60f324d992e1b8ec275f594..f3f96a9506ade3bac52c2499b6086ba4d86b688b 100644 (file)
@@ -44,13 +44,13 @@ export function clickOutside(event = {}): void {
   window.dispatchEvent(dispatchedEvent);
 }
 
-export function submit(element: ShallowWrapper): void {
+export function submit(element: ShallowWrapper | ReactWrapper): void {
   element.simulate('submit', {
     preventDefault() {}
   });
 }
 
-export function change(element: ShallowWrapper, value: string, event = {}): void {
+export function change(element: ShallowWrapper | ReactWrapper, value: string, event = {}): void {
   element.simulate('change', {
     target: { value },
     currentTarget: { value },
index ac5f5a477ce798f66296d7bdb73af3633ad3bf95..0027ae9588213735f79e1a49d46fc49e37418e40 100644 (file)
@@ -202,6 +202,7 @@ no=No
 
 and_worse=and worse
 are_you_sure=Are you sure?
+as_explained_here=as explained here
 assigned_to=Assigned to
 bulk_change=Bulk Change
 bulleted_point=Bulleted point
@@ -921,8 +922,8 @@ shortcuts.section.rules.deactivate=deactivate selected rule
 tutorials.onboarding=Analyze a new project
 tutorials.skip=Skip this tutorial
 tutorials.finish=Finish this tutorial
-tutorials.find_it_back_in_help=Find it back anytime in the Help section
-tutorials.find_it_back_in_plus=Find it back anytime in the "+" menu
+tutorials.find_tutorial_back_in_help=Find this tutorial back anytime in the Help section
+tutorials.find_tutorial_back_in_plus=Find this tutorial back anytime in the "+" menu
 
 
 #------------------------------------------------------------------------------
@@ -2589,15 +2590,30 @@ footer.web_api=Web API
 # ONBOARDING
 #
 #------------------------------------------------------------------------------
-onboarding.header=Welcome to {instance}!
-onboarding.header.description=Want to quickly analyze a first project? Follow these {0} easy steps.
+onboarding.header=Welcome to SonarCloud!
+onboarding.header.description=Let us help you get started. What do you want to do?
+
+onboarding.project.header=Analyze a project
+onboarding.project.header.description=Want to quickly analyze a first project? Follow these {0} easy steps.
+
+onboarding.team.header=Join a team
+onboarding.team.first_step=Well congrats, the first step is done!
+onboarding.team.how_to_join=To join a team, the only thing you need to do is to be a user registered on Sonarcloud. The administrator of the Sonarcloud organization you wish to join has to add you to his organization's members {link}. Ask him to do so!
+onboarding.team.work_in_progress=We are currently working on a better way to join a team or invite people to yours.
+
+onboarding.analyze_public_code=I want to analyze public code
+onboarding.analyze_public_code.button=Analyze a project
+onboarding.analyze_private_code=I want to analyze private code
+onboarding.analyze_private_code.button=Setup a new organization
+onboarding.contribute_existing_project=I want to contribute to an existing project
+onboarding.contribute_existing_project.button=Join a team
 
 onboarding.token.header=Provide a token
 onboarding.token.text=The token is used to identify you when an analysis is performed. If it has been compromised, you can revoke it at any point of time in your user account.
 onboarding.token.generate=Generate
 onboarding.token.placeholder=Enter a name for your token
-onboading.token.generate_token=Generate a token
-onboading.token.generate_token.placeholder=Enter a name for your token
+onboarding.token.generate_token=Generate a token
+onboarding.token.generate_token.placeholder=Enter a name for your token
 onboarding.token.use_existing_token=Use existing token
 onboarding.token.use_existing_token.placeholder=Enter your existing token
 onboarding.token.invalid_format=The token you have entered has invalid format.