]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11029 Move the tutorial inside provisioned projects dashboard
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Thu, 26 Jul 2018 09:44:57 +0000 (11:44 +0200)
committerSonarTech <sonartech@sonarsource.com>
Fri, 10 Aug 2018 18:21:30 +0000 (20:21 +0200)
* Move/Rename files of tutorials folder
* SONAR-11049 Update tutorial UI and move it inside the project dashboard
* SONAR-11050 Update tutorial to skip now useless steps
* Remove unused style
* SONAR-11030 Make dashboard tutorial work with already known project key
* Better manage error messages when no analysis and analyzed branches
* SONAR-11052 Refresh project status as long as there is no analysis
* SONAR-11051 Add infos suggestions depending on the ALM of the project
* Do no display tutorial when there is analyses in the pipe

173 files changed:
server/sonar-docs/src/templates/page.css
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
server/sonar-web/src/main/js/app/components/StartupModal.tsx
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
server/sonar-web/src/main/js/app/styles/components/alerts.css
server/sonar-web/src/main/js/app/styles/components/boxed-group.css
server/sonar-web/src/main/js/app/styles/components/page.css
server/sonar-web/src/main/js/app/styles/init/misc.css
server/sonar-web/src/main/js/app/styles/sonarcloud.css
server/sonar-web/src/main/js/app/types.ts
server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx
server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js
server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.js.snap
server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx
server/sonar-web/src/main/js/apps/overview/components/App.tsx
server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
server/sonar-web/src/main/js/apps/overview/components/SonarCloudEmptyOverview.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx
server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx
server/sonar-web/src/main/js/apps/overview/components/__tests__/SonarCloudEmptyOverview-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/components/__tests__/Timeline-test.js [deleted file]
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/EmptyOverview-test.tsx.snap
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/SonarCloudEmptyOverview-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/Timeline-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx
server/sonar-web/src/main/js/apps/overview/styles.css
server/sonar-web/src/main/js/apps/projects/create/AlmRepositoryItem.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/create/AutoProjectCreate.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/create/CreateProjectPage.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/create/ManualProjectCreate.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/create/__tests__/AlmRepositoryItem-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/create/__tests__/AutoProjectCreate-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/create/__tests__/CreateProjectPage-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/create/__tests__/ManualProjectCreate-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/AlmRepositoryItem-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/create/utils.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/routes.ts
server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorial-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSuggestion-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorial-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorialSuggestion-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/LanguageForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/NewOrganizationForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/NewProjectForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/OrganizationStep.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/ProjectAnalysisStep.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/Step.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/TokenStep.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/LanguageForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/NewOrganizationForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/NewProjectForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/OrganizationStep-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/Step-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TokenStep-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/LanguageForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/NewProjectForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/OrganizationStep-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/Step-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/TokenStep-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/AnalysisCommand.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/BuildWrapper.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/ClangGCC.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/DotNet.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaGradle.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaMaven.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/MSBuildScanner.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/Msvc.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/Other.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/SQScanner.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/AnalysisCommand-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/BuildWrapper-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/ClangGCC-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/DotNet-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/JavaGradle-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/JavaMaven-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/MSBuildScanner-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/Msvc-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/Other-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/SQScanner-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/AnalysisCommand-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/DotNet-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/Msvc-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/Other-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AlmRepositoryItem.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AutoProjectCreate.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/CreateProjectOnboarding.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/ManualProjectCreate.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AlmRepositoryItem-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AutoProjectCreate-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/CreateProjectOnboarding-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/ManualProjectCreate-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AlmRepositoryItem-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/CreateProjectOnboarding-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/AnalysisStep.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/LanguageStep.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewOrganizationForm.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewProjectForm.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/OrganizationStep.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/Step.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/TokenStep.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/LanguageStep-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewOrganizationForm-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewProjectForm-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/OrganizationStep-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/Step-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/TokenStep-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/LanguageStep-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewProjectForm-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/OrganizationStep-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectOnboarding-test.tsx.snap
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/Step-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/TokenStep-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/BuildWrapper.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/ClangGCC.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/DotNet.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaGradle.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaMaven.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/MSBuildScanner.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Msvc.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Other.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/SQScanner.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/BuildWrapper-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/ClangGCC-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/DotNet-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaGradle-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaMaven-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/MSBuildScanner-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Msvc-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Other-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/SQScanner-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/DotNet-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Msvc-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Other-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/tutorials/routes.ts
server/sonar-web/src/main/js/apps/tutorials/styles.css
server/sonar-web/src/main/js/apps/tutorials/utils.ts [new file with mode: 0644]
server/sonar-web/src/main/js/components/icons-components/OnboardingPrivateIcon.tsx
server/sonar-web/src/main/js/components/icons-components/OnboardingProjectIcon.tsx
server/sonar-web/src/main/js/components/icons-components/OnboardingTeamIcon.tsx
server/sonar-web/src/main/js/components/ui/buttons.css
server/sonar-web/src/main/js/helpers/markdown.js
server/sonar-web/src/main/js/helpers/urls.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 73515b1460dd3deb5400f4a70f6149e8d7fb0ac1..3b7180f4baf501b74c3cd77c9e502e3ac409c95f 100644 (file)
@@ -24,7 +24,7 @@
 .alert-info {
   border-color: #bce8f1;
   background-color: #d9edf7;
-  color: #31708f;
+  color: #666666;
 }
 
 .alert-success {
index 82d4b81197ee1fb7058a4512e770c542f6721d64..176a21d06ac03faabbf99a05d98a3bf2bb0736be 100644 (file)
@@ -176,7 +176,12 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
                   differenceBy(newTasksInProgress, tasksInProgress, 'id').length > 0);
 
               shouldFetchComponent = Boolean(currentTaskChanged || progressChanged);
-              if (!shouldFetchComponent && component && newTasksInProgress.length > 0) {
+              if (
+                !shouldFetchComponent &&
+                component &&
+                (newTasksInProgress.length > 0 || !component.analysisDate)
+              ) {
+                // Refresh the status as long as there is tasks in progress or no analysis
                 window.clearTimeout(this.watchStatusTimer);
                 this.watchStatusTimer = window.setTimeout(
                   () => this.fetchStatus(component),
index 8a7e7e2f44469cf8be08b841249dfefe39289d63..b85885bbcd0af8fd0ba0c79a198904431ccd66b8 100644 (file)
@@ -35,7 +35,7 @@ import { lazyLoad } from '../../components/lazyLoad';
 const CreateOrganizationForm = lazyLoad(() =>
   import('../../apps/account/organizations/CreateOrganizationForm')
 );
-const Onboarding = lazyLoad(() => import('../../apps/tutorials/Onboarding'));
+const OnboardingModal = lazyLoad(() => import('../../apps/tutorials/onboarding/OnboardingModal'));
 const LicensePromptModal = lazyLoad(
   () => import('../../apps/marketplace/components/LicensePromptModal'),
   'LicensePromptModal'
@@ -135,7 +135,7 @@ export class StartupModal extends React.PureComponent<Props, State> {
   openProjectOnboarding = () => {
     if (isSonarCloud()) {
       this.setState({ automatic: false, modal: undefined });
-      this.context.router.push(`/onboarding`);
+      this.context.router.push(`/projects/create`);
     } else {
       this.setState({ modal: ModalKey.projectOnboarding });
     }
@@ -189,7 +189,7 @@ export class StartupModal extends React.PureComponent<Props, State> {
         {this.props.children}
         {modal === ModalKey.license && <LicensePromptModal onClose={this.closeLicense} />}
         {modal === ModalKey.onboarding && (
-          <Onboarding
+          <OnboardingModal
             onClose={this.closeOnboarding}
             onOpenOrganizationOnboarding={this.openOrganizationOnboarding}
             onOpenProjectOnboarding={this.openProjectOnboarding}
index 83a4afb28220a38008fdce7910c2fe91075777aa..e67564559f667dea0ccbdfcb0ba241d36a448eef 100644 (file)
@@ -36,25 +36,23 @@ import { STATUSES } from '../../../apps/background-tasks/constants';
 import { waitAndUpdate } from '../../../helpers/testUtils';
 
 jest.mock('../../../api/branches', () => ({
-  getBranches: jest.fn(() => Promise.resolve([])),
-  getPullRequests: jest.fn(() => Promise.resolve([]))
+  getBranches: jest.fn().mockResolvedValue([]),
+  getPullRequests: jest.fn().mockResolvedValue([])
 }));
 
 jest.mock('../../../api/ce', () => ({
-  getTasksForComponent: jest.fn(() => Promise.resolve({ queue: [] }))
+  getTasksForComponent: jest.fn().mockResolvedValue({ queue: [] })
 }));
 
 jest.mock('../../../api/components', () => ({
-  getComponentData: jest.fn(() => Promise.resolve({}))
+  getComponentData: jest.fn().mockResolvedValue({ analysisDate: '2018-07-30' })
 }));
 
 jest.mock('../../../api/nav', () => ({
-  getComponentNavigation: jest.fn(() =>
-    Promise.resolve({
-      breadcrumbs: [{ key: 'portfolioKey', name: 'portfolio', qualifier: 'VW' }],
-      key: 'portfolioKey'
-    })
-  )
+  getComponentNavigation: jest.fn().mockResolvedValue({
+    breadcrumbs: [{ key: 'portfolioKey', name: 'portfolio', qualifier: 'VW' }],
+    key: 'portfolioKey'
+  })
 }));
 
 // mock this, because some of its children are using redux store
@@ -90,14 +88,12 @@ it('changes component', () => {
 });
 
 it("loads branches for module's project", async () => {
-  (getComponentNavigation as jest.Mock<any>).mockImplementationOnce(() =>
-    Promise.resolve({
-      breadcrumbs: [
-        { key: 'projectKey', name: 'project', qualifier: 'TRK' },
-        { key: 'moduleKey', name: 'module', qualifier: 'BRC' }
-      ]
-    })
-  );
+  (getComponentNavigation as jest.Mock<any>).mockResolvedValueOnce({
+    breadcrumbs: [
+      { key: 'projectKey', name: 'project', qualifier: 'TRK' },
+      { key: 'moduleKey', name: 'module', qualifier: 'BRC' }
+    ]
+  });
 
   mount(
     <ComponentContainer fetchOrganizations={jest.fn()} location={{ query: { id: 'moduleKey' } }}>
@@ -149,9 +145,7 @@ it('updates branches on change', () => {
 });
 
 it('loads organization', async () => {
-  (getComponentData as jest.Mock<any>).mockImplementationOnce(() =>
-    Promise.resolve({ organization: 'org' })
-  );
+  (getComponentData as jest.Mock<any>).mockResolvedValueOnce({ organization: 'org' });
 
   const fetchOrganizations = jest.fn();
   mount(
@@ -166,9 +160,7 @@ it('loads organization', async () => {
 });
 
 it('fetches status', async () => {
-  (getComponentData as jest.Mock<any>).mockImplementationOnce(() =>
-    Promise.resolve({ organization: 'org' })
-  );
+  (getComponentData as jest.Mock<any>).mockResolvedValueOnce({ organization: 'org' });
 
   mount(
     <ComponentContainer fetchOrganizations={jest.fn()} location={{ query: { id: 'foo' } }}>
index b965dced2d66e16de4cb298c36ea4b64ce2fac9d..97457fe8dd61a7c63f44c97c3d9cd651c9f5a1c2 100644 (file)
@@ -51,7 +51,7 @@
 .alert-info {
   border-color: #bce8f1;
   background-color: #d9edf7;
-  color: #31708f;
+  color: #666666;
 }
 
 .alert-success {
index e77b22430da0848136f557d648547af32d8bf129..5a79c7f70e84fa977bf5f77f68d68212e3d96f54 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 .boxed-group {
-  margin-bottom: 20px;
+  margin-bottom: calc(2.5 * var(--gridSize));
   border: 1px solid var(--barBorderColor);
   border-radius: 2px;
   background-color: #fff;
index c493b82d22ca5ec6d868c9be39bf1ff95916c4f1..a18b282cc457fd4c39c1be485342ba130c9c6617 100644 (file)
 }
 
 .page-sidebar-fixed {
-  width: 30%;
   min-width: 300px;
   flex-shrink: 0;
   padding-left: 40px;
   }
 }
 
-.page-sidebar-sticky .page-sidebar-sticky-inner .search-navigator-facets-list {
-  width: 260px;
-  margin-left: calc(50vw - 640px + 290px - 260px - 37px);
-}
-
-@media (max-width: 1335px) {
-  .page-sidebar-sticky .page-sidebar-sticky-inner .search-navigator-facets-list {
-    margin-left: 20px;
-  }
-}
-
 .layout-page {
   display: flex;
   align-items: stretch;
index c0f7bd23abb49a4d0bc4f8c52a0a40e77a23db41..11bbc4bb9f7aa0e3ae61abc2613009b16c117906 100644 (file)
@@ -293,6 +293,11 @@ td.big-spacer-top {
   flex-direction: row;
 }
 
+.display-flex-column {
+  display: flex !important;
+  flex-direction: column;
+}
+
 .display-flex-center {
   display: flex !important;
   align-items: center;
index 66a4f2f0ba8ee3744f9beeafd9912c94c62094cb..aa5ab4086599622ae8c681ba35d9d7d3b7068f13 100644 (file)
   display: flex;
   clear: left;
   margin-bottom: calc(3 * var(--gridSize));
-  box-shadow: 0 1px 0 var(--barBorderColor);
+  border-bottom: 1px solid var(--barBorderColor);
+  font-size: var(--mediumFontSize);
 }
 
 .sonarcloud .flex-tabs > li > a {
+  position: relative;
   display: block;
+  top: 1px;
   height: 100%;
   width: 100%;
   box-sizing: border-box;
   color: var(--secondFontColor);
   font-weight: 600;
   cursor: pointer;
-  padding-bottom: var(--gridSize);
-  border-bottom: 2px solid transparent;
+  padding-bottom: calc(1.5 * var(--gridSize));
+  border-bottom: 3px solid transparent;
   transition: color 0.2s ease;
 }
 
index ea3be59fc5c1aeab586c38a9a8cbb76aba8fa138..4b55b604c22d68ead37b1c9092a30a09473d5f46 100644 (file)
@@ -61,6 +61,8 @@ export interface Breadcrumb {
 }
 
 export interface Component extends LightComponent {
+  almId?: string;
+  almRepoUrl?: string;
   analysisDate?: string;
   breadcrumbs: Breadcrumb[];
   configuration?: ComponentConfiguration;
index 9eb8edb79ed39a9c7ea61ce9abd3396f5225ff8e..2540d76707c3fc47f7597a9f47149dcfdcd0cb57 100644 (file)
@@ -57,7 +57,7 @@ export default function FacetsList(props: Props) {
     props.selectedProfile === undefined ||
     !props.query.activation;
   return (
-    <div className="search-navigator-facets-list">
+    <>
       <LanguageFacet
         onChange={props.onFilterChange}
         onToggle={props.onFacetToggle}
@@ -145,6 +145,6 @@ export default function FacetsList(props: Props) {
           />
         </>
       )}
-    </div>
+    </>
   );
 }
index 4b8edc255c383dd3eafc3f25c7c25812f630bffd..183875dc42591e3a7718383ae9b5c5f31aa28abf 100644 (file)
@@ -77,7 +77,7 @@ export default class Sidebar extends React.PureComponent {
 
   render() {
     return (
-      <div className="search-navigator-facets-list">
+      <div>
         <ProjectOverviewFacet
           onChange={this.changeMetric}
           selected={this.props.selectedMetric}
index 2a3aab950cb6a13b4d128b1a92f4733930d74d1a..f526bbb3a16647ee4bc795798e7fb2945d1b1e1e 100644 (file)
@@ -1,9 +1,7 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`should display two facets 1`] = `
-<div
-  className="search-navigator-facets-list"
->
+<div>
   <ProjectOverviewFacet
     onChange={[Function]}
     selected="duplicated_lines_density"
index d888dbbb50751b0d070e59437f15965724170baf..2953934dd4a36fd4ba13aa8c259e83f1c0158ae3 100644 (file)
@@ -77,7 +77,7 @@ export default class Sidebar extends React.PureComponent<Props> {
       (this.props.organization && this.props.organization.key);
 
     return (
-      <div className="search-navigator-facets-list">
+      <>
         <TypeFacet
           fetching={this.props.loadingFacets.types === true}
           loading={this.props.loading}
@@ -256,7 +256,7 @@ export default class Sidebar extends React.PureComponent<Props> {
             stats={facets.authors}
           />
         )}
-      </div>
+      </>
     );
   }
 }
index d54ea53f10b8dee490b0afd90b062b432778687a..68c8238c23c267255c9a3b2a12c890af28f21518 100644 (file)
  */
 import * as React from 'react';
 import * as PropTypes from 'prop-types';
-import OverviewApp from './OverviewApp';
+import { Helmet } from 'react-helmet';
 import EmptyOverview from './EmptyOverview';
+import OverviewApp from './OverviewApp';
+import SonarCloudEmptyOverview from './SonarCloudEmptyOverview';
+import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import { Component, BranchLike } from '../../../app/types';
 import { isShortLivingBranch } from '../../../helpers/branches';
-import { getShortLivingBranchUrl, getCodeUrl } from '../../../helpers/urls';
+import {
+  getShortLivingBranchUrl,
+  getCodeUrl,
+  getProjectUrl,
+  getBaseUrl,
+  getPathUrlAsString
+} from '../../../helpers/urls';
+import { isSonarCloud } from '../../../helpers/system';
 
 interface Props {
   branchLike?: BranchLike;
@@ -67,22 +77,43 @@ export default class App extends React.PureComponent<Props> {
       return null;
     }
 
-    if (!component.analysisDate) {
-      return (
-        <EmptyOverview
-          component={component.key}
-          hasBranches={branchLikes.length > 1}
-          showWarning={!this.props.isPending && !this.props.isInProgress}
-        />
-      );
-    }
-
     return (
-      <OverviewApp
-        branchLike={branchLike}
-        component={component}
-        onComponentChange={this.props.onComponentChange}
-      />
+      <>
+        {isSonarCloud() && (
+          <Helmet>
+            <link
+              href={getBaseUrl() + getPathUrlAsString(getProjectUrl(component.key))}
+              rel="canonical"
+            />
+          </Helmet>
+        )}
+        <Suggestions suggestions="overview" />
+
+        {!component.analysisDate &&
+          (isSonarCloud() ? (
+            <SonarCloudEmptyOverview
+              branchLike={branchLike}
+              branchLikes={branchLikes}
+              component={component}
+              hasAnalyses={this.props.isPending || this.props.isInProgress}
+              onComponentChange={this.props.onComponentChange}
+            />
+          ) : (
+            <EmptyOverview
+              branchLike={branchLike}
+              branchLikes={branchLikes}
+              component={component.key}
+              showWarning={!this.props.isPending && !this.props.isInProgress}
+            />
+          ))}
+        {component.analysisDate && (
+          <OverviewApp
+            branchLike={branchLike}
+            component={component}
+            onComponentChange={this.props.onComponentChange}
+          />
+        )}
+      </>
     );
   }
 }
index 9beed6e65b5cea94e3874e9d3e8ba5fb6bd68007..69c3d81ae27c91849bcd64688e4a660f389e80bc 100644 (file)
@@ -21,30 +21,39 @@ import * as React from 'react';
 import { Link } from 'react-router';
 import { FormattedMessage } from 'react-intl';
 import { translate } from '../../../helpers/l10n';
+import { BranchLike } from '../../../app/types';
+import { isBranch, isLongLivingBranch } from '../../../helpers/branches';
 
 interface Props {
+  branchLike?: BranchLike;
+  branchLikes: BranchLike[];
   component: string;
-  hasBranches?: boolean;
   showWarning?: boolean;
 }
 
-export default function EmptyOverview({ component, hasBranches, showWarning }: Props) {
-  const rawMessage = translate('provisioning.no_analysis.delete');
-  const head = rawMessage.substr(0, rawMessage.indexOf('{0}'));
-  const tail = rawMessage.substr(rawMessage.indexOf('{0}') + 3);
+export default function EmptyOverview({ branchLike, branchLikes, component, showWarning }: Props) {
+  const hasBranches = branchLikes.length > 1;
+  const hasBadConfig =
+    branchLikes.length > 2 ||
+    (branchLikes.length === 2 && branchLikes.some(branch => isLongLivingBranch(branch)));
+
+  const branchWarnMsg = hasBadConfig
+    ? translate('provisioning.no_analysis_on_main_branch.bad_configuration')
+    : translate('provisioning.no_analysis_on_main_branch');
 
   return (
     <div className="page page-limited">
       {showWarning && (
         <div className="big-spacer-bottom">
           <div className="alert alert-warning">
-            {hasBranches ? (
+            {hasBranches && isBranch(branchLike) ? (
               <FormattedMessage
-                defaultMessage={translate('provisioning.no_analysis_on_main_branch')}
-                id="provisioning.no_analysis_on_main_branch"
+                defaultMessage={branchWarnMsg}
+                id={branchWarnMsg}
                 values={{
-                  branch: (
-                    <div className="outline-badge text-baseline little-spacer-right">
+                  branchName: branchLike.name,
+                  branchType: (
+                    <div className="outline-badge text-baseline">
                       {translate('branches.main_branch')}
                     </div>
                   )
@@ -57,13 +66,19 @@ export default function EmptyOverview({ component, hasBranches, showWarning }: P
 
           {!hasBranches && (
             <div className="big-spacer-top">
-              {head}
-              <Link
-                className="text-danger"
-                to={{ pathname: '/project/deletion', query: { id: component } }}>
-                {translate('provisioning.no_analysis.delete_project')}
-              </Link>
-              {tail}
+              <FormattedMessage
+                defaultMessage={translate('provisioning.no_analysis.delete')}
+                id={'provisioning.no_analysis.delete'}
+                values={{
+                  link: (
+                    <Link
+                      className="text-danger"
+                      to={{ pathname: '/project/deletion', query: { id: component } }}>
+                      {translate('provisioning.no_analysis.delete_project')}
+                    </Link>
+                  )
+                }}
+              />
             </div>
           )}
         </div>
index 402725bdc7133eb345b817e895ea7c0b9c2ec51c..dcf365a66151f470355f1279d39defbfb90d2018 100644 (file)
 import * as React from 'react';
 import { uniq } from 'lodash';
 import { connect } from 'react-redux';
-import { Helmet } from 'react-helmet';
-import QualityGate from '../qualityGate/QualityGate';
 import ApplicationQualityGate from '../qualityGate/ApplicationQualityGate';
 import BugsAndVulnerabilities from '../main/BugsAndVulnerabilities';
 import CodeSmells from '../main/CodeSmells';
 import Coverage from '../main/Coverage';
 import Duplications from '../main/Duplications';
 import MetaContainer from '../meta/MetaContainer';
+import QualityGate from '../qualityGate/QualityGate';
 import throwGlobalError from '../../../app/utils/throwGlobalError';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import { getMeasuresAndMeta } from '../../../api/measures';
 import { getAllTimeMachineData, History } from '../../../api/time-machine';
 import { parseDate } from '../../../helpers/dates';
@@ -52,8 +50,6 @@ import { fetchMetrics } from '../../../store/rootActions';
 import { getMetrics } from '../../../store/rootReducer';
 import { BranchLike, Component, Metric } from '../../../app/types';
 import { translate } from '../../../helpers/l10n';
-import { getProjectUrl, getSonarCloudUrlAsString } from '../../../helpers/urls';
-import { isSonarCloud } from '../../../helpers/system';
 import '../styles.css';
 
 interface OwnProps {
@@ -246,14 +242,6 @@ export class OverviewApp extends React.PureComponent<Props, State> {
     return (
       <div className="page page-limited">
         <div className="overview page-with-sidebar">
-          <Suggestions suggestions="overview" />
-
-          {isSonarCloud() && (
-            <Helmet>
-              <link href={getSonarCloudUrlAsString(getProjectUrl(component.key))} rel="canonical" />
-            </Helmet>
-          )}
-
           {this.renderMain()}
 
           <div className="overview-sidebar page-sidebar-fixed">
diff --git a/server/sonar-web/src/main/js/apps/overview/components/SonarCloudEmptyOverview.tsx b/server/sonar-web/src/main/js/apps/overview/components/SonarCloudEmptyOverview.tsx
new file mode 100644 (file)
index 0000000..53dace6
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * 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 { FormattedMessage } from 'react-intl';
+import AnalyzeTutorial from '../../tutorials/analyzeProject/AnalyzeTutorial';
+import MetaContainer from '../meta/MetaContainer';
+import { BranchLike, Component, CurrentUser, isLoggedIn } from '../../../app/types';
+import { isLongLivingBranch, isBranch, isMainBranch } from '../../../helpers/branches';
+import { translate } from '../../../helpers/l10n';
+import { getCurrentUser } from '../../../store/rootReducer';
+import '../../../app/styles/sonarcloud.css';
+
+interface OwnProps {
+  branchLike?: BranchLike;
+  branchLikes: BranchLike[];
+  component: Component;
+  hasAnalyses?: boolean;
+  onComponentChange: (changes: {}) => void;
+}
+
+interface StateProps {
+  currentUser: CurrentUser;
+}
+
+type Props = OwnProps & StateProps;
+
+export function SonarCloudEmptyOverview({
+  branchLike,
+  branchLikes,
+  component,
+  currentUser,
+  hasAnalyses,
+  onComponentChange
+}: Props) {
+  const hasBranches = branchLikes.length > 1;
+  const hasBadBranchConfig =
+    branchLikes.length > 2 ||
+    (branchLikes.length === 2 && branchLikes.some(branch => isLongLivingBranch(branch)));
+  return (
+    <div className="page page-limited">
+      <div className="overview page-with-sidebar">
+        <div className="overview-main page-main sonarcloud">
+          {isLoggedIn(currentUser) && isMainBranch(branchLike) ? (
+            <>
+              {hasBranches && (
+                <WarningMessage
+                  branchLike={branchLike}
+                  message={
+                    hasBadBranchConfig
+                      ? translate('provisioning.no_analysis_on_main_branch.bad_configuration')
+                      : translate('provisioning.no_analysis_on_main_branch')
+                  }
+                />
+              )}
+              {!hasBranches &&
+                !hasAnalyses && <AnalyzeTutorial component={component} currentUser={currentUser} />}
+            </>
+          ) : (
+            <WarningMessage
+              branchLike={branchLike}
+              message={translate('provisioning.no_analysis_on_main_branch')}
+            />
+          )}
+        </div>
+
+        <div className="overview-sidebar page-sidebar-fixed">
+          <MetaContainer
+            branchLike={branchLike}
+            component={component}
+            onComponentChange={onComponentChange}
+          />
+        </div>
+      </div>
+    </div>
+  );
+}
+
+export function WarningMessage({
+  branchLike,
+  message
+}: {
+  branchLike?: BranchLike;
+  message: string;
+}) {
+  if (!isBranch(branchLike)) {
+    return null;
+  }
+  return (
+    <div className="alert alert-warning">
+      <FormattedMessage
+        defaultMessage={message}
+        id={message}
+        values={{
+          branchName: branchLike.name,
+          branchType: (
+            <div className="outline-badge text-baseline">{translate('branches.main_branch')}</div>
+          )
+        }}
+      />
+    </div>
+  );
+}
+
+const mapStateToProps = (state: any) => ({
+  currentUser: getCurrentUser(state)
+});
+
+export default connect<StateProps, {}, OwnProps>(mapStateToProps)(SonarCloudEmptyOverview);
index ac84c986293060133297f1ebd7a53e3473b89fd1..73eb99d9350dcdaed99aa2b923aae2a5482a2d9e 100644 (file)
 import * as React from 'react';
 import { mount, shallow } from 'enzyme';
 import App from '../App';
-import OverviewApp from '../OverviewApp';
-import EmptyOverview from '../EmptyOverview';
 import { BranchType, LongLivingBranch } from '../../../../app/types';
+import { isSonarCloud } from '../../../../helpers/system';
+
+jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
 
 const component = {
   key: 'foo',
@@ -34,13 +35,34 @@ const component = {
   version: '0.0.1'
 };
 
+beforeEach(() => {
+  (isSonarCloud as jest.Mock<any>).mockClear();
+  (isSonarCloud as jest.Mock<any>).mockReturnValue(false);
+});
+
 it('should render OverviewApp', () => {
-  expect(getWrapper().type()).toBe(OverviewApp);
+  expect(
+    getWrapper()
+      .find('Connect(OverviewApp)')
+      .exists()
+  ).toBeTruthy();
 });
 
 it('should render EmptyOverview', () => {
-  const output = getWrapper({ component: { key: 'foo' } });
-  expect(output.type()).toBe(EmptyOverview);
+  expect(
+    getWrapper({ component: { key: 'foo' } })
+      .find('EmptyOverview')
+      .exists()
+  ).toBeTruthy();
+});
+
+it('should render SonarCloudEmptyOverview', () => {
+  (isSonarCloud as jest.Mock<any>).mockReturnValue(true);
+  expect(
+    getWrapper({ component: { key: 'foo' } })
+      .find('Connect(SonarCloudEmptyOverview)')
+      .exists()
+  ).toBeTruthy();
 });
 
 it('redirects on Code page for files', () => {
index c2468df363814e06683587c7beaee567e0aad2df..6fd204472ab93bc9f13f82c3a270f02ada9048fb 100644 (file)
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import EmptyOverview from '../EmptyOverview';
+import { BranchType } from '../../../../app/types';
+
+const branch = { isMain: true, name: 'b', type: BranchType.LONG };
 
 it('renders', () => {
-  expect(shallow(<EmptyOverview component="abcd" showWarning={true} />)).toMatchSnapshot();
+  expect(
+    shallow(<EmptyOverview branchLikes={[]} component="abcd" showWarning={true} />)
+  ).toMatchSnapshot();
 });
 
 it('does not render warning', () => {
-  expect(shallow(<EmptyOverview component="abcd" showWarning={false} />)).toMatchSnapshot();
+  expect(
+    shallow(<EmptyOverview branchLikes={[]} component="abcd" showWarning={false} />)
+  ).toMatchSnapshot();
 });
 
 it('should render another message when there are branches', () => {
   expect(
-    shallow(<EmptyOverview component="abcd" hasBranches={true} showWarning={true} />)
+    shallow(
+      <EmptyOverview
+        branchLike={branch}
+        branchLikes={[branch, branch]}
+        component="abcd"
+        showWarning={true}
+      />
+    )
+  ).toMatchSnapshot();
+  expect(
+    shallow(
+      <EmptyOverview
+        branchLike={branch}
+        branchLikes={[branch, branch, branch]}
+        component="abcd"
+        showWarning={true}
+      />
+    )
   ).toMatchSnapshot();
 });
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/SonarCloudEmptyOverview-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/SonarCloudEmptyOverview-test.tsx
new file mode 100644 (file)
index 0000000..278fca0
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * 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 { SonarCloudEmptyOverview, WarningMessage } from '../SonarCloudEmptyOverview';
+import { BranchType } from '../../../../app/types';
+
+const branch = { isMain: true, name: 'b', type: BranchType.LONG };
+
+const component = {
+  key: 'foo',
+  analysisDate: '2016-01-01',
+  breadcrumbs: [],
+  name: 'Foo',
+  organization: 'org',
+  qualifier: 'TRK',
+  version: '0.0.1'
+};
+
+const LoggedInUser = {
+  isLoggedIn: true,
+  login: 'luke',
+  name: 'Skywalker'
+};
+
+it('renders correctly', () => {
+  expect(
+    shallow(
+      <SonarCloudEmptyOverview
+        branchLike={branch}
+        branchLikes={[branch]}
+        component={component}
+        currentUser={LoggedInUser}
+        onComponentChange={jest.fn()}
+      />
+    )
+  ).toMatchSnapshot();
+});
+
+it('should render another message when there are branches', () => {
+  expect(
+    shallow(
+      <SonarCloudEmptyOverview
+        branchLike={branch}
+        branchLikes={[branch, branch]}
+        component={component}
+        currentUser={LoggedInUser}
+        onComponentChange={jest.fn()}
+      />
+    )
+  ).toMatchSnapshot();
+  expect(
+    shallow(
+      <SonarCloudEmptyOverview
+        branchLike={branch}
+        branchLikes={[branch, branch, branch]}
+        component={component}
+        currentUser={LoggedInUser}
+        onComponentChange={jest.fn()}
+      />
+    )
+  ).toMatchSnapshot();
+});
+
+it('should not render the tutorial', () => {
+  expect(
+    shallow(
+      <SonarCloudEmptyOverview
+        branchLike={branch}
+        branchLikes={[branch]}
+        component={component}
+        currentUser={LoggedInUser}
+        hasAnalyses={true}
+        onComponentChange={jest.fn()}
+      />
+    )
+  ).toMatchSnapshot();
+});
+
+it('should render warning message', () => {
+  expect(shallow(<WarningMessage branchLike={branch} message="foo" />)).toMatchSnapshot();
+});
+
+it('should not render warning message', () => {
+  expect(
+    shallow(
+      <WarningMessage
+        branchLike={{
+          base: 'foo',
+          branch: 'bar',
+          key: '1',
+          title: 'PR bar'
+        }}
+        message="foo"
+      />
+    )
+      .find('FormattedMessage')
+      .exists()
+  ).toBeFalsy();
+});
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/Timeline-test.js b/server/sonar-web/src/main/js/apps/overview/components/__tests__/Timeline-test.js
deleted file mode 100644 (file)
index 0d9a0fd..0000000
+++ /dev/null
@@ -1,51 +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 React from 'react';
-import { shallow } from 'enzyme';
-import Timeline from '../Timeline';
-import { parseDate } from '../../../../helpers/dates';
-
-const range = parseDate('2017-05-01T00:00:00.000Z');
-const history = [
-  { date: parseDate('2017-04-08T00:00:00.000Z'), value: '29.6' },
-  { date: parseDate('2017-04-09T00:00:00.000Z'), value: '170.8' },
-  { date: parseDate('2017-05-08T00:00:00.000Z'), value: '360' },
-  { date: parseDate('2017-05-09T00:00:00.000Z'), value: '39' }
-];
-
-it('should render correctly with an "after" range', () => {
-  expect(shallow(<Timeline after={range} history={history} />)).toMatchSnapshot();
-});
-
-it('should render correctly with a "before" range', () => {
-  expect(shallow(<Timeline before={range} history={history} />)).toMatchSnapshot();
-});
-
-it('should have a correct domain with strings or numbers', () => {
-  const date = parseDate('2017-05-08T00:00:00.000Z');
-  const wrapper = shallow(<Timeline after={range} history={history} />);
-  expect(wrapper.find('LineChart').props().domain).toEqual([0, 360]);
-
-  wrapper.setProps({ history: [{ date, value: '360.33' }, { date, value: '39.54' }] });
-  expect(wrapper.find('LineChart').props().domain).toEqual([0, 360.33]);
-
-  wrapper.setProps({ history: [{ date, value: 360 }, { date, value: 39 }] });
-  expect(wrapper.find('LineChart').props().domain).toEqual([0, 360]);
-});
index 4d8cdf632481b617b83fb619a10c135f4a59abf6..cede657480c1dac9cc3348e5718ff22e2a31b239 100644 (file)
@@ -30,22 +30,29 @@ exports[`renders 1`] = `
     <div
       className="big-spacer-top"
     >
-      <Link
-        className="text-danger"
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        to={
+      <FormattedMessage
+        defaultMessage="provisioning.no_analysis.delete"
+        id="provisioning.no_analysis.delete"
+        values={
           Object {
-            "pathname": "/project/deletion",
-            "query": Object {
-              "id": "abcd",
-            },
+            "link": <Link
+              className="text-danger"
+              onlyActiveOnIndex={false}
+              style={Object {}}
+              to={
+                Object {
+                  "pathname": "/project/deletion",
+                  "query": Object {
+                    "id": "abcd",
+                  },
+                }
+              }
+            >
+              provisioning.no_analysis.delete_project
+            </Link>,
           }
         }
-      >
-        provisioning.no_analysis.delete_project
-      </Link>
-      ovisioning.no_analysis.delete
+      />
     </div>
   </div>
   <div>
@@ -74,8 +81,46 @@ exports[`should render another message when there are branches 1`] = `
         id="provisioning.no_analysis_on_main_branch"
         values={
           Object {
-            "branch": <div
-              className="outline-badge text-baseline little-spacer-right"
+            "branchName": "b",
+            "branchType": <div
+              className="outline-badge text-baseline"
+            >
+              branches.main_branch
+            </div>,
+          }
+        }
+      />
+    </div>
+  </div>
+  <div>
+    <h4>
+      key
+    </h4>
+    <code>
+      abcd
+    </code>
+  </div>
+</div>
+`;
+
+exports[`should render another message when there are branches 2`] = `
+<div
+  className="page page-limited"
+>
+  <div
+    className="big-spacer-bottom"
+  >
+    <div
+      className="alert alert-warning"
+    >
+      <FormattedMessage
+        defaultMessage="provisioning.no_analysis_on_main_branch.bad_configuration"
+        id="provisioning.no_analysis_on_main_branch.bad_configuration"
+        values={
+          Object {
+            "branchName": "b",
+            "branchType": <div
+              className="outline-badge text-baseline"
             >
               branches.main_branch
             </div>,
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/SonarCloudEmptyOverview-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/SonarCloudEmptyOverview-test.tsx.snap
new file mode 100644 (file)
index 0000000..1dd9c74
--- /dev/null
@@ -0,0 +1,229 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<div
+  className="page page-limited"
+>
+  <div
+    className="overview page-with-sidebar"
+  >
+    <div
+      className="overview-main page-main sonarcloud"
+    >
+      <React.Fragment>
+        <AnalyzeTutorial
+          component={
+            Object {
+              "analysisDate": "2016-01-01",
+              "breadcrumbs": Array [],
+              "key": "foo",
+              "name": "Foo",
+              "organization": "org",
+              "qualifier": "TRK",
+              "version": "0.0.1",
+            }
+          }
+          currentUser={
+            Object {
+              "isLoggedIn": true,
+              "login": "luke",
+              "name": "Skywalker",
+            }
+          }
+        />
+      </React.Fragment>
+    </div>
+    <div
+      className="overview-sidebar page-sidebar-fixed"
+    >
+      <Connect(Meta)
+        branchLike={
+          Object {
+            "isMain": true,
+            "name": "b",
+            "type": "LONG",
+          }
+        }
+        component={
+          Object {
+            "analysisDate": "2016-01-01",
+            "breadcrumbs": Array [],
+            "key": "foo",
+            "name": "Foo",
+            "organization": "org",
+            "qualifier": "TRK",
+            "version": "0.0.1",
+          }
+        }
+        onComponentChange={[MockFunction]}
+      />
+    </div>
+  </div>
+</div>
+`;
+
+exports[`should not render the tutorial 1`] = `
+<div
+  className="page page-limited"
+>
+  <div
+    className="overview page-with-sidebar"
+  >
+    <div
+      className="overview-main page-main sonarcloud"
+    >
+      <React.Fragment />
+    </div>
+    <div
+      className="overview-sidebar page-sidebar-fixed"
+    >
+      <Connect(Meta)
+        branchLike={
+          Object {
+            "isMain": true,
+            "name": "b",
+            "type": "LONG",
+          }
+        }
+        component={
+          Object {
+            "analysisDate": "2016-01-01",
+            "breadcrumbs": Array [],
+            "key": "foo",
+            "name": "Foo",
+            "organization": "org",
+            "qualifier": "TRK",
+            "version": "0.0.1",
+          }
+        }
+        onComponentChange={[MockFunction]}
+      />
+    </div>
+  </div>
+</div>
+`;
+
+exports[`should render another message when there are branches 1`] = `
+<div
+  className="page page-limited"
+>
+  <div
+    className="overview page-with-sidebar"
+  >
+    <div
+      className="overview-main page-main sonarcloud"
+    >
+      <React.Fragment>
+        <WarningMessage
+          branchLike={
+            Object {
+              "isMain": true,
+              "name": "b",
+              "type": "LONG",
+            }
+          }
+          message="provisioning.no_analysis_on_main_branch"
+        />
+      </React.Fragment>
+    </div>
+    <div
+      className="overview-sidebar page-sidebar-fixed"
+    >
+      <Connect(Meta)
+        branchLike={
+          Object {
+            "isMain": true,
+            "name": "b",
+            "type": "LONG",
+          }
+        }
+        component={
+          Object {
+            "analysisDate": "2016-01-01",
+            "breadcrumbs": Array [],
+            "key": "foo",
+            "name": "Foo",
+            "organization": "org",
+            "qualifier": "TRK",
+            "version": "0.0.1",
+          }
+        }
+        onComponentChange={[MockFunction]}
+      />
+    </div>
+  </div>
+</div>
+`;
+
+exports[`should render another message when there are branches 2`] = `
+<div
+  className="page page-limited"
+>
+  <div
+    className="overview page-with-sidebar"
+  >
+    <div
+      className="overview-main page-main sonarcloud"
+    >
+      <React.Fragment>
+        <WarningMessage
+          branchLike={
+            Object {
+              "isMain": true,
+              "name": "b",
+              "type": "LONG",
+            }
+          }
+          message="provisioning.no_analysis_on_main_branch.bad_configuration"
+        />
+      </React.Fragment>
+    </div>
+    <div
+      className="overview-sidebar page-sidebar-fixed"
+    >
+      <Connect(Meta)
+        branchLike={
+          Object {
+            "isMain": true,
+            "name": "b",
+            "type": "LONG",
+          }
+        }
+        component={
+          Object {
+            "analysisDate": "2016-01-01",
+            "breadcrumbs": Array [],
+            "key": "foo",
+            "name": "Foo",
+            "organization": "org",
+            "qualifier": "TRK",
+            "version": "0.0.1",
+          }
+        }
+        onComponentChange={[MockFunction]}
+      />
+    </div>
+  </div>
+</div>
+`;
+
+exports[`should render warning message 1`] = `
+<div
+  className="alert alert-warning"
+>
+  <FormattedMessage
+    defaultMessage="foo"
+    id="foo"
+    values={
+      Object {
+        "branchName": "b",
+        "branchType": <div
+          className="outline-badge text-baseline"
+        >
+          branches.main_branch
+        </div>,
+      }
+    }
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/Timeline-test.js.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/Timeline-test.js.snap
deleted file mode 100644 (file)
index 3c01c85..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly with a "before" range 1`] = `
-<LineChart
-  data={
-    Array [
-      Object {
-        "x": 0,
-        "y": 29.6,
-      },
-      Object {
-        "x": 1,
-        "y": 170.8,
-      },
-    ]
-  }
-  displayBackdrop={true}
-  displayPoints={false}
-  displayVerticalGrid={false}
-  domain={
-    Array [
-      0,
-      360,
-    ]
-  }
-  height={80}
-  padding={
-    Array [
-      0,
-      0,
-      0,
-      0,
-    ]
-  }
-/>
-`;
-
-exports[`should render correctly with an "after" range 1`] = `
-<LineChart
-  data={
-    Array [
-      Object {
-        "x": 0,
-        "y": 360,
-      },
-      Object {
-        "x": 1,
-        "y": 39,
-      },
-    ]
-  }
-  displayBackdrop={true}
-  displayPoints={false}
-  displayVerticalGrid={false}
-  domain={
-    Array [
-      0,
-      360,
-    ]
-  }
-  height={80}
-  padding={
-    Array [
-      0,
-      0,
-      0,
-      0,
-    ]
-  }
-/>
-`;
index 3e7749343a551438749a5a86e0ed03c717668809..64a0a9e4ea6f70c7cf1a55c6b78ed7276547e373 100644 (file)
@@ -58,8 +58,8 @@ interface OwnProps {
   branchLike?: BranchLike;
   component: Component;
   history?: History;
-  measures: MeasureEnhanced[];
-  metrics: { [key: string]: Metric };
+  measures?: MeasureEnhanced[];
+  metrics?: { [key: string]: Metric };
   onComponentChange: (changes: {}) => void;
 }
 
@@ -106,7 +106,7 @@ export class Meta extends React.PureComponent<Props> {
 
   render() {
     const { organizationsEnabled } = this.context;
-    const { branchLike, component, metrics, organization } = this.props;
+    const { branchLike, component, measures, metrics, organization } = this.props;
     const { qualifier, description, visibility } = component;
 
     const isProject = qualifier === 'TRK';
@@ -131,16 +131,20 @@ export class Meta extends React.PureComponent<Props> {
           {isProject && (
             <MetaTags component={component} onComponentChange={this.props.onComponentChange} />
           )}
-          <MetaSize branchLike={branchLike} component={component} measures={this.props.measures} />
+          {measures && (
+            <MetaSize branchLike={branchLike} component={component} measures={measures} />
+          )}
         </div>
 
-        <AnalysesList
-          branchLike={branchLike}
-          component={component}
-          history={this.props.history}
-          metrics={metrics}
-          qualifier={component.qualifier}
-        />
+        {metrics && (
+          <AnalysesList
+            branchLike={branchLike}
+            component={component}
+            history={this.props.history}
+            metrics={metrics}
+            qualifier={component.qualifier}
+          />
+        )}
 
         {this.renderQualityInfos()}
 
@@ -152,7 +156,8 @@ export class Meta extends React.PureComponent<Props> {
         </div>
 
         {!isPrivate &&
-          (isProject || isApp) && (
+          (isProject || isApp) &&
+          metrics && (
             <BadgesModal
               branchLike={branchLike}
               metrics={metrics}
index 7f14ac0420a6eaa2289aea2d0d1cdc98115fe7f0..92ed309cda0cae057ff3b2364259604fdf08c72d 100644 (file)
@@ -24,6 +24,7 @@
 .overview-main {
   background-color: var(--barBackgroundColor);
   transition: transform 0.5s ease, opacity 0.5s ease;
+  width: calc(100% - 300px);
 }
 
 .overview-sidebar {
diff --git a/server/sonar-web/src/main/js/apps/projects/create/AlmRepositoryItem.tsx b/server/sonar-web/src/main/js/apps/projects/create/AlmRepositoryItem.tsx
new file mode 100644 (file)
index 0000000..8e21b71
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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 Checkbox from '../../../components/controls/Checkbox';
+import { AlmRepository, IdentityProvider } from '../../../app/types';
+import { getBaseUrl } from '../../../helpers/urls';
+import { translate } from '../../../helpers/l10n';
+import CheckIcon from '../../../components/icons-components/CheckIcon';
+
+interface Props {
+  identityProvider: IdentityProvider;
+  repository: AlmRepository;
+  selected: boolean;
+  toggleRepository: (repository: AlmRepository) => void;
+}
+
+export default class AlmRepositoryItem extends React.PureComponent<Props> {
+  handleChange = () => {
+    this.props.toggleRepository(this.props.repository);
+  };
+
+  render() {
+    const { identityProvider, repository, selected } = this.props;
+    const alreadyImported = Boolean(repository.linkedProjectKey);
+    return (
+      <Checkbox
+        checked={selected || alreadyImported}
+        disabled={alreadyImported}
+        onCheck={this.handleChange}>
+        <img
+          alt={identityProvider.name}
+          className="spacer-left"
+          height={14}
+          src={getBaseUrl() + identityProvider.iconPath}
+          style={{ filter: alreadyImported ? 'invert(50%)' : 'invert(100%)' }}
+          width={14}
+        />
+        <span className="spacer-left">{this.props.repository.label}</span>
+        {alreadyImported && (
+          <span className="big-spacer-left">
+            <CheckIcon className="little-spacer-right" />
+            {translate('onboarding.create_project.already_imported')}
+          </span>
+        )}
+      </Checkbox>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/create/AutoProjectCreate.tsx b/server/sonar-web/src/main/js/apps/projects/create/AutoProjectCreate.tsx
new file mode 100644 (file)
index 0000000..078a54d
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * 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 AlmRepositoryItem from './AlmRepositoryItem';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import IdentityProviderLink from '../../../components/ui/IdentityProviderLink';
+import { getIdentityProviders } from '../../../api/users';
+import { getRepositories, provisionProject } from '../../../api/alm-integration';
+import { IdentityProvider, LoggedInUser, AlmRepository } from '../../../app/types';
+import { ProjectBase } from '../../../api/components';
+import { SubmitButton } from '../../../components/ui/buttons';
+import { translateWithParameters, translate } from '../../../helpers/l10n';
+
+interface Props {
+  currentUser: LoggedInUser;
+  onProjectCreate: (project: ProjectBase[]) => void;
+}
+
+interface State {
+  identityProviders: IdentityProvider[];
+  installationUrl?: string;
+  installed?: boolean;
+  loading: boolean;
+  repositories: AlmRepository[];
+  selectedRepositories: { [key: string]: AlmRepository | undefined };
+  submitting: boolean;
+}
+
+export default class AutoProjectCreate extends React.PureComponent<Props, State> {
+  mounted = false;
+  state: State = {
+    identityProviders: [],
+    loading: true,
+    repositories: [],
+    selectedRepositories: {},
+    submitting: false
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+    Promise.all([this.fetchIdentityProviders(), this.fetchRepositories()]).then(
+      this.stopLoading,
+      this.stopLoading
+    );
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  fetchIdentityProviders = () => {
+    return getIdentityProviders().then(
+      ({ identityProviders }) => {
+        if (this.mounted) {
+          this.setState({ identityProviders });
+        }
+      },
+      () => {
+        return Promise.resolve();
+      }
+    );
+  };
+
+  fetchRepositories = () => {
+    return getRepositories().then(({ almIntegration, repositories }) => {
+      if (this.mounted) {
+        this.setState({ ...almIntegration, repositories });
+      }
+    });
+  };
+
+  handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
+    event.preventDefault();
+
+    if (this.isValid()) {
+      const { selectedRepositories } = this.state;
+      this.setState({ submitting: true });
+      provisionProject({
+        repositories: Object.keys(selectedRepositories).filter(key =>
+          Boolean(selectedRepositories[key])
+        )
+      }).then(
+        ({ project }) => this.props.onProjectCreate([project]),
+        () => {
+          if (this.mounted) {
+            this.setState({ submitting: false });
+            this.reloadRepositories();
+          }
+        }
+      );
+    }
+  };
+
+  isValid = () => {
+    return this.state.repositories.some(repo =>
+      Boolean(this.state.selectedRepositories[repo.installationKey])
+    );
+  };
+
+  reloadRepositories = () => {
+    this.setState({ loading: true });
+    this.fetchRepositories().then(this.stopLoading, this.stopLoading);
+  };
+
+  stopLoading = () => {
+    if (this.mounted) {
+      this.setState({ loading: false });
+    }
+  };
+
+  toggleRepository = (repository: AlmRepository) => {
+    this.setState(({ selectedRepositories }) => ({
+      selectedRepositories: {
+        ...selectedRepositories,
+        [repository.installationKey]: selectedRepositories[repository.installationKey]
+          ? undefined
+          : repository
+      }
+    }));
+  };
+
+  render() {
+    if (this.state.loading) {
+      return <DeferredSpinner />;
+    }
+
+    const { currentUser } = this.props;
+    const identityProvider = this.state.identityProviders.find(
+      identityProvider => identityProvider.key === currentUser.externalProvider
+    );
+
+    if (!identityProvider) {
+      return null;
+    }
+
+    const { selectedRepositories, submitting } = this.state;
+
+    return (
+      <>
+        <p className="alert alert-info width-60 big-spacer-bottom">
+          {translateWithParameters(
+            'onboarding.create_project.beta_feature_x',
+            identityProvider.name
+          )}
+        </p>
+        {this.state.installed ? (
+          <form onSubmit={this.handleFormSubmit}>
+            <ul>
+              {this.state.repositories.map(repo => (
+                <li className="big-spacer-bottom" key={repo.installationKey}>
+                  <AlmRepositoryItem
+                    identityProvider={identityProvider}
+                    repository={repo}
+                    selected={Boolean(selectedRepositories[repo.installationKey])}
+                    toggleRepository={this.toggleRepository}
+                  />
+                </li>
+              ))}
+            </ul>
+            <SubmitButton disabled={!this.isValid() || submitting}>
+              {translate('onboarding.create_project.create_project')}
+            </SubmitButton>
+            <DeferredSpinner className="spacer-left" loading={submitting} />
+          </form>
+        ) : (
+          <div>
+            <p className="spacer-bottom">
+              {translateWithParameters(
+                'onboarding.create_project.install_app_x',
+                identityProvider.name
+              )}
+            </p>
+            <IdentityProviderLink
+              className="display-inline-block"
+              identityProvider={identityProvider}
+              small={true}
+              url={this.state.installationUrl}>
+              {translateWithParameters(
+                'onboarding.create_project.install_app_x.button',
+                identityProvider.name
+              )}
+            </IdentityProviderLink>
+          </div>
+        )}
+      </>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/create/CreateProjectPage.tsx b/server/sonar-web/src/main/js/apps/projects/create/CreateProjectPage.tsx
new file mode 100644 (file)
index 0000000..63224eb
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * 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 { connect } from 'react-redux';
+import { InjectedRouter } from 'react-router';
+import { Location } from 'history';
+import Helmet from 'react-helmet';
+import AutoProjectCreate from './AutoProjectCreate';
+import ManualProjectCreate from './ManualProjectCreate';
+import { serializeQuery, Query, parseQuery } from './utils';
+import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
+import { getCurrentUser } from '../../../store/rootReducer';
+import { skipOnboarding } from '../../../store/users/actions';
+import { CurrentUser, isLoggedIn } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
+import { ProjectBase } from '../../../api/components';
+import { getProjectUrl, getOrganizationUrl } from '../../../helpers/urls';
+import '../../../app/styles/sonarcloud.css';
+
+interface OwnProps {
+  location: Location;
+  onFinishOnboarding: () => void;
+  router: Pick<InjectedRouter, 'push' | 'replace'>;
+}
+
+interface StateProps {
+  currentUser: CurrentUser;
+}
+
+interface DispatchProps {
+  skipOnboarding: () => void;
+}
+
+type Props = OwnProps & StateProps & DispatchProps;
+
+export class CreateProjectPage extends React.PureComponent<Props> {
+  mounted = false;
+
+  constructor(props: Props) {
+    super(props);
+    if (!this.canAutoCreate(props)) {
+      this.updateQuery({ manual: true });
+    }
+  }
+
+  componentDidMount() {
+    this.mounted = true;
+    if (!isLoggedIn(this.props.currentUser)) {
+      handleRequiredAuthentication();
+    }
+    document.body.classList.add('white-page');
+    document.documentElement.classList.add('white-page');
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+    document.body.classList.remove('white-page');
+    document.documentElement.classList.remove('white-page');
+  }
+
+  handleProjectCreate = (projects: Pick<ProjectBase, 'key'>[], organization?: string) => {
+    if (projects.length > 1 && organization) {
+      this.props.router.push(getOrganizationUrl(organization) + '/projects');
+    } else if (projects.length === 1) {
+      this.props.router.push(getProjectUrl(projects[0].key));
+    }
+  };
+
+  canAutoCreate = ({ currentUser } = this.props) => {
+    return (
+      isLoggedIn(currentUser) &&
+      ['bitbucket', 'github'].includes(currentUser.externalProvider || '')
+    );
+  };
+
+  showAuto = (event: React.MouseEvent<HTMLAnchorElement>) => {
+    event.preventDefault();
+    this.updateQuery({ manual: false });
+  };
+
+  showManual = (event: React.MouseEvent<HTMLAnchorElement>) => {
+    event.preventDefault();
+    this.updateQuery({ manual: true });
+  };
+
+  updateQuery = (changes: Partial<Query>) => {
+    this.props.router.replace({
+      pathname: this.props.location.pathname,
+      query: serializeQuery({ ...parseQuery(this.props.location.query), ...changes })
+    });
+  };
+
+  render() {
+    const { currentUser } = this.props;
+    if (!isLoggedIn(currentUser)) {
+      return null;
+    }
+    const displayManual = parseQuery(this.props.location.query).manual;
+    const header = translate('onboarding.create_project.header');
+    return (
+      <>
+        <Helmet title={header} titleTemplate="%s" />
+        <div className="sonarcloud page page-limited">
+          <div className="page-header">
+            <h1 className="page-title">{header}</h1>
+          </div>
+
+          {this.canAutoCreate() && (
+            <ul className="flex-tabs">
+              <li>
+                <a
+                  className={classNames('js-auto', { selected: !displayManual })}
+                  href="#"
+                  onClick={this.showAuto}>
+                  {translate('onboarding.create_project.select_repositories')}
+                  <span
+                    className={classNames(
+                      'rounded alert alert-small spacer-left display-inline-block',
+                      {
+                        'alert-info': !displayManual,
+                        'alert-muted': displayManual
+                      }
+                    )}>
+                    {translate('beta')}
+                  </span>
+                </a>
+              </li>
+              <li>
+                <a
+                  className={classNames('js-manual', { selected: displayManual })}
+                  href="#"
+                  onClick={this.showManual}>
+                  {translate('onboarding.create_project.create_manually')}
+                </a>
+              </li>
+            </ul>
+          )}
+
+          {displayManual || !this.canAutoCreate() ? (
+            <ManualProjectCreate
+              currentUser={currentUser}
+              onProjectCreate={this.handleProjectCreate}
+            />
+          ) : (
+            <AutoProjectCreate
+              currentUser={currentUser}
+              onProjectCreate={this.handleProjectCreate}
+            />
+          )}
+        </div>
+      </>
+    );
+  }
+}
+
+const mapStateToProps = (state: any): StateProps => {
+  return {
+    currentUser: getCurrentUser(state)
+  };
+};
+
+const mapDispatchToProps: DispatchProps = { skipOnboarding };
+
+export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)(
+  CreateProjectPage
+);
diff --git a/server/sonar-web/src/main/js/apps/projects/create/ManualProjectCreate.tsx b/server/sonar-web/src/main/js/apps/projects/create/ManualProjectCreate.tsx
new file mode 100644 (file)
index 0000000..041b09b
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * 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 { sortBy } from 'lodash';
+import { connect } from 'react-redux';
+import CreateOrganizationForm from '../../account/organizations/CreateOrganizationForm';
+import Select from '../../../components/controls/Select';
+import { Button, SubmitButton } from '../../../components/ui/buttons';
+import { LoggedInUser, Organization } from '../../../app/types';
+import { fetchMyOrganizations } from '../../account/organizations/actions';
+import { getMyOrganizations } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
+import { createProject, ProjectBase } from '../../../api/components';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+
+interface StateProps {
+  userOrganizations: Organization[];
+}
+
+interface DispatchProps {
+  fetchMyOrganizations: () => Promise<void>;
+}
+
+interface OwnProps {
+  currentUser: LoggedInUser;
+  onProjectCreate: (project: ProjectBase[]) => void;
+}
+
+type Props = OwnProps & StateProps & DispatchProps;
+
+interface State {
+  createOrganizationModal: boolean;
+  projectName: string;
+  projectKey: string;
+  selectedOrganization: string;
+  submitting: boolean;
+}
+
+export class ManualProjectCreate extends React.PureComponent<Props, State> {
+  mounted = false;
+
+  constructor(props: Props) {
+    super(props);
+    this.state = {
+      createOrganizationModal: false,
+      projectName: '',
+      projectKey: '',
+      selectedOrganization:
+        props.userOrganizations.length === 1 ? props.userOrganizations[0].key : '',
+      submitting: false
+    };
+  }
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  closeCreateOrganization = () => {
+    this.setState({ createOrganizationModal: false });
+  };
+
+  handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
+    event.preventDefault();
+
+    if (this.isValid()) {
+      const { projectKey, projectName, selectedOrganization } = this.state;
+      this.setState({ submitting: true });
+      createProject({
+        project: projectKey,
+        name: projectName,
+        organization: selectedOrganization
+      }).then(
+        ({ project }) => this.props.onProjectCreate([project]),
+        () => {
+          if (this.mounted) {
+            this.setState({ submitting: false });
+          }
+        }
+      );
+    }
+  };
+
+  handleOrganizationSelect = ({ value }: { value: string }) => {
+    this.setState({ selectedOrganization: value });
+  };
+
+  handleProjectNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    this.setState({ projectName: event.currentTarget.value });
+  };
+
+  handleProjectKeyChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    this.setState({ projectKey: event.currentTarget.value });
+  };
+
+  isValid = () => {
+    const { projectKey, projectName, selectedOrganization } = this.state;
+    return Boolean(projectKey && projectName && selectedOrganization);
+  };
+
+  onCreateOrganization = (organization: { key: string }) => {
+    this.props.fetchMyOrganizations().then(
+      () => {
+        this.handleOrganizationSelect({ value: organization.key });
+        this.closeCreateOrganization();
+      },
+      () => {
+        this.closeCreateOrganization();
+      }
+    );
+  };
+
+  showCreateOrganization = () => {
+    this.setState({ createOrganizationModal: true });
+  };
+
+  render() {
+    const { submitting } = this.state;
+    return (
+      <>
+        <form onSubmit={this.handleFormSubmit}>
+          <div className="form-field">
+            <label htmlFor="select-organization">
+              {translate('onboarding.create_project.organization')}
+              <em className="mandatory">*</em>
+            </label>
+            <Select
+              autoFocus={true}
+              className="input-super-large"
+              clearable={false}
+              id="select-organization"
+              onChange={this.handleOrganizationSelect}
+              options={sortBy(this.props.userOrganizations, o => o.name.toLowerCase()).map(
+                organization => ({
+                  label: organization.name,
+                  value: organization.key
+                })
+              )}
+              required={true}
+              value={this.state.selectedOrganization}
+            />
+            <Button
+              className="button-link big-spacer-left js-new-org"
+              onClick={this.showCreateOrganization}>
+              {translate('onboarding.create_project.create_new_org')}
+            </Button>
+          </div>
+          <div className="form-field">
+            <label htmlFor="project-name">
+              {translate('onboarding.create_project.project_name')}
+              <em className="mandatory">*</em>
+            </label>
+            <input
+              className="input-super-large"
+              id="project-name"
+              maxLength={400}
+              minLength={1}
+              onChange={this.handleProjectNameChange}
+              required={true}
+              type="text"
+              value={this.state.projectName}
+            />
+          </div>
+          <div className="form-field">
+            <label htmlFor="project-key">
+              {translate('onboarding.create_project.project_key')}
+              <em className="mandatory">*</em>
+            </label>
+            <input
+              className="input-super-large"
+              id="project-key"
+              maxLength={400}
+              minLength={1}
+              onChange={this.handleProjectKeyChange}
+              required={true}
+              type="text"
+              value={this.state.projectKey}
+            />
+          </div>
+          <SubmitButton disabled={!this.isValid() || submitting}>
+            {translate('onboarding.create_project.create_project')}
+          </SubmitButton>
+          <DeferredSpinner className="spacer-left" loading={submitting} />
+        </form>
+        {this.state.createOrganizationModal && (
+          <CreateOrganizationForm
+            onClose={this.closeCreateOrganization}
+            onCreate={this.onCreateOrganization}
+          />
+        )}
+      </>
+    );
+  }
+}
+
+const mapDispatchToProps = ({
+  fetchMyOrganizations
+} as any) as DispatchProps;
+
+const mapStateToProps = (state: any): StateProps => {
+  return {
+    userOrganizations: getMyOrganizations(state)
+  };
+};
+export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)(
+  ManualProjectCreate
+);
diff --git a/server/sonar-web/src/main/js/apps/projects/create/__tests__/AlmRepositoryItem-test.tsx b/server/sonar-web/src/main/js/apps/projects/create/__tests__/AlmRepositoryItem-test.tsx
new file mode 100644 (file)
index 0000000..72b25cb
--- /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 { shallow } from 'enzyme';
+import AlmRepositoryItem from '../AlmRepositoryItem';
+
+const identityProviders = {
+  backgroundColor: 'blue',
+  iconPath: 'icon/path',
+  key: 'foo',
+  name: 'Foo Provider'
+};
+
+const repositories = [
+  {
+    label: 'Cool Project',
+    installationKey: 'github/cool',
+    linkedProjectKey: 'proj_cool',
+    linkedProjectName: 'Proj Cool'
+  },
+  {
+    label: 'Awesome Project',
+    installationKey: 'github/awesome'
+  }
+];
+
+it('should render correctly', () => {
+  expect(getWrapper()).toMatchSnapshot();
+});
+
+it('should render selected', () => {
+  expect(getWrapper({ selected: true })).toMatchSnapshot();
+});
+
+it('should render disabled', () => {
+  expect(getWrapper({ repository: repositories[0] })).toMatchSnapshot();
+});
+
+function getWrapper(props = {}) {
+  return shallow(
+    <AlmRepositoryItem
+      identityProvider={identityProviders}
+      repository={repositories[1]}
+      selected={false}
+      toggleRepository={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/create/__tests__/AutoProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/projects/create/__tests__/AutoProjectCreate-test.tsx
new file mode 100644 (file)
index 0000000..bfe9543
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * 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 AutoProjectCreate from '../AutoProjectCreate';
+import { getIdentityProviders } from '../../../../api/users';
+import { getRepositories } from '../../../../api/alm-integration';
+import { LoggedInUser } from '../../../../app/types';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+
+jest.mock('../../../../api/users', () => ({
+  getIdentityProviders: jest.fn().mockResolvedValue({
+    identityProviders: [
+      {
+        backgroundColor: 'blue',
+        iconPath: 'icon/path',
+        key: 'foo',
+        name: 'Foo Provider'
+      }
+    ]
+  })
+}));
+
+jest.mock('../../../../api/alm-integration', () => ({
+  getRepositories: jest.fn().mockResolvedValue({
+    almIntegration: {
+      installationUrl: 'https://alm.foo.com/install',
+      installed: false
+    },
+    repositories: []
+  }),
+  provisionProject: jest.fn().mockResolvedValue({ projects: [] })
+}));
+
+const user: LoggedInUser = { isLoggedIn: true, login: 'foo', name: 'Foo', externalProvider: 'foo' };
+const repositories = [
+  {
+    label: 'Cool Project',
+    installationKey: 'github/cool',
+    linkedProjectKey: 'proj_cool',
+    linkedProjectName: 'Proj Cool'
+  },
+  {
+    label: 'Awesome Project',
+    installationKey: 'github/awesome'
+  }
+];
+
+beforeEach(() => {
+  (getIdentityProviders as jest.Mock<any>).mockClear();
+  (getRepositories as jest.Mock<any>).mockClear();
+});
+
+it('should display the provider app install button', async () => {
+  const wrapper = getWrapper();
+  expect(wrapper).toMatchSnapshot();
+  expect(getIdentityProviders).toHaveBeenCalled();
+  expect(getRepositories).toHaveBeenCalled();
+
+  await waitAndUpdate(wrapper);
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('should display the list of repositories', async () => {
+  (getRepositories as jest.Mock<any>).mockResolvedValue({
+    almIntegration: {
+      installationUrl: 'https://alm.foo.com/install',
+      installed: true
+    },
+    repositories
+  });
+  const wrapper = getWrapper();
+  await waitAndUpdate(wrapper);
+  expect(wrapper).toMatchSnapshot();
+});
+
+function getWrapper(props = {}) {
+  return shallow(<AutoProjectCreate currentUser={user} onProjectCreate={jest.fn()} {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/create/__tests__/CreateProjectPage-test.tsx b/server/sonar-web/src/main/js/apps/projects/create/__tests__/CreateProjectPage-test.tsx
new file mode 100644 (file)
index 0000000..6f995af
--- /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 { shallow } from 'enzyme';
+import { Location } from 'history';
+import { CreateProjectPage } from '../CreateProjectPage';
+import { LoggedInUser } from '../../../../app/types';
+import { click } from '../../../../helpers/testUtils';
+
+const user: LoggedInUser = {
+  externalProvider: 'github',
+  isLoggedIn: true,
+  login: 'foo',
+  name: 'Foo'
+};
+
+it('should render correctly', () => {
+  expect(getWrapper()).toMatchSnapshot();
+});
+
+it('should render with Manual creation only', () => {
+  expect(getWrapper({ currentUser: { ...user, externalProvider: 'microsoft' } })).toMatchSnapshot();
+});
+
+it('should switch tabs', () => {
+  const replace = jest.fn();
+  const wrapper = getWrapper({ router: { replace } });
+  replace.mockImplementation(location => {
+    wrapper.setProps({ location }).update();
+  });
+
+  click(wrapper.find('.js-manual'));
+  expect(wrapper.find('Connect(ManualProjectCreate)').exists()).toBeTruthy();
+  click(wrapper.find('.js-auto'));
+  expect(wrapper.find('AutoProjectCreate').exists()).toBeTruthy();
+});
+
+function getWrapper(props = {}) {
+  return shallow(
+    <CreateProjectPage
+      currentUser={user}
+      location={{ pathname: 'foo', query: { manual: 'false' } } as Location}
+      onFinishOnboarding={jest.fn()}
+      router={{ push: jest.fn(), replace: jest.fn() }}
+      skipOnboarding={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/create/__tests__/ManualProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/projects/create/__tests__/ManualProjectCreate-test.tsx
new file mode 100644 (file)
index 0000000..b79b4e4
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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 { ManualProjectCreate } from '../ManualProjectCreate';
+import { change, click, submit, waitAndUpdate } from '../../../../helpers/testUtils';
+import { createProject } from '../../../../api/components';
+
+jest.mock('../../../../api/components', () => ({
+  createProject: jest.fn().mockResolvedValue({ project: { key: 'bar', name: 'Bar' } })
+}));
+
+beforeEach(() => {
+  (createProject as jest.Mock<any>).mockClear();
+});
+
+it('should render correctly', () => {
+  expect(getWrapper()).toMatchSnapshot();
+});
+
+it('should allow to create a new org', async () => {
+  const fetchMyOrganizations = jest.fn().mockResolvedValueOnce([]);
+  const wrapper = getWrapper({ fetchMyOrganizations });
+
+  click(wrapper.find('.js-new-org'));
+  const createForm = wrapper.find('Connect(CreateOrganizationForm)');
+  expect(createForm.exists()).toBeTruthy();
+
+  createForm.prop<Function>('onCreate')({ key: 'baz' });
+  expect(fetchMyOrganizations).toHaveBeenCalled();
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state('selectedOrganization')).toBe('baz');
+});
+
+it('should correctly create a project', async () => {
+  const onProjectCreate = jest.fn();
+  const wrapper = getWrapper({ onProjectCreate });
+  wrapper.find('Select').prop<Function>('onChange')({ value: 'foo' });
+  change(wrapper.find('#project-name'), 'Bar');
+  expect(wrapper.find('SubmitButton')).toMatchSnapshot();
+
+  change(wrapper.find('#project-key'), 'bar');
+  expect(wrapper.find('SubmitButton')).toMatchSnapshot();
+
+  submit(wrapper.find('form'));
+  expect(createProject).toBeCalledWith({ project: 'bar', name: 'Bar', organization: 'foo' });
+
+  await waitAndUpdate(wrapper);
+  expect(onProjectCreate).toBeCalledWith([{ key: 'bar', name: 'Bar' }]);
+});
+
+function getWrapper(props = {}) {
+  return shallow(
+    <ManualProjectCreate
+      currentUser={{ isLoggedIn: true, login: 'foo', name: 'Foo' }}
+      fetchMyOrganizations={jest.fn()}
+      onProjectCreate={jest.fn()}
+      userOrganizations={[{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }]}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/AlmRepositoryItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/AlmRepositoryItem-test.tsx.snap
new file mode 100644 (file)
index 0000000..ad71bfc
--- /dev/null
@@ -0,0 +1,90 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Checkbox
+  checked={false}
+  disabled={false}
+  onCheck={[Function]}
+  thirdState={false}
+>
+  <img
+    alt="Foo Provider"
+    className="spacer-left"
+    height={14}
+    src="icon/path"
+    style={
+      Object {
+        "filter": "invert(100%)",
+      }
+    }
+    width={14}
+  />
+  <span
+    className="spacer-left"
+  >
+    Awesome Project
+  </span>
+</Checkbox>
+`;
+
+exports[`should render disabled 1`] = `
+<Checkbox
+  checked={true}
+  disabled={true}
+  onCheck={[Function]}
+  thirdState={false}
+>
+  <img
+    alt="Foo Provider"
+    className="spacer-left"
+    height={14}
+    src="icon/path"
+    style={
+      Object {
+        "filter": "invert(50%)",
+      }
+    }
+    width={14}
+  />
+  <span
+    className="spacer-left"
+  >
+    Cool Project
+  </span>
+  <span
+    className="big-spacer-left"
+  >
+    <CheckIcon
+      className="little-spacer-right"
+    />
+    onboarding.create_project.already_imported
+  </span>
+</Checkbox>
+`;
+
+exports[`should render selected 1`] = `
+<Checkbox
+  checked={true}
+  disabled={false}
+  onCheck={[Function]}
+  thirdState={false}
+>
+  <img
+    alt="Foo Provider"
+    className="spacer-left"
+    height={14}
+    src="icon/path"
+    style={
+      Object {
+        "filter": "invert(100%)",
+      }
+    }
+    width={14}
+  />
+  <span
+    className="spacer-left"
+  >
+    Awesome Project
+  </span>
+</Checkbox>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap
new file mode 100644 (file)
index 0000000..6d6c039
--- /dev/null
@@ -0,0 +1,113 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display the list of repositories 1`] = `
+<React.Fragment>
+  <p
+    className="alert alert-info width-60 big-spacer-bottom"
+  >
+    onboarding.create_project.beta_feature_x.Foo Provider
+  </p>
+  <form
+    onSubmit={[Function]}
+  >
+    <ul>
+      <li
+        className="big-spacer-bottom"
+        key="github/cool"
+      >
+        <AlmRepositoryItem
+          identityProvider={
+            Object {
+              "backgroundColor": "blue",
+              "iconPath": "icon/path",
+              "key": "foo",
+              "name": "Foo Provider",
+            }
+          }
+          repository={
+            Object {
+              "installationKey": "github/cool",
+              "label": "Cool Project",
+              "linkedProjectKey": "proj_cool",
+              "linkedProjectName": "Proj Cool",
+            }
+          }
+          selected={false}
+          toggleRepository={[Function]}
+        />
+      </li>
+      <li
+        className="big-spacer-bottom"
+        key="github/awesome"
+      >
+        <AlmRepositoryItem
+          identityProvider={
+            Object {
+              "backgroundColor": "blue",
+              "iconPath": "icon/path",
+              "key": "foo",
+              "name": "Foo Provider",
+            }
+          }
+          repository={
+            Object {
+              "installationKey": "github/awesome",
+              "label": "Awesome Project",
+            }
+          }
+          selected={false}
+          toggleRepository={[Function]}
+        />
+      </li>
+    </ul>
+    <SubmitButton
+      disabled={true}
+    >
+      onboarding.create_project.create_project
+    </SubmitButton>
+    <DeferredSpinner
+      className="spacer-left"
+      loading={false}
+      timeout={100}
+    />
+  </form>
+</React.Fragment>
+`;
+
+exports[`should display the provider app install button 1`] = `
+<DeferredSpinner
+  timeout={100}
+/>
+`;
+
+exports[`should display the provider app install button 2`] = `
+<React.Fragment>
+  <p
+    className="alert alert-info width-60 big-spacer-bottom"
+  >
+    onboarding.create_project.beta_feature_x.Foo Provider
+  </p>
+  <div>
+    <p
+      className="spacer-bottom"
+    >
+      onboarding.create_project.install_app_x.Foo Provider
+    </p>
+    <IdentityProviderLink
+      className="display-inline-block"
+      identityProvider={
+        Object {
+          "backgroundColor": "blue",
+          "iconPath": "icon/path",
+          "key": "foo",
+          "name": "Foo Provider",
+        }
+      }
+      small={true}
+      url="https://alm.foo.com/install"
+    >
+      onboarding.create_project.install_app_x.button.Foo Provider
+    </IdentityProviderLink>
+  </div>
+</React.Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap
new file mode 100644 (file)
index 0000000..1880a16
--- /dev/null
@@ -0,0 +1,98 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<React.Fragment>
+  <HelmetWrapper
+    defer={true}
+    encodeSpecialCharacters={true}
+    title="onboarding.create_project.header"
+    titleTemplate="%s"
+  />
+  <div
+    className="sonarcloud page page-limited"
+  >
+    <div
+      className="page-header"
+    >
+      <h1
+        className="page-title"
+      >
+        onboarding.create_project.header
+      </h1>
+    </div>
+    <ul
+      className="flex-tabs"
+    >
+      <li>
+        <a
+          className="js-auto selected"
+          href="#"
+          onClick={[Function]}
+        >
+          onboarding.create_project.select_repositories
+          <span
+            className="rounded alert alert-small spacer-left display-inline-block alert-info"
+          >
+            beta
+          </span>
+        </a>
+      </li>
+      <li>
+        <a
+          className="js-manual"
+          href="#"
+          onClick={[Function]}
+        >
+          onboarding.create_project.create_manually
+        </a>
+      </li>
+    </ul>
+    <AutoProjectCreate
+      currentUser={
+        Object {
+          "externalProvider": "github",
+          "isLoggedIn": true,
+          "login": "foo",
+          "name": "Foo",
+        }
+      }
+      onProjectCreate={[Function]}
+    />
+  </div>
+</React.Fragment>
+`;
+
+exports[`should render with Manual creation only 1`] = `
+<React.Fragment>
+  <HelmetWrapper
+    defer={true}
+    encodeSpecialCharacters={true}
+    title="onboarding.create_project.header"
+    titleTemplate="%s"
+  />
+  <div
+    className="sonarcloud page page-limited"
+  >
+    <div
+      className="page-header"
+    >
+      <h1
+        className="page-title"
+      >
+        onboarding.create_project.header
+      </h1>
+    </div>
+    <Connect(ManualProjectCreate)
+      currentUser={
+        Object {
+          "externalProvider": "microsoft",
+          "isLoggedIn": true,
+          "login": "foo",
+          "name": "Foo",
+        }
+      }
+      onProjectCreate={[Function]}
+    />
+  </div>
+</React.Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap
new file mode 100644 (file)
index 0000000..fafb751
--- /dev/null
@@ -0,0 +1,125 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should correctly create a project 1`] = `
+<SubmitButton
+  disabled={true}
+>
+  onboarding.create_project.create_project
+</SubmitButton>
+`;
+
+exports[`should correctly create a project 2`] = `
+<SubmitButton
+  disabled={false}
+>
+  onboarding.create_project.create_project
+</SubmitButton>
+`;
+
+exports[`should render correctly 1`] = `
+<React.Fragment>
+  <form
+    onSubmit={[Function]}
+  >
+    <div
+      className="form-field"
+    >
+      <label
+        htmlFor="select-organization"
+      >
+        onboarding.create_project.organization
+        <em
+          className="mandatory"
+        >
+          *
+        </em>
+      </label>
+      <Select
+        autoFocus={true}
+        className="input-super-large"
+        clearable={false}
+        id="select-organization"
+        onChange={[Function]}
+        options={
+          Array [
+            Object {
+              "label": "Bar",
+              "value": "bar",
+            },
+            Object {
+              "label": "Foo",
+              "value": "foo",
+            },
+          ]
+        }
+        required={true}
+        value=""
+      />
+      <Button
+        className="button-link big-spacer-left js-new-org"
+        onClick={[Function]}
+      >
+        onboarding.create_project.create_new_org
+      </Button>
+    </div>
+    <div
+      className="form-field"
+    >
+      <label
+        htmlFor="project-name"
+      >
+        onboarding.create_project.project_name
+        <em
+          className="mandatory"
+        >
+          *
+        </em>
+      </label>
+      <input
+        className="input-super-large"
+        id="project-name"
+        maxLength={400}
+        minLength={1}
+        onChange={[Function]}
+        required={true}
+        type="text"
+        value=""
+      />
+    </div>
+    <div
+      className="form-field"
+    >
+      <label
+        htmlFor="project-key"
+      >
+        onboarding.create_project.project_key
+        <em
+          className="mandatory"
+        >
+          *
+        </em>
+      </label>
+      <input
+        className="input-super-large"
+        id="project-key"
+        maxLength={400}
+        minLength={1}
+        onChange={[Function]}
+        required={true}
+        type="text"
+        value=""
+      />
+    </div>
+    <SubmitButton
+      disabled={true}
+    >
+      onboarding.create_project.create_project
+    </SubmitButton>
+    <DeferredSpinner
+      className="spacer-left"
+      loading={false}
+      timeout={100}
+    />
+  </form>
+</React.Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projects/create/utils.ts b/server/sonar-web/src/main/js/apps/projects/create/utils.ts
new file mode 100644 (file)
index 0000000..f3528d0
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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 { memoize } from 'lodash';
+import {
+  cleanQuery,
+  RawQuery,
+  parseAsBoolean,
+  serializeOptionalBoolean
+} from '../../../helpers/query';
+
+export interface Query {
+  manual: boolean;
+}
+
+export const parseQuery = memoize((urlQuery: RawQuery): Query => {
+  return {
+    manual: parseAsBoolean(urlQuery['manual'], false)
+  };
+});
+
+export const serializeQuery = memoize((query: Query): RawQuery =>
+  cleanQuery({
+    manual: serializeOptionalBoolean(query.manual || undefined)
+  })
+);
index 873d933ce071c4b58d691a0127956af85713ac90..061627c206a8eb379b11bbf7eaea49a181c205dd 100644 (file)
@@ -22,6 +22,8 @@ import DefaultPageSelectorContainer from './components/DefaultPageSelectorContai
 import FavoriteProjectsContainer from './components/FavoriteProjectsContainer';
 import { PROJECTS_DEFAULT_FILTER, PROJECTS_ALL } from './utils';
 import { save } from '../../helpers/storage';
+import { isSonarCloud } from '../../helpers/system';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   { indexRoute: { component: DefaultPageSelectorContainer } },
@@ -32,7 +34,11 @@ const routes = [
       replace('/projects');
     }
   },
-  { path: 'favorite', component: FavoriteProjectsContainer }
-];
+  { path: 'favorite', component: FavoriteProjectsContainer },
+  isSonarCloud() && {
+    path: 'create',
+    component: lazyLoad(() => import('./create/CreateProjectPage'))
+  }
+].filter(Boolean);
 
 export default routes;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx b/server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx
deleted file mode 100644 (file)
index 6f19ade..0000000
+++ /dev/null
@@ -1,99 +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 { connect } from 'react-redux';
-import handleRequiredAuthentication from '../../app/utils/handleRequiredAuthentication';
-import Modal from '../../components/controls/Modal';
-import OnboardingPrivateIcon from '../../components/icons-components/OnboardingPrivateIcon';
-import OnboardingProjectIcon from '../../components/icons-components/OnboardingProjectIcon';
-import OnboardingTeamIcon from '../../components/icons-components/OnboardingTeamIcon';
-import { Button, ResetButtonLink } 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 {
-  onClose: () => 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.onClose}
-        shouldCloseOnOverlayClick={false}>
-        <div className="modal-simple-head text-center">
-          <h1>{translate('onboarding.header')}</h1>
-          <p className="spacer-top">{translate('onboarding.header.description')}</p>
-        </div>
-        <div className="modal-simple-body text-center onboarding-choices">
-          <Button className="onboarding-choice" onClick={this.props.onOpenProjectOnboarding}>
-            <OnboardingProjectIcon />
-            <span>{translate('onboarding.analyze_public_code')}</span>
-            <p className="note">{translate('onboarding.analyze_public_code.note')}</p>
-          </Button>
-          <Button className="onboarding-choice" onClick={this.props.onOpenOrganizationOnboarding}>
-            <OnboardingPrivateIcon />
-            <span>{translate('onboarding.analyze_private_code')}</span>
-            <p className="note">{translate('onboarding.analyze_private_code.note')}</p>
-          </Button>
-          <Button className="onboarding-choice" onClick={this.props.onOpenTeamOnboarding}>
-            <OnboardingTeamIcon />
-            <span>{translate('onboarding.contribute_existing_project')}</span>
-            <p className="note">{translate('onboarding.contribute_existing_project.note')}</p>
-          </Button>
-        </div>
-        <div className="modal-simple-footer text-center">
-          <ResetButtonLink className="spacer-bottom" onClick={this.props.onClose}>
-            {translate('not_now')}
-          </ResetButtonLink>
-          <p className="note">{translate('onboarding.footer')}</p>
-        </div>
-      </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
deleted file mode 100644 (file)
index 9bab367..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.
- */
-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 }}
-        onClose={jest.fn()}
-        onOpenOrganizationOnboarding={jest.fn()}
-        onOpenProjectOnboarding={jest.fn()}
-        onOpenTeamOnboarding={jest.fn()}
-      />
-    )
-  ).toMatchSnapshot();
-});
-
-it('should correctly open the different tutorials', () => {
-  const onClose = jest.fn();
-  const onOpenOrganizationOnboarding = jest.fn();
-  const onOpenProjectOnboarding = jest.fn();
-  const onOpenTeamOnboarding = jest.fn();
-  const push = jest.fn();
-  const wrapper = shallow(
-    <Onboarding
-      currentUser={{ isLoggedIn: true }}
-      onClose={onClose}
-      onOpenOrganizationOnboarding={onOpenOrganizationOnboarding}
-      onOpenProjectOnboarding={onOpenProjectOnboarding}
-      onOpenTeamOnboarding={onOpenTeamOnboarding}
-    />,
-    { context: { router: { push } } }
-  );
-
-  click(wrapper.find('ResetButtonLink'));
-  expect(onClose).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
deleted file mode 100644 (file)
index 18b2f16..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly 1`] = `
-<Modal
-  contentLabel="onboarding.header"
-  medium={true}
-  onRequestClose={[MockFunction]}
-  shouldCloseOnOverlayClick={false}
->
-  <div
-    className="modal-simple-head text-center"
-  >
-    <h1>
-      onboarding.header
-    </h1>
-    <p
-      className="spacer-top"
-    >
-      onboarding.header.description
-    </p>
-  </div>
-  <div
-    className="modal-simple-body text-center onboarding-choices"
-  >
-    <Button
-      className="onboarding-choice"
-      onClick={[MockFunction]}
-    >
-      <OnboardingProjectIcon />
-      <span>
-        onboarding.analyze_public_code
-      </span>
-      <p
-        className="note"
-      >
-        onboarding.analyze_public_code.note
-      </p>
-    </Button>
-    <Button
-      className="onboarding-choice"
-      onClick={[MockFunction]}
-    >
-      <OnboardingPrivateIcon />
-      <span>
-        onboarding.analyze_private_code
-      </span>
-      <p
-        className="note"
-      >
-        onboarding.analyze_private_code.note
-      </p>
-    </Button>
-    <Button
-      className="onboarding-choice"
-      onClick={[MockFunction]}
-    >
-      <OnboardingTeamIcon />
-      <span>
-        onboarding.contribute_existing_project
-      </span>
-      <p
-        className="note"
-      >
-        onboarding.contribute_existing_project.note
-      </p>
-    </Button>
-  </div>
-  <div
-    className="modal-simple-footer text-center"
-  >
-    <ResetButtonLink
-      className="spacer-bottom"
-      onClick={[MockFunction]}
-    >
-      not_now
-    </ResetButtonLink>
-    <p
-      className="note"
-    >
-      onboarding.footer
-    </p>
-  </div>
-</Modal>
-`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx
new file mode 100644 (file)
index 0000000..7ed7513
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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 AnalyzeTutorialSuggestion from './AnalyzeTutorialSuggestion';
+import ProjectAnalysisStep from '../components/ProjectAnalysisStep';
+import TokenStep from '../components/TokenStep';
+import { Component, LoggedInUser } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
+import '../styles.css';
+
+enum Steps {
+  ANALYSIS,
+  TOKEN
+}
+
+interface Props {
+  component: Component;
+  currentUser: LoggedInUser;
+}
+
+interface State {
+  step: Steps;
+  token?: string;
+}
+
+export default class AnalyzeTutorial extends React.PureComponent<Props, State> {
+  state: State = { step: Steps.TOKEN };
+
+  handleTokenDone = (token: string) => {
+    this.setState({ step: Steps.ANALYSIS, token });
+  };
+
+  handleTokenOpen = () => {
+    this.setState({ step: Steps.TOKEN });
+  };
+
+  render() {
+    const { component, currentUser } = this.props;
+    const { step, token } = this.state;
+    let stepNumber = 1;
+
+    const almId = component.almId || currentUser.externalProvider;
+    const showTutorial = almId !== 'microsoft';
+    return (
+      <>
+        <div className="page-header big-spacer-bottom">
+          <h1 className="page-title">{translate('onboarding.project_analysis.header')}</h1>
+          <p className="page-description">{translate('onboarding.project_analysis.description')}</p>
+        </div>
+
+        <AnalyzeTutorialSuggestion almId={almId} />
+
+        {showTutorial && (
+          <>
+            <TokenStep
+              currentUser={currentUser}
+              finished={Boolean(this.state.token)}
+              onContinue={this.handleTokenDone}
+              onOpen={this.handleTokenOpen}
+              open={step === Steps.TOKEN}
+              stepNumber={stepNumber++}
+            />
+
+            <ProjectAnalysisStep
+              component={component}
+              displayRowLayout={true}
+              open={step === Steps.ANALYSIS}
+              organization={component.organization}
+              stepNumber={stepNumber++}
+              token={token}
+            />
+          </>
+        )}
+      </>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx
new file mode 100644 (file)
index 0000000..a93c73b
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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 { translate } from '../../../helpers/l10n';
+import { getBaseUrl } from '../../../helpers/urls';
+
+export default function AnalyzeTutorialSuggestion({ almId }: { almId?: string }) {
+  if (almId && almId.startsWith('bitbucket')) {
+    return (
+      <div className="alert alert-info big-spacer-bottom">
+        <p>{translate('onboarding.project_analysis.commands_for_analysis')}</p>
+        <p>{translate('onboarding.project_analysis.suggestions.bitbucket')}</p>
+        <FormattedMessage
+          defaultMessage={translate('onboarding.project_analysis.simply_link')}
+          id={'onboarding.project_analysis.simply_link'}
+          values={{
+            link: (
+              <a
+                href={
+                  getBaseUrl() +
+                  '/documentation/integrations/bitbucketcloud#analyzing-with-pipelines'
+                }
+                target="_blank">
+                {translate('onboarding.project_analysis.guide_to_integrate_piplines')}
+              </a>
+            )
+          }}
+        />
+      </div>
+    );
+  } else if (almId === 'github') {
+    return (
+      <div className="alert alert-info big-spacer-bottom">
+        <p>{translate('onboarding.project_analysis.commands_for_analysis')} </p>
+        <p>{translate('onboarding.project_analysis.suggestions.github')}</p>
+        <FormattedMessage
+          defaultMessage={translate('onboarding.project_analysis.simply_link')}
+          id={'onboarding.project_analysis.simply_link'}
+          values={{
+            link: (
+              <a
+                href="https://docs.travis-ci.com/user/sonarcloud/"
+                rel="noopener noreferrer"
+                target="_blank">
+                {translate('onboarding.project_analysis.guide_to_integrate_travis')}
+              </a>
+            )
+          }}
+        />
+      </div>
+    );
+  } else if (almId === 'microsoft') {
+    return (
+      <p className="alert alert-info big-spacer-bottom">
+        <FormattedMessage
+          defaultMessage={translate('onboarding.project_analysis.simply_link')}
+          id={'onboarding.project_analysis.simply_link'}
+          values={{
+            link: (
+              <a href={getBaseUrl() + '/documentation/integrations/vsts'} target="_blank">
+                {translate('onboarding.project_analysis.guide_to_integrate_vsts')}
+              </a>
+            )
+          }}
+        />
+      </p>
+    );
+  }
+  return null;
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorial-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorial-test.tsx
new file mode 100644 (file)
index 0000000..39f0233
--- /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 { shallow } from 'enzyme';
+import AnalyzeTutorial from '../AnalyzeTutorial';
+import { LoggedInUser } from '../../../../app/types';
+
+const component = {
+  key: 'foo',
+  analysisDate: '2016-01-01',
+  breadcrumbs: [],
+  name: 'Foo',
+  organization: 'org',
+  qualifier: 'TRK',
+  version: '0.0.1'
+};
+
+const loggedInUser: LoggedInUser = {
+  isLoggedIn: true,
+  login: 'luke',
+  name: 'Skywalker'
+};
+
+it('renders correctly', () => {
+  expect(getWrapper()).toMatchSnapshot();
+});
+
+function getWrapper(props = {}) {
+  return shallow(<AnalyzeTutorial component={component} currentUser={loggedInUser} {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSuggestion-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSuggestion-test.tsx
new file mode 100644 (file)
index 0000000..bc4a6d7
--- /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 { shallow } from 'enzyme';
+import AnalyzeTutorialSuggestion from '../AnalyzeTutorialSuggestion';
+
+it('should not render', () => {
+  expect(shallow(<AnalyzeTutorialSuggestion almId={undefined} />).type()).toBeNull();
+});
+
+it('renders bitbucket suggestions correctly', () => {
+  expect(shallow(<AnalyzeTutorialSuggestion almId="bitbucket" />)).toMatchSnapshot();
+});
+
+it('renders github suggestions correctly', () => {
+  expect(shallow(<AnalyzeTutorialSuggestion almId="github" />)).toMatchSnapshot();
+});
+
+it('renders vsts suggestions correctly', () => {
+  expect(shallow(<AnalyzeTutorialSuggestion almId="microsoft" />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorial-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorial-test.tsx.snap
new file mode 100644 (file)
index 0000000..af09b97
--- /dev/null
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<React.Fragment>
+  <div
+    className="page-header big-spacer-bottom"
+  >
+    <h1
+      className="page-title"
+    >
+      onboarding.project_analysis.header
+    </h1>
+    <p
+      className="page-description"
+    >
+      onboarding.project_analysis.description
+    </p>
+  </div>
+  <AnalyzeTutorialSuggestion />
+  <React.Fragment>
+    <TokenStep
+      currentUser={
+        Object {
+          "isLoggedIn": true,
+          "login": "luke",
+          "name": "Skywalker",
+        }
+      }
+      finished={false}
+      onContinue={[Function]}
+      onOpen={[Function]}
+      open={true}
+      stepNumber={1}
+    />
+    <ProjectAnalysisStep
+      component={
+        Object {
+          "analysisDate": "2016-01-01",
+          "breadcrumbs": Array [],
+          "key": "foo",
+          "name": "Foo",
+          "organization": "org",
+          "qualifier": "TRK",
+          "version": "0.0.1",
+        }
+      }
+      displayRowLayout={true}
+      open={false}
+      organization="org"
+      stepNumber={2}
+    />
+  </React.Fragment>
+</React.Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorialSuggestion-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorialSuggestion-test.tsx.snap
new file mode 100644 (file)
index 0000000..b682fbd
--- /dev/null
@@ -0,0 +1,78 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders bitbucket suggestions correctly 1`] = `
+<div
+  className="alert alert-info big-spacer-bottom"
+>
+  <p>
+    onboarding.project_analysis.commands_for_analysis
+  </p>
+  <p>
+    onboarding.project_analysis.suggestions.bitbucket
+  </p>
+  <FormattedMessage
+    defaultMessage="onboarding.project_analysis.simply_link"
+    id="onboarding.project_analysis.simply_link"
+    values={
+      Object {
+        "link": <a
+          href="/documentation/integrations/bitbucketcloud#analyzing-with-pipelines"
+          target="_blank"
+        >
+          onboarding.project_analysis.guide_to_integrate_piplines
+        </a>,
+      }
+    }
+  />
+</div>
+`;
+
+exports[`renders github suggestions correctly 1`] = `
+<div
+  className="alert alert-info big-spacer-bottom"
+>
+  <p>
+    onboarding.project_analysis.commands_for_analysis
+     
+  </p>
+  <p>
+    onboarding.project_analysis.suggestions.github
+  </p>
+  <FormattedMessage
+    defaultMessage="onboarding.project_analysis.simply_link"
+    id="onboarding.project_analysis.simply_link"
+    values={
+      Object {
+        "link": <a
+          href="https://docs.travis-ci.com/user/sonarcloud/"
+          rel="noopener noreferrer"
+          target="_blank"
+        >
+          onboarding.project_analysis.guide_to_integrate_travis
+        </a>,
+      }
+    }
+  />
+</div>
+`;
+
+exports[`renders vsts suggestions correctly 1`] = `
+<p
+  className="alert alert-info big-spacer-bottom"
+>
+  <FormattedMessage
+    defaultMessage="onboarding.project_analysis.simply_link"
+    id="onboarding.project_analysis.simply_link"
+    values={
+      Object {
+        "link": <a
+          href="/documentation/integrations/vsts"
+          target="_blank"
+        >
+          onboarding.project_analysis.guide_to_integrate_vsts
+        </a>,
+      }
+    }
+  />
+</p>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/LanguageForm.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/LanguageForm.tsx
new file mode 100644 (file)
index 0000000..8762501
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * 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';
+import { Component } from '../../../app/types';
+import { isLanguageConfigured, LanguageConfig } from '../utils';
+
+interface Props {
+  component?: Component;
+  config?: LanguageConfig;
+  onDone: (config: LanguageConfig) => void;
+  onReset: () => void;
+  organization?: string;
+}
+
+type State = LanguageConfig;
+
+export default class LanguageForm extends React.PureComponent<Props, State> {
+  constructor(props: Props) {
+    super(props);
+    this.state = {
+      ...(this.props.config || {}),
+      projectKey: props.component ? props.component.key : undefined
+    };
+  }
+
+  handleChange = () => {
+    if (isLanguageConfigured(this.state)) {
+      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 = () => {
+    const { cFamilyCompiler, language, os } = this.state;
+    const needProjectKey =
+      language === 'dotnet' ||
+      (language === 'c-family' &&
+        (cFamilyCompiler === 'msvc' || (cFamilyCompiler === 'clang-gcc' && os !== undefined))) ||
+      (language === 'other' && os !== undefined);
+
+    if (!needProjectKey || this.props.component) {
+      return null;
+    }
+
+    return (
+      <NewProjectForm
+        onDelete={this.handleProjectKeyDelete}
+        onDone={this.handleProjectKeyDone}
+        organization={this.props.organization}
+        projectKey={this.state.projectKey}
+      />
+    );
+  };
+
+  render() {
+    const { cFamilyCompiler, language } = this.state;
+    const languages = isSonarCloud()
+      ? ['java', 'dotnet', 'c-family', 'other']
+      : ['java', 'dotnet', 'other'];
+
+    return (
+      <>
+        <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={language}
+          />
+        </div>
+        {language === 'java' && this.renderJavaBuild()}
+        {language === 'c-family' && this.renderCFamilyCompiler()}
+        {((language === 'c-family' && cFamilyCompiler === 'clang-gcc') || language === 'other') &&
+          this.renderOS()}
+        {this.renderProjectKey()}
+      </>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/NewOrganizationForm.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/NewOrganizationForm.tsx
new file mode 100644 (file)
index 0000000..5244898
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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/components/NewProjectForm.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/OrganizationStep.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/OrganizationStep.tsx
new file mode 100644 (file)
index 0000000..934965c
--- /dev/null
@@ -0,0 +1,273 @@
+/*
+ * 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={import(/* webpackMode: "eager" */ 'Docs/tooltips/organizations/organization.md')}
+            />
+          </span>
+        }
+      />
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/ProjectAnalysisStep.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/ProjectAnalysisStep.tsx
new file mode 100644 (file)
index 0000000..3a924f2
--- /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 Step from './Step';
+import LanguageForm from './LanguageForm';
+import AnalysisCommand from './commands/AnalysisCommand';
+import { translate } from '../../../helpers/l10n';
+import { Component } from '../../../app/types';
+import { LanguageConfig } from '../utils';
+
+interface Props {
+  component?: Component;
+  displayRowLayout?: boolean;
+  onFinish?: (projectKey?: string) => void;
+  onReset?: () => void;
+  open: boolean;
+  organization?: string;
+  stepNumber: number;
+  token?: string;
+}
+
+interface State {
+  config?: LanguageConfig;
+}
+
+export default class ProjectAnalysisStep extends React.PureComponent<Props, State> {
+  state: State = {};
+
+  getProjectKey = ({ config } = this.state, { component } = this.props) => {
+    return (component && component.key) || (config && config.projectKey);
+  };
+
+  handleLanguageSelect = (config: LanguageConfig) => {
+    this.setState({ config });
+    if (this.props.onFinish) {
+      const projectKey = config.language !== 'java' ? this.getProjectKey({ config }) : undefined;
+      this.props.onFinish(projectKey);
+    }
+  };
+
+  handleLanguageReset = () => {
+    this.setState({ config: undefined });
+    if (this.props.onReset) {
+      this.props.onReset();
+    }
+  };
+
+  renderForm = () => {
+    const languageComponent = (
+      <LanguageForm
+        component={this.props.component}
+        onDone={this.handleLanguageSelect}
+        onReset={this.handleLanguageReset}
+        organization={this.props.organization}
+      />
+    );
+    const analysisComponent = this.state.config && (
+      <AnalysisCommand
+        component={this.props.component}
+        languageConfig={this.state.config}
+        organization={this.props.organization}
+        small={true}
+        token={this.props.token}
+      />
+    );
+
+    if (this.props.displayRowLayout) {
+      return (
+        <div className="boxed-group-inner">
+          <div className="display-flex-column">
+            {languageComponent}
+            {analysisComponent && <div className="huge-spacer-top">{analysisComponent}</div>}
+          </div>
+        </div>
+      );
+    }
+
+    return (
+      <div className="boxed-group-inner">
+        <div className="flex-columns">
+          <div className="flex-column flex-column-half bordered-right">{languageComponent}</div>
+          <div className="flex-column flex-column-half">{analysisComponent}</div>
+        </div>
+      </div>
+    );
+  };
+
+  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/components/Step.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/TokenStep.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/__tests__/LanguageForm-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/LanguageForm-test.tsx
new file mode 100644 (file)
index 0000000..f767450
--- /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 LanguageForm from '../LanguageForm';
+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(<LanguageForm 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(<LanguageForm 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(<LanguageForm 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(<LanguageForm 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/components/__tests__/NewOrganizationForm-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__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/components/__tests__/NewProjectForm-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__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/components/__tests__/OrganizationStep-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__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/components/__tests__/Step-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__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/components/__tests__/TokenStep-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__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/components/__tests__/__snapshots__/LanguageForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/LanguageForm-test.tsx.snap
new file mode 100644 (file)
index 0000000..c653180
--- /dev/null
@@ -0,0 +1,687 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`selects c# 1`] = `
+<React.Fragment>
+  <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]}
+  />
+</React.Fragment>
+`;
+
+exports[`selects c-family 1`] = `
+<React.Fragment>
+  <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>
+</React.Fragment>
+`;
+
+exports[`selects c-family 2`] = `
+<React.Fragment>
+  <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]}
+  />
+</React.Fragment>
+`;
+
+exports[`selects c-family 3`] = `
+<React.Fragment>
+  <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>
+</React.Fragment>
+`;
+
+exports[`selects c-family 4`] = `
+<React.Fragment>
+  <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"
+  />
+</React.Fragment>
+`;
+
+exports[`selects java 1`] = `
+<React.Fragment>
+  <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>
+</React.Fragment>
+`;
+
+exports[`selects java 2`] = `
+<React.Fragment>
+  <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>
+</React.Fragment>
+`;
+
+exports[`selects java 3`] = `
+<React.Fragment>
+  <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>
+</React.Fragment>
+`;
+
+exports[`selects other 1`] = `
+<React.Fragment>
+  <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>
+</React.Fragment>
+`;
+
+exports[`selects other 2`] = `
+<React.Fragment>
+  <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]}
+  />
+</React.Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/__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/components/__tests__/__snapshots__/NewProjectForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/__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/components/__tests__/__snapshots__/OrganizationStep-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/OrganizationStep-test.tsx.snap
new file mode 100644 (file)
index 0000000..8ece74c
--- /dev/null
@@ -0,0 +1,286 @@
+// 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={Promise {}}
+        />
+      </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={Promise {}}
+            />
+          </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/components/__tests__/__snapshots__/Step-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/__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/components/__tests__/__snapshots__/TokenStep-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/__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/components/commands/AnalysisCommand.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/AnalysisCommand.tsx
new file mode 100644 (file)
index 0000000..3bcdb9b
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * 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 JavaMaven from './JavaMaven';
+import JavaGradle from './JavaGradle';
+import DotNet from './DotNet';
+import Msvc from './Msvc';
+import ClangGCC from './ClangGCC';
+import Other from './Other';
+import { getHostUrl } from '../../../../helpers/urls';
+import { Component } from '../../../../app/types';
+import { LanguageConfig } from '../../utils';
+
+interface Props {
+  component?: Component;
+  organization?: string;
+  languageConfig: LanguageConfig;
+  small?: boolean;
+  token?: string;
+}
+
+export default class AnalysisCommand extends React.PureComponent<Props> {
+  getProjectKey = ({ component, languageConfig } = this.props) => {
+    return (component && component.key) || languageConfig.projectKey;
+  };
+
+  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 { small, token } = this.props;
+    const projectKey = this.getProjectKey();
+    if (!projectKey || !token) {
+      return null;
+    }
+    return (
+      <DotNet
+        host={getHostUrl()}
+        organization={this.props.organization}
+        projectKey={projectKey}
+        small={small}
+        token={token}
+      />
+    );
+  };
+
+  renderCommandForMSVC = () => {
+    const { small, token } = this.props;
+    const projectKey = this.getProjectKey();
+    if (!projectKey || !token) {
+      return null;
+    }
+    return (
+      <Msvc
+        host={getHostUrl()}
+        organization={this.props.organization}
+        projectKey={projectKey}
+        small={small}
+        token={token}
+      />
+    );
+  };
+
+  renderCommandForClangGCC = () => {
+    const { languageConfig, small, token } = this.props;
+    const projectKey = this.getProjectKey();
+    if (!languageConfig || !projectKey || !languageConfig.os || !token) {
+      return null;
+    }
+    return (
+      <ClangGCC
+        host={getHostUrl()}
+        organization={this.props.organization}
+        os={languageConfig.os}
+        projectKey={projectKey}
+        small={small}
+        token={token}
+      />
+    );
+  };
+
+  renderCommandForOther = () => {
+    const { languageConfig, token } = this.props;
+    const projectKey = this.getProjectKey();
+    if (!languageConfig || !projectKey || !languageConfig.os || !token) {
+      return null;
+    }
+    return (
+      <Other
+        host={getHostUrl()}
+        organization={this.props.organization}
+        os={languageConfig.os}
+        projectKey={projectKey}
+        token={token}
+      />
+    );
+  };
+
+  render() {
+    const { languageConfig } = this.props;
+
+    if (languageConfig.language === 'java') {
+      return languageConfig.javaBuild === 'maven'
+        ? this.renderCommandForMaven()
+        : this.renderCommandForGradle();
+    } else if (languageConfig.language === 'dotnet') {
+      return this.renderCommandForDotNet();
+    } else if (languageConfig.language === 'c-family') {
+      return languageConfig.cFamilyCompiler === 'msvc'
+        ? this.renderCommandForMSVC()
+        : this.renderCommandForClangGCC();
+    } else {
+      return this.renderCommandForOther();
+    }
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/commands/BuildWrapper.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/commands/ClangGCC.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/ClangGCC.tsx
new file mode 100644 (file)
index 0000000..0afc978
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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;
+  small?: boolean;
+  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={props.small} 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/components/commands/DotNet.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/DotNet.tsx
new file mode 100644 (file)
index 0000000..12de244
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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;
+  small?: boolean;
+  token: string;
+}
+
+export default function DotNet(props: Props) {
+  const command1 = [
+    'SonarScanner.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 = ['SonarScanner.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={false} snippet={command2} />
+      <CodeSnippet isOneLine={props.small} 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/components/commands/JavaGradle.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaGradle.tsx
new file mode 100644 (file)
index 0000000..fa95c27
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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: props.projectKey
+            ? translate('onboarding.analysis.auto_refresh_after_analysis')
+            : translate('onboarding.analysis.browse_url_after_analysis')
+        }}
+      />
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaMaven.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaMaven.tsx
new file mode 100644 (file)
index 0000000..8ade1b4
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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: props.projectKey
+            ? translate('onboarding.analysis.auto_refresh_after_analysis')
+            : translate('onboarding.analysis.browse_url_after_analysis')
+        }}
+      />
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/commands/MSBuildScanner.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/commands/Msvc.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/Msvc.tsx
new file mode 100644 (file)
index 0000000..838da87
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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;
+  small?: boolean;
+  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={props.small} snippet={command2} />
+      <CodeSnippet isOneLine={props.small} 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/components/commands/Other.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/commands/SQScanner.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/commands/__tests__/AnalysisCommand-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/AnalysisCommand-test.tsx
new file mode 100644 (file)
index 0000000..3dc1615
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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 AnalysisCommand from '../AnalysisCommand';
+
+jest.mock('../../../../../helpers/system', () => ({
+  isSonarCloud: jest.fn().mockReturnValue(true)
+}));
+
+it('display java command', () => {
+  expect(
+    getWrapper({ languageConfig: { language: 'java', javaBuild: 'gradle' } })
+  ).toMatchSnapshot();
+  expect(
+    getWrapper({ languageConfig: { language: 'java', javaBuild: 'maven' } })
+  ).toMatchSnapshot();
+});
+
+it('display c# command', () => {
+  expect(
+    getWrapper({ languageConfig: { language: 'dotnet', projectKey: 'project-foo' } })
+  ).toMatchSnapshot();
+});
+
+it('display c-family command', () => {
+  expect(
+    getWrapper({
+      languageConfig: { language: 'c-family', cFamilyCompiler: 'msvc', projectKey: 'project-foo' }
+    })
+  ).toMatchSnapshot();
+  expect(
+    getWrapper({
+      languageConfig: {
+        language: 'c-family',
+        cFamilyCompiler: 'clang-gcc',
+        os: 'linux',
+        projectKey: 'project-foo'
+      }
+    })
+  ).toMatchSnapshot();
+});
+
+it('display others command', () => {
+  expect(
+    getWrapper({
+      languageConfig: { language: 'other', os: 'window', projectKey: 'project-foo' }
+    })
+  ).toMatchSnapshot();
+});
+
+function getWrapper(props = {}) {
+  return shallow(<AnalysisCommand languageConfig={{}} token="myToken" {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/BuildWrapper-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/commands/__tests__/ClangGCC-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/ClangGCC-test.tsx
new file mode 100644 (file)
index 0000000..8ffa75c
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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"
+        small={true}
+        token="token"
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/DotNet-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/DotNet-test.tsx
new file mode 100644 (file)
index 0000000..e95302c
--- /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 { 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"
+        small={true}
+        token="token"
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/JavaGradle-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/commands/__tests__/JavaMaven-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/commands/__tests__/MSBuildScanner-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/commands/__tests__/Msvc-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/Msvc-test.tsx
new file mode 100644 (file)
index 0000000..68ee47b
--- /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 { 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"
+        small={true}
+        token="token"
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/Other-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/commands/__tests__/SQScanner-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/commands/__tests__/__snapshots__/AnalysisCommand-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/AnalysisCommand-test.tsx.snap
new file mode 100644 (file)
index 0000000..f6a0972
--- /dev/null
@@ -0,0 +1,49 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`display c# command 1`] = `
+<DotNet
+  host="null"
+  projectKey="project-foo"
+  token="myToken"
+/>
+`;
+
+exports[`display c-family command 1`] = `
+<Msvc
+  host="null"
+  projectKey="project-foo"
+  token="myToken"
+/>
+`;
+
+exports[`display c-family command 2`] = `
+<ClangGCC
+  host="null"
+  os="linux"
+  projectKey="project-foo"
+  token="myToken"
+/>
+`;
+
+exports[`display java command 1`] = `
+<JavaGradle
+  host="null"
+  token="myToken"
+/>
+`;
+
+exports[`display java command 2`] = `
+<JavaMaven
+  host="null"
+  token="myToken"
+/>
+`;
+
+exports[`display others command 1`] = `
+<Other
+  host="null"
+  os="window"
+  projectKey="project-foo"
+  token="myToken"
+/>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap
new file mode 100644 (file)
index 0000000..6429352
--- /dev/null
@@ -0,0 +1,137 @@
+// 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
+    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
+    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/components/commands/__tests__/__snapshots__/DotNet-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/DotNet-test.tsx.snap
new file mode 100644 (file)
index 0000000..7f0e70d
--- /dev/null
@@ -0,0 +1,94 @@
+// 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 [
+        "SonarScanner.MSBuild.exe begin",
+        "/k:\\"projectKey\\"",
+        undefined,
+        "/d:sonar.host.url=\\"host\\"",
+        "/d:sonar.login=\\"token\\"",
+      ]
+    }
+  />
+  <CodeSnippet
+    isOneLine={false}
+    snippet="MsBuild.exe /t:Rebuild"
+  />
+  <CodeSnippet
+    snippet={
+      Array [
+        "SonarScanner.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 [
+        "SonarScanner.MSBuild.exe begin",
+        "/k:\\"projectKey\\"",
+        "/d:sonar.organization=\\"organization\\"",
+        "/d:sonar.host.url=\\"host\\"",
+        "/d:sonar.login=\\"token\\"",
+      ]
+    }
+  />
+  <CodeSnippet
+    isOneLine={false}
+    snippet="MsBuild.exe /t:Rebuild"
+  />
+  <CodeSnippet
+    isOneLine={true}
+    snippet={
+      Array [
+        "SonarScanner.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/components/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap
new file mode 100644 (file)
index 0000000..c5ff1eb
--- /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.auto_refresh_after_analysis",
+      }
+    }
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap
new file mode 100644 (file)
index 0000000..7993a39
--- /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.auto_refresh_after_analysis",
+      }
+    }
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/commands/__tests__/__snapshots__/Msvc-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/Msvc-test.tsx.snap
new file mode 100644 (file)
index 0000000..accb581
--- /dev/null
@@ -0,0 +1,103 @@
+// 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
+    snippet="build-wrapper-win-x86-64.exe --out-dir bw-output MsBuild.exe /t:Rebuild"
+  />
+  <CodeSnippet
+    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/components/commands/__tests__/__snapshots__/Other-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/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/components/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/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/createProjectOnboarding/AlmRepositoryItem.tsx b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AlmRepositoryItem.tsx
deleted file mode 100644 (file)
index 8e21b71..0000000
+++ /dev/null
@@ -1,65 +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 Checkbox from '../../../components/controls/Checkbox';
-import { AlmRepository, IdentityProvider } from '../../../app/types';
-import { getBaseUrl } from '../../../helpers/urls';
-import { translate } from '../../../helpers/l10n';
-import CheckIcon from '../../../components/icons-components/CheckIcon';
-
-interface Props {
-  identityProvider: IdentityProvider;
-  repository: AlmRepository;
-  selected: boolean;
-  toggleRepository: (repository: AlmRepository) => void;
-}
-
-export default class AlmRepositoryItem extends React.PureComponent<Props> {
-  handleChange = () => {
-    this.props.toggleRepository(this.props.repository);
-  };
-
-  render() {
-    const { identityProvider, repository, selected } = this.props;
-    const alreadyImported = Boolean(repository.linkedProjectKey);
-    return (
-      <Checkbox
-        checked={selected || alreadyImported}
-        disabled={alreadyImported}
-        onCheck={this.handleChange}>
-        <img
-          alt={identityProvider.name}
-          className="spacer-left"
-          height={14}
-          src={getBaseUrl() + identityProvider.iconPath}
-          style={{ filter: alreadyImported ? 'invert(50%)' : 'invert(100%)' }}
-          width={14}
-        />
-        <span className="spacer-left">{this.props.repository.label}</span>
-        {alreadyImported && (
-          <span className="big-spacer-left">
-            <CheckIcon className="little-spacer-right" />
-            {translate('onboarding.create_project.already_imported')}
-          </span>
-        )}
-      </Checkbox>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AutoProjectCreate.tsx b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AutoProjectCreate.tsx
deleted file mode 100644 (file)
index 078a54d..0000000
+++ /dev/null
@@ -1,205 +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 AlmRepositoryItem from './AlmRepositoryItem';
-import DeferredSpinner from '../../../components/common/DeferredSpinner';
-import IdentityProviderLink from '../../../components/ui/IdentityProviderLink';
-import { getIdentityProviders } from '../../../api/users';
-import { getRepositories, provisionProject } from '../../../api/alm-integration';
-import { IdentityProvider, LoggedInUser, AlmRepository } from '../../../app/types';
-import { ProjectBase } from '../../../api/components';
-import { SubmitButton } from '../../../components/ui/buttons';
-import { translateWithParameters, translate } from '../../../helpers/l10n';
-
-interface Props {
-  currentUser: LoggedInUser;
-  onProjectCreate: (project: ProjectBase[]) => void;
-}
-
-interface State {
-  identityProviders: IdentityProvider[];
-  installationUrl?: string;
-  installed?: boolean;
-  loading: boolean;
-  repositories: AlmRepository[];
-  selectedRepositories: { [key: string]: AlmRepository | undefined };
-  submitting: boolean;
-}
-
-export default class AutoProjectCreate extends React.PureComponent<Props, State> {
-  mounted = false;
-  state: State = {
-    identityProviders: [],
-    loading: true,
-    repositories: [],
-    selectedRepositories: {},
-    submitting: false
-  };
-
-  componentDidMount() {
-    this.mounted = true;
-    Promise.all([this.fetchIdentityProviders(), this.fetchRepositories()]).then(
-      this.stopLoading,
-      this.stopLoading
-    );
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  fetchIdentityProviders = () => {
-    return getIdentityProviders().then(
-      ({ identityProviders }) => {
-        if (this.mounted) {
-          this.setState({ identityProviders });
-        }
-      },
-      () => {
-        return Promise.resolve();
-      }
-    );
-  };
-
-  fetchRepositories = () => {
-    return getRepositories().then(({ almIntegration, repositories }) => {
-      if (this.mounted) {
-        this.setState({ ...almIntegration, repositories });
-      }
-    });
-  };
-
-  handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
-    event.preventDefault();
-
-    if (this.isValid()) {
-      const { selectedRepositories } = this.state;
-      this.setState({ submitting: true });
-      provisionProject({
-        repositories: Object.keys(selectedRepositories).filter(key =>
-          Boolean(selectedRepositories[key])
-        )
-      }).then(
-        ({ project }) => this.props.onProjectCreate([project]),
-        () => {
-          if (this.mounted) {
-            this.setState({ submitting: false });
-            this.reloadRepositories();
-          }
-        }
-      );
-    }
-  };
-
-  isValid = () => {
-    return this.state.repositories.some(repo =>
-      Boolean(this.state.selectedRepositories[repo.installationKey])
-    );
-  };
-
-  reloadRepositories = () => {
-    this.setState({ loading: true });
-    this.fetchRepositories().then(this.stopLoading, this.stopLoading);
-  };
-
-  stopLoading = () => {
-    if (this.mounted) {
-      this.setState({ loading: false });
-    }
-  };
-
-  toggleRepository = (repository: AlmRepository) => {
-    this.setState(({ selectedRepositories }) => ({
-      selectedRepositories: {
-        ...selectedRepositories,
-        [repository.installationKey]: selectedRepositories[repository.installationKey]
-          ? undefined
-          : repository
-      }
-    }));
-  };
-
-  render() {
-    if (this.state.loading) {
-      return <DeferredSpinner />;
-    }
-
-    const { currentUser } = this.props;
-    const identityProvider = this.state.identityProviders.find(
-      identityProvider => identityProvider.key === currentUser.externalProvider
-    );
-
-    if (!identityProvider) {
-      return null;
-    }
-
-    const { selectedRepositories, submitting } = this.state;
-
-    return (
-      <>
-        <p className="alert alert-info width-60 big-spacer-bottom">
-          {translateWithParameters(
-            'onboarding.create_project.beta_feature_x',
-            identityProvider.name
-          )}
-        </p>
-        {this.state.installed ? (
-          <form onSubmit={this.handleFormSubmit}>
-            <ul>
-              {this.state.repositories.map(repo => (
-                <li className="big-spacer-bottom" key={repo.installationKey}>
-                  <AlmRepositoryItem
-                    identityProvider={identityProvider}
-                    repository={repo}
-                    selected={Boolean(selectedRepositories[repo.installationKey])}
-                    toggleRepository={this.toggleRepository}
-                  />
-                </li>
-              ))}
-            </ul>
-            <SubmitButton disabled={!this.isValid() || submitting}>
-              {translate('onboarding.create_project.create_project')}
-            </SubmitButton>
-            <DeferredSpinner className="spacer-left" loading={submitting} />
-          </form>
-        ) : (
-          <div>
-            <p className="spacer-bottom">
-              {translateWithParameters(
-                'onboarding.create_project.install_app_x',
-                identityProvider.name
-              )}
-            </p>
-            <IdentityProviderLink
-              className="display-inline-block"
-              identityProvider={identityProvider}
-              small={true}
-              url={this.state.installationUrl}>
-              {translateWithParameters(
-                'onboarding.create_project.install_app_x.button',
-                identityProvider.name
-              )}
-            </IdentityProviderLink>
-          </div>
-        )}
-      </>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/CreateProjectOnboarding.tsx b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/CreateProjectOnboarding.tsx
deleted file mode 100644 (file)
index 17bae76..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.
- */
-import * as React from 'react';
-import * as classNames from 'classnames';
-import * as PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import Helmet from 'react-helmet';
-import AutoProjectCreate from './AutoProjectCreate';
-import ManualProjectCreate from './ManualProjectCreate';
-import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
-import { getCurrentUser } from '../../../store/rootReducer';
-import { skipOnboarding } from '../../../store/users/actions';
-import { CurrentUser, isLoggedIn } from '../../../app/types';
-import { translate } from '../../../helpers/l10n';
-import { ProjectBase } from '../../../api/components';
-import { getProjectUrl, getOrganizationUrl } from '../../../helpers/urls';
-import '../../../app/styles/sonarcloud.css';
-import '../styles.css';
-
-interface OwnProps {
-  onFinishOnboarding: () => void;
-}
-
-interface StateProps {
-  currentUser: CurrentUser;
-}
-
-interface DispatchProps {
-  skipOnboarding: () => void;
-}
-
-enum Tabs {
-  AUTO,
-  MANUAL
-}
-
-type Props = OwnProps & StateProps & DispatchProps;
-
-interface State {
-  activeTab: Tabs;
-}
-
-export class CreateProjectOnboarding extends React.PureComponent<Props, State> {
-  mounted = false;
-  static contextTypes = {
-    router: PropTypes.object
-  };
-
-  constructor(props: Props) {
-    super(props);
-    this.state = { activeTab: this.shouldDisplayTabs(props) ? Tabs.AUTO : Tabs.MANUAL };
-  }
-
-  componentDidMount() {
-    this.mounted = true;
-    if (!isLoggedIn(this.props.currentUser)) {
-      handleRequiredAuthentication();
-    }
-    document.body.classList.add('white-page');
-    document.documentElement.classList.add('white-page');
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-    document.body.classList.remove('white-page');
-    document.documentElement.classList.remove('white-page');
-  }
-
-  handleProjectCreate = (projects: Pick<ProjectBase, 'key'>[], organization?: string) => {
-    if (projects.length > 1 && organization) {
-      this.context.router.push(getOrganizationUrl(organization) + '/projects');
-    } else if (projects.length === 1) {
-      this.context.router.push(getProjectUrl(projects[0].key));
-    }
-  };
-
-  shouldDisplayTabs = ({ currentUser } = this.props) => {
-    return (
-      isLoggedIn(currentUser) &&
-      ['bitbucket', 'github'].includes(currentUser.externalProvider || '')
-    );
-  };
-
-  showAuto = (event: React.MouseEvent<HTMLAnchorElement>) => {
-    event.preventDefault();
-    this.setState({ activeTab: Tabs.AUTO });
-  };
-
-  showManual = (event: React.MouseEvent<HTMLAnchorElement>) => {
-    event.preventDefault();
-    this.setState({ activeTab: Tabs.MANUAL });
-  };
-
-  render() {
-    const { currentUser } = this.props;
-    if (!isLoggedIn(currentUser)) {
-      return null;
-    }
-
-    const { activeTab } = this.state;
-    const header = translate('onboarding.create_project.header');
-    return (
-      <>
-        <Helmet title={header} titleTemplate="%s" />
-        <div className="sonarcloud page page-limited">
-          <div className="page-header">
-            <h1 className="page-title">{header}</h1>
-          </div>
-
-          {this.shouldDisplayTabs() && (
-            <ul className="flex-tabs">
-              <li>
-                <a
-                  className={classNames('js-auto', { selected: activeTab === Tabs.AUTO })}
-                  href="#"
-                  onClick={this.showAuto}>
-                  {translate('onboarding.create_project.select_repositories')}
-                  <span
-                    className={classNames(
-                      'rounded alert alert-small spacer-left display-inline-block',
-                      {
-                        'alert-info': activeTab === Tabs.AUTO,
-                        'alert-muted': activeTab !== Tabs.AUTO
-                      }
-                    )}>
-                    {translate('beta')}
-                  </span>
-                </a>
-              </li>
-              <li>
-                <a
-                  className={classNames('js-manual', { selected: activeTab === Tabs.MANUAL })}
-                  href="#"
-                  onClick={this.showManual}>
-                  {translate('onboarding.create_project.create_manually')}
-                </a>
-              </li>
-            </ul>
-          )}
-
-          {activeTab === Tabs.AUTO ? (
-            <AutoProjectCreate
-              currentUser={currentUser}
-              onProjectCreate={this.handleProjectCreate}
-            />
-          ) : (
-            <ManualProjectCreate
-              currentUser={currentUser}
-              onProjectCreate={this.handleProjectCreate}
-            />
-          )}
-        </div>
-      </>
-    );
-  }
-}
-
-const mapStateToProps = (state: any): StateProps => {
-  return {
-    currentUser: getCurrentUser(state)
-  };
-};
-
-const mapDispatchToProps: DispatchProps = { skipOnboarding };
-
-export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)(
-  CreateProjectOnboarding
-);
diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/ManualProjectCreate.tsx b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/ManualProjectCreate.tsx
deleted file mode 100644 (file)
index 59d6155..0000000
+++ /dev/null
@@ -1,227 +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 { sortBy } from 'lodash';
-import { connect } from 'react-redux';
-import CreateOrganizationForm from '../../account/organizations/CreateOrganizationForm';
-import Select from '../../../components/controls/Select';
-import { Button, SubmitButton } from '../../../components/ui/buttons';
-import { LoggedInUser, Organization } from '../../../app/types';
-import { fetchMyOrganizations } from '../../account/organizations/actions';
-import { getMyOrganizations } from '../../../store/rootReducer';
-import { translate } from '../../../helpers/l10n';
-import { createProject, ProjectBase } from '../../../api/components';
-import DeferredSpinner from '../../../components/common/DeferredSpinner';
-
-interface StateProps {
-  userOrganizations: Organization[];
-}
-
-interface DispatchProps {
-  fetchMyOrganizations: () => Promise<void>;
-}
-
-interface OwnProps {
-  currentUser: LoggedInUser;
-  onProjectCreate: (project: ProjectBase[]) => void;
-}
-
-type Props = OwnProps & StateProps & DispatchProps;
-
-interface State {
-  createOrganizationModal: boolean;
-  projectName: string;
-  projectKey: string;
-  selectedOrganization: string;
-  submitting: boolean;
-}
-
-export class ManualProjectCreate extends React.PureComponent<Props, State> {
-  mounted = false;
-
-  constructor(props: Props) {
-    super(props);
-    this.state = {
-      createOrganizationModal: false,
-      projectName: '',
-      projectKey: '',
-      selectedOrganization:
-        props.userOrganizations.length <= 1 ? props.userOrganizations[0].key : '',
-      submitting: false
-    };
-  }
-
-  componentDidMount() {
-    this.mounted = true;
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  closeCreateOrganization = () => {
-    this.setState({ createOrganizationModal: false });
-  };
-
-  handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
-    event.preventDefault();
-
-    if (this.isValid()) {
-      const { projectKey, projectName, selectedOrganization } = this.state;
-      this.setState({ submitting: true });
-      createProject({
-        project: projectKey,
-        name: projectName,
-        organization: selectedOrganization
-      }).then(
-        ({ project }) => this.props.onProjectCreate([project]),
-        () => {
-          if (this.mounted) {
-            this.setState({ submitting: false });
-          }
-        }
-      );
-    }
-  };
-
-  handleOrganizationSelect = ({ value }: { value: string }) => {
-    this.setState({ selectedOrganization: value });
-  };
-
-  handleProjectNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
-    this.setState({ projectName: event.currentTarget.value });
-  };
-
-  handleProjectKeyChange = (event: React.ChangeEvent<HTMLInputElement>) => {
-    this.setState({ projectKey: event.currentTarget.value });
-  };
-
-  isValid = () => {
-    const { projectKey, projectName, selectedOrganization } = this.state;
-    return Boolean(projectKey && projectName && selectedOrganization);
-  };
-
-  onCreateOrganization = (organization: { key: string }) => {
-    this.props.fetchMyOrganizations().then(
-      () => {
-        this.handleOrganizationSelect({ value: organization.key });
-        this.closeCreateOrganization();
-      },
-      () => {
-        this.closeCreateOrganization();
-      }
-    );
-  };
-
-  showCreateOrganization = () => {
-    this.setState({ createOrganizationModal: true });
-  };
-
-  render() {
-    const { submitting } = this.state;
-    return (
-      <>
-        <form onSubmit={this.handleFormSubmit}>
-          <div className="form-field">
-            <label htmlFor="select-organization">
-              {translate('onboarding.create_project.organization')}
-              <em className="mandatory">*</em>
-            </label>
-            <Select
-              autoFocus={true}
-              className="input-super-large"
-              clearable={false}
-              id="select-organization"
-              onChange={this.handleOrganizationSelect}
-              options={sortBy(this.props.userOrganizations, o => o.name.toLowerCase()).map(
-                organization => ({
-                  label: organization.name,
-                  value: organization.key
-                })
-              )}
-              required={true}
-              value={this.state.selectedOrganization}
-            />
-            <Button
-              className="button-link big-spacer-left js-new-org"
-              onClick={this.showCreateOrganization}>
-              {translate('onboarding.create_project.create_new_org')}
-            </Button>
-          </div>
-          <div className="form-field">
-            <label htmlFor="project-name">
-              {translate('onboarding.create_project.project_name')}
-              <em className="mandatory">*</em>
-            </label>
-            <input
-              className="input-super-large"
-              id="project-name"
-              maxLength={400}
-              minLength={1}
-              onChange={this.handleProjectNameChange}
-              required={true}
-              type="text"
-              value={this.state.projectName}
-            />
-          </div>
-          <div className="form-field">
-            <label htmlFor="project-key">
-              {translate('onboarding.create_project.project_key')}
-              <em className="mandatory">*</em>
-            </label>
-            <input
-              className="input-super-large"
-              id="project-key"
-              maxLength={400}
-              minLength={1}
-              onChange={this.handleProjectKeyChange}
-              required={true}
-              type="text"
-              value={this.state.projectKey}
-            />
-          </div>
-          <SubmitButton disabled={!this.isValid() || submitting}>
-            {translate('onboarding.create_project.create_project')}
-          </SubmitButton>
-          <DeferredSpinner className="spacer-left" loading={submitting} />
-        </form>
-        {this.state.createOrganizationModal && (
-          <CreateOrganizationForm
-            onClose={this.closeCreateOrganization}
-            onCreate={this.onCreateOrganization}
-          />
-        )}
-      </>
-    );
-  }
-}
-
-const mapDispatchToProps = ({
-  fetchMyOrganizations
-} as any) as DispatchProps;
-
-const mapStateToProps = (state: any): StateProps => {
-  return {
-    userOrganizations: getMyOrganizations(state)
-  };
-};
-export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)(
-  ManualProjectCreate
-);
diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AlmRepositoryItem-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AlmRepositoryItem-test.tsx
deleted file mode 100644 (file)
index 72b25cb..0000000
+++ /dev/null
@@ -1,66 +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 { shallow } from 'enzyme';
-import AlmRepositoryItem from '../AlmRepositoryItem';
-
-const identityProviders = {
-  backgroundColor: 'blue',
-  iconPath: 'icon/path',
-  key: 'foo',
-  name: 'Foo Provider'
-};
-
-const repositories = [
-  {
-    label: 'Cool Project',
-    installationKey: 'github/cool',
-    linkedProjectKey: 'proj_cool',
-    linkedProjectName: 'Proj Cool'
-  },
-  {
-    label: 'Awesome Project',
-    installationKey: 'github/awesome'
-  }
-];
-
-it('should render correctly', () => {
-  expect(getWrapper()).toMatchSnapshot();
-});
-
-it('should render selected', () => {
-  expect(getWrapper({ selected: true })).toMatchSnapshot();
-});
-
-it('should render disabled', () => {
-  expect(getWrapper({ repository: repositories[0] })).toMatchSnapshot();
-});
-
-function getWrapper(props = {}) {
-  return shallow(
-    <AlmRepositoryItem
-      identityProvider={identityProviders}
-      repository={repositories[1]}
-      selected={false}
-      toggleRepository={jest.fn()}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AutoProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AutoProjectCreate-test.tsx
deleted file mode 100644 (file)
index bfe9543..0000000
+++ /dev/null
@@ -1,96 +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 { shallow } from 'enzyme';
-import AutoProjectCreate from '../AutoProjectCreate';
-import { getIdentityProviders } from '../../../../api/users';
-import { getRepositories } from '../../../../api/alm-integration';
-import { LoggedInUser } from '../../../../app/types';
-import { waitAndUpdate } from '../../../../helpers/testUtils';
-
-jest.mock('../../../../api/users', () => ({
-  getIdentityProviders: jest.fn().mockResolvedValue({
-    identityProviders: [
-      {
-        backgroundColor: 'blue',
-        iconPath: 'icon/path',
-        key: 'foo',
-        name: 'Foo Provider'
-      }
-    ]
-  })
-}));
-
-jest.mock('../../../../api/alm-integration', () => ({
-  getRepositories: jest.fn().mockResolvedValue({
-    almIntegration: {
-      installationUrl: 'https://alm.foo.com/install',
-      installed: false
-    },
-    repositories: []
-  }),
-  provisionProject: jest.fn().mockResolvedValue({ projects: [] })
-}));
-
-const user: LoggedInUser = { isLoggedIn: true, login: 'foo', name: 'Foo', externalProvider: 'foo' };
-const repositories = [
-  {
-    label: 'Cool Project',
-    installationKey: 'github/cool',
-    linkedProjectKey: 'proj_cool',
-    linkedProjectName: 'Proj Cool'
-  },
-  {
-    label: 'Awesome Project',
-    installationKey: 'github/awesome'
-  }
-];
-
-beforeEach(() => {
-  (getIdentityProviders as jest.Mock<any>).mockClear();
-  (getRepositories as jest.Mock<any>).mockClear();
-});
-
-it('should display the provider app install button', async () => {
-  const wrapper = getWrapper();
-  expect(wrapper).toMatchSnapshot();
-  expect(getIdentityProviders).toHaveBeenCalled();
-  expect(getRepositories).toHaveBeenCalled();
-
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should display the list of repositories', async () => {
-  (getRepositories as jest.Mock<any>).mockResolvedValue({
-    almIntegration: {
-      installationUrl: 'https://alm.foo.com/install',
-      installed: true
-    },
-    repositories
-  });
-  const wrapper = getWrapper();
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-});
-
-function getWrapper(props = {}) {
-  return shallow(<AutoProjectCreate currentUser={user} onProjectCreate={jest.fn()} {...props} />);
-}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/CreateProjectOnboarding-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/CreateProjectOnboarding-test.tsx
deleted file mode 100644 (file)
index f7cfa3c..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.
- */
-import * as React from 'react';
-import { shallow } from 'enzyme';
-import { CreateProjectOnboarding } from '../CreateProjectOnboarding';
-import { LoggedInUser } from '../../../../app/types';
-import { click } from '../../../../helpers/testUtils';
-
-const user: LoggedInUser = {
-  externalProvider: 'github',
-  isLoggedIn: true,
-  login: 'foo',
-  name: 'Foo'
-};
-
-it('should render correctly', () => {
-  expect(getWrapper()).toMatchSnapshot();
-});
-
-it('should render with Manual creation only', () => {
-  expect(getWrapper({ currentUser: { ...user, externalProvider: 'vsts' } })).toMatchSnapshot();
-});
-
-it('should switch tabs', () => {
-  const wrapper = getWrapper();
-  click(wrapper.find('.js-manual'));
-  expect(wrapper.find('Connect(ManualProjectCreate)').exists()).toBeTruthy();
-  click(wrapper.find('.js-auto'));
-  expect(wrapper.find('AutoProjectCreate').exists()).toBeTruthy();
-});
-
-function getWrapper(props = {}) {
-  return shallow(
-    <CreateProjectOnboarding
-      currentUser={user}
-      onFinishOnboarding={jest.fn()}
-      skipOnboarding={jest.fn()}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/ManualProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/ManualProjectCreate-test.tsx
deleted file mode 100644 (file)
index b79b4e4..0000000
+++ /dev/null
@@ -1,79 +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 { shallow } from 'enzyme';
-import { ManualProjectCreate } from '../ManualProjectCreate';
-import { change, click, submit, waitAndUpdate } from '../../../../helpers/testUtils';
-import { createProject } from '../../../../api/components';
-
-jest.mock('../../../../api/components', () => ({
-  createProject: jest.fn().mockResolvedValue({ project: { key: 'bar', name: 'Bar' } })
-}));
-
-beforeEach(() => {
-  (createProject as jest.Mock<any>).mockClear();
-});
-
-it('should render correctly', () => {
-  expect(getWrapper()).toMatchSnapshot();
-});
-
-it('should allow to create a new org', async () => {
-  const fetchMyOrganizations = jest.fn().mockResolvedValueOnce([]);
-  const wrapper = getWrapper({ fetchMyOrganizations });
-
-  click(wrapper.find('.js-new-org'));
-  const createForm = wrapper.find('Connect(CreateOrganizationForm)');
-  expect(createForm.exists()).toBeTruthy();
-
-  createForm.prop<Function>('onCreate')({ key: 'baz' });
-  expect(fetchMyOrganizations).toHaveBeenCalled();
-  await waitAndUpdate(wrapper);
-  expect(wrapper.state('selectedOrganization')).toBe('baz');
-});
-
-it('should correctly create a project', async () => {
-  const onProjectCreate = jest.fn();
-  const wrapper = getWrapper({ onProjectCreate });
-  wrapper.find('Select').prop<Function>('onChange')({ value: 'foo' });
-  change(wrapper.find('#project-name'), 'Bar');
-  expect(wrapper.find('SubmitButton')).toMatchSnapshot();
-
-  change(wrapper.find('#project-key'), 'bar');
-  expect(wrapper.find('SubmitButton')).toMatchSnapshot();
-
-  submit(wrapper.find('form'));
-  expect(createProject).toBeCalledWith({ project: 'bar', name: 'Bar', organization: 'foo' });
-
-  await waitAndUpdate(wrapper);
-  expect(onProjectCreate).toBeCalledWith([{ key: 'bar', name: 'Bar' }]);
-});
-
-function getWrapper(props = {}) {
-  return shallow(
-    <ManualProjectCreate
-      currentUser={{ isLoggedIn: true, login: 'foo', name: 'Foo' }}
-      fetchMyOrganizations={jest.fn()}
-      onProjectCreate={jest.fn()}
-      userOrganizations={[{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }]}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AlmRepositoryItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AlmRepositoryItem-test.tsx.snap
deleted file mode 100644 (file)
index ad71bfc..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<Checkbox
-  checked={false}
-  disabled={false}
-  onCheck={[Function]}
-  thirdState={false}
->
-  <img
-    alt="Foo Provider"
-    className="spacer-left"
-    height={14}
-    src="icon/path"
-    style={
-      Object {
-        "filter": "invert(100%)",
-      }
-    }
-    width={14}
-  />
-  <span
-    className="spacer-left"
-  >
-    Awesome Project
-  </span>
-</Checkbox>
-`;
-
-exports[`should render disabled 1`] = `
-<Checkbox
-  checked={true}
-  disabled={true}
-  onCheck={[Function]}
-  thirdState={false}
->
-  <img
-    alt="Foo Provider"
-    className="spacer-left"
-    height={14}
-    src="icon/path"
-    style={
-      Object {
-        "filter": "invert(50%)",
-      }
-    }
-    width={14}
-  />
-  <span
-    className="spacer-left"
-  >
-    Cool Project
-  </span>
-  <span
-    className="big-spacer-left"
-  >
-    <CheckIcon
-      className="little-spacer-right"
-    />
-    onboarding.create_project.already_imported
-  </span>
-</Checkbox>
-`;
-
-exports[`should render selected 1`] = `
-<Checkbox
-  checked={true}
-  disabled={false}
-  onCheck={[Function]}
-  thirdState={false}
->
-  <img
-    alt="Foo Provider"
-    className="spacer-left"
-    height={14}
-    src="icon/path"
-    style={
-      Object {
-        "filter": "invert(100%)",
-      }
-    }
-    width={14}
-  />
-  <span
-    className="spacer-left"
-  >
-    Awesome Project
-  </span>
-</Checkbox>
-`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap
deleted file mode 100644 (file)
index 6d6c039..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should display the list of repositories 1`] = `
-<React.Fragment>
-  <p
-    className="alert alert-info width-60 big-spacer-bottom"
-  >
-    onboarding.create_project.beta_feature_x.Foo Provider
-  </p>
-  <form
-    onSubmit={[Function]}
-  >
-    <ul>
-      <li
-        className="big-spacer-bottom"
-        key="github/cool"
-      >
-        <AlmRepositoryItem
-          identityProvider={
-            Object {
-              "backgroundColor": "blue",
-              "iconPath": "icon/path",
-              "key": "foo",
-              "name": "Foo Provider",
-            }
-          }
-          repository={
-            Object {
-              "installationKey": "github/cool",
-              "label": "Cool Project",
-              "linkedProjectKey": "proj_cool",
-              "linkedProjectName": "Proj Cool",
-            }
-          }
-          selected={false}
-          toggleRepository={[Function]}
-        />
-      </li>
-      <li
-        className="big-spacer-bottom"
-        key="github/awesome"
-      >
-        <AlmRepositoryItem
-          identityProvider={
-            Object {
-              "backgroundColor": "blue",
-              "iconPath": "icon/path",
-              "key": "foo",
-              "name": "Foo Provider",
-            }
-          }
-          repository={
-            Object {
-              "installationKey": "github/awesome",
-              "label": "Awesome Project",
-            }
-          }
-          selected={false}
-          toggleRepository={[Function]}
-        />
-      </li>
-    </ul>
-    <SubmitButton
-      disabled={true}
-    >
-      onboarding.create_project.create_project
-    </SubmitButton>
-    <DeferredSpinner
-      className="spacer-left"
-      loading={false}
-      timeout={100}
-    />
-  </form>
-</React.Fragment>
-`;
-
-exports[`should display the provider app install button 1`] = `
-<DeferredSpinner
-  timeout={100}
-/>
-`;
-
-exports[`should display the provider app install button 2`] = `
-<React.Fragment>
-  <p
-    className="alert alert-info width-60 big-spacer-bottom"
-  >
-    onboarding.create_project.beta_feature_x.Foo Provider
-  </p>
-  <div>
-    <p
-      className="spacer-bottom"
-    >
-      onboarding.create_project.install_app_x.Foo Provider
-    </p>
-    <IdentityProviderLink
-      className="display-inline-block"
-      identityProvider={
-        Object {
-          "backgroundColor": "blue",
-          "iconPath": "icon/path",
-          "key": "foo",
-          "name": "Foo Provider",
-        }
-      }
-      small={true}
-      url="https://alm.foo.com/install"
-    >
-      onboarding.create_project.install_app_x.button.Foo Provider
-    </IdentityProviderLink>
-  </div>
-</React.Fragment>
-`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/CreateProjectOnboarding-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/CreateProjectOnboarding-test.tsx.snap
deleted file mode 100644 (file)
index 1920eb2..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<React.Fragment>
-  <HelmetWrapper
-    defer={true}
-    encodeSpecialCharacters={true}
-    title="onboarding.create_project.header"
-    titleTemplate="%s"
-  />
-  <div
-    className="sonarcloud page page-limited"
-  >
-    <div
-      className="page-header"
-    >
-      <h1
-        className="page-title"
-      >
-        onboarding.create_project.header
-      </h1>
-    </div>
-    <ul
-      className="flex-tabs"
-    >
-      <li>
-        <a
-          className="js-auto selected"
-          href="#"
-          onClick={[Function]}
-        >
-          onboarding.create_project.select_repositories
-          <span
-            className="rounded alert alert-small spacer-left display-inline-block alert-info"
-          >
-            beta
-          </span>
-        </a>
-      </li>
-      <li>
-        <a
-          className="js-manual"
-          href="#"
-          onClick={[Function]}
-        >
-          onboarding.create_project.create_manually
-        </a>
-      </li>
-    </ul>
-    <AutoProjectCreate
-      currentUser={
-        Object {
-          "externalProvider": "github",
-          "isLoggedIn": true,
-          "login": "foo",
-          "name": "Foo",
-        }
-      }
-      onProjectCreate={[Function]}
-    />
-  </div>
-</React.Fragment>
-`;
-
-exports[`should render with Manual creation only 1`] = `
-<React.Fragment>
-  <HelmetWrapper
-    defer={true}
-    encodeSpecialCharacters={true}
-    title="onboarding.create_project.header"
-    titleTemplate="%s"
-  />
-  <div
-    className="sonarcloud page page-limited"
-  >
-    <div
-      className="page-header"
-    >
-      <h1
-        className="page-title"
-      >
-        onboarding.create_project.header
-      </h1>
-    </div>
-    <Connect(ManualProjectCreate)
-      currentUser={
-        Object {
-          "externalProvider": "vsts",
-          "isLoggedIn": true,
-          "login": "foo",
-          "name": "Foo",
-        }
-      }
-      onProjectCreate={[Function]}
-    />
-  </div>
-</React.Fragment>
-`;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap
deleted file mode 100644 (file)
index fafb751..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should correctly create a project 1`] = `
-<SubmitButton
-  disabled={true}
->
-  onboarding.create_project.create_project
-</SubmitButton>
-`;
-
-exports[`should correctly create a project 2`] = `
-<SubmitButton
-  disabled={false}
->
-  onboarding.create_project.create_project
-</SubmitButton>
-`;
-
-exports[`should render correctly 1`] = `
-<React.Fragment>
-  <form
-    onSubmit={[Function]}
-  >
-    <div
-      className="form-field"
-    >
-      <label
-        htmlFor="select-organization"
-      >
-        onboarding.create_project.organization
-        <em
-          className="mandatory"
-        >
-          *
-        </em>
-      </label>
-      <Select
-        autoFocus={true}
-        className="input-super-large"
-        clearable={false}
-        id="select-organization"
-        onChange={[Function]}
-        options={
-          Array [
-            Object {
-              "label": "Bar",
-              "value": "bar",
-            },
-            Object {
-              "label": "Foo",
-              "value": "foo",
-            },
-          ]
-        }
-        required={true}
-        value=""
-      />
-      <Button
-        className="button-link big-spacer-left js-new-org"
-        onClick={[Function]}
-      >
-        onboarding.create_project.create_new_org
-      </Button>
-    </div>
-    <div
-      className="form-field"
-    >
-      <label
-        htmlFor="project-name"
-      >
-        onboarding.create_project.project_name
-        <em
-          className="mandatory"
-        >
-          *
-        </em>
-      </label>
-      <input
-        className="input-super-large"
-        id="project-name"
-        maxLength={400}
-        minLength={1}
-        onChange={[Function]}
-        required={true}
-        type="text"
-        value=""
-      />
-    </div>
-    <div
-      className="form-field"
-    >
-      <label
-        htmlFor="project-key"
-      >
-        onboarding.create_project.project_key
-        <em
-          className="mandatory"
-        >
-          *
-        </em>
-      </label>
-      <input
-        className="input-super-large"
-        id="project-key"
-        maxLength={400}
-        minLength={1}
-        onChange={[Function]}
-        required={true}
-        type="text"
-        value=""
-      />
-    </div>
-    <SubmitButton
-      disabled={true}
-    >
-      onboarding.create_project.create_project
-    </SubmitButton>
-    <DeferredSpinner
-      className="spacer-left"
-      loading={false}
-      timeout={100}
-    />
-  </form>
-</React.Fragment>
-`;
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
new file mode 100644 (file)
index 0000000..b8006ff
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * 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 OnboardingPrivateIcon from '../../../components/icons-components/OnboardingPrivateIcon';
+import OnboardingProjectIcon from '../../../components/icons-components/OnboardingProjectIcon';
+import OnboardingTeamIcon from '../../../components/icons-components/OnboardingTeamIcon';
+import { Button, ResetButtonLink } 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 {
+  onClose: () => void;
+  onOpenOrganizationOnboarding: () => void;
+  onOpenProjectOnboarding: () => void;
+  onOpenTeamOnboarding: () => void;
+}
+
+interface StateProps {
+  currentUser: CurrentUser;
+}
+
+type Props = OwnProps & StateProps;
+
+export class OnboardingModal 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.onClose}
+        shouldCloseOnOverlayClick={false}>
+        <div className="modal-simple-head text-center">
+          <h1>{translate('onboarding.header')}</h1>
+          <p className="spacer-top">{translate('onboarding.header.description')}</p>
+        </div>
+        <div className="modal-simple-body text-center onboarding-choices">
+          <Button className="onboarding-choice" onClick={this.props.onOpenProjectOnboarding}>
+            <OnboardingProjectIcon />
+            <span>{translate('onboarding.analyze_public_code')}</span>
+            <p className="note">{translate('onboarding.analyze_public_code.note')}</p>
+          </Button>
+          <Button className="onboarding-choice" onClick={this.props.onOpenOrganizationOnboarding}>
+            <OnboardingPrivateIcon />
+            <span>{translate('onboarding.analyze_private_code')}</span>
+            <p className="note">{translate('onboarding.analyze_private_code.note')}</p>
+          </Button>
+          <Button className="onboarding-choice" onClick={this.props.onOpenTeamOnboarding}>
+            <OnboardingTeamIcon />
+            <span>{translate('onboarding.contribute_existing_project')}</span>
+            <p className="note">{translate('onboarding.contribute_existing_project.note')}</p>
+          </Button>
+        </div>
+        <div className="modal-simple-footer text-center">
+          <ResetButtonLink className="spacer-bottom" onClick={this.props.onClose}>
+            {translate('not_now')}
+          </ResetButtonLink>
+          <p className="note">{translate('onboarding.footer')}</p>
+        </div>
+      </Modal>
+    );
+  }
+}
+
+const mapStateToProps = (state: any): StateProps => ({ currentUser: getCurrentUser(state) });
+
+export default connect<StateProps, {}, OwnProps>(mapStateToProps)(OnboardingModal);
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
new file mode 100644 (file)
index 0000000..d69e98a
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * 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 '../../../api/users';
+import { skipOnboarding as skipOnboardingAction } from '../../../store/users/actions';
+import CreateOrganizationForm from '../../account/organizations/CreateOrganizationForm';
+import TeamOnboardingModal from '../teamOnboarding/TeamOnboardingModal';
+import { Organization } from '../../../app/types';
+
+interface DispatchProps {
+  skipOnboardingAction: () => void;
+}
+
+enum ModalKey {
+  onboarding,
+  organizationOnboarding,
+  teamOnboarding
+}
+
+interface State {
+  modal?: ModalKey;
+}
+
+export class OnboardingPage extends React.PureComponent<DispatchProps, State> {
+  static contextTypes = {
+    openProjectOnboarding: PropTypes.func.isRequired,
+    router: PropTypes.object.isRequired
+  };
+
+  state: State = { modal: ModalKey.onboarding };
+
+  closeOnboarding = () => {
+    skipOnboarding();
+    this.props.skipOnboardingAction();
+    this.context.router.replace('/');
+  };
+
+  closeOrganizationOnboarding = ({ key }: Pick<Organization, 'key'>) => {
+    this.closeOnboarding();
+    this.context.router.push(`/organizations/${key}`);
+  };
+
+  openOrganizationOnboarding = () => {
+    this.setState({ modal: ModalKey.organizationOnboarding });
+  };
+
+  openTeamOnboarding = () => {
+    this.setState({ modal: ModalKey.teamOnboarding });
+  };
+
+  render() {
+    const { modal } = this.state;
+    return (
+      <>
+        {modal === ModalKey.onboarding && (
+          <OnboardingModal
+            onClose={this.closeOnboarding}
+            onOpenOrganizationOnboarding={this.openOrganizationOnboarding}
+            onOpenProjectOnboarding={this.context.openProjectOnboarding}
+            onOpenTeamOnboarding={this.openTeamOnboarding}
+          />
+        )}
+        {modal === ModalKey.organizationOnboarding && (
+          <CreateOrganizationForm
+            onClose={this.closeOnboarding}
+            onCreate={this.closeOrganizationOnboarding}
+          />
+        )}
+        {modal === ModalKey.teamOnboarding && (
+          <TeamOnboardingModal onFinish={this.closeOnboarding} />
+        )}
+      </>
+    );
+  }
+}
+
+const mapDispatchToProps: DispatchProps = { skipOnboardingAction };
+
+export default connect<{}, DispatchProps>(null, mapDispatchToProps)(OnboardingPage);
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx
new file mode 100644 (file)
index 0000000..76c801f
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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 { OnboardingModal } from '../OnboardingModal';
+import { click } from '../../../../helpers/testUtils';
+
+it('renders correctly', () => {
+  expect(
+    shallow(
+      <OnboardingModal
+        currentUser={{ isLoggedIn: true }}
+        onClose={jest.fn()}
+        onOpenOrganizationOnboarding={jest.fn()}
+        onOpenProjectOnboarding={jest.fn()}
+        onOpenTeamOnboarding={jest.fn()}
+      />
+    )
+  ).toMatchSnapshot();
+});
+
+it('should correctly open the different tutorials', () => {
+  const onClose = jest.fn();
+  const onOpenOrganizationOnboarding = jest.fn();
+  const onOpenProjectOnboarding = jest.fn();
+  const onOpenTeamOnboarding = jest.fn();
+  const push = jest.fn();
+  const wrapper = shallow(
+    <OnboardingModal
+      currentUser={{ isLoggedIn: true }}
+      onClose={onClose}
+      onOpenOrganizationOnboarding={onOpenOrganizationOnboarding}
+      onOpenProjectOnboarding={onOpenProjectOnboarding}
+      onOpenTeamOnboarding={onOpenTeamOnboarding}
+    />,
+    { context: { router: { push } } }
+  );
+
+  click(wrapper.find('ResetButtonLink'));
+  expect(onClose).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/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap
new file mode 100644 (file)
index 0000000..18b2f16
--- /dev/null
@@ -0,0 +1,84 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<Modal
+  contentLabel="onboarding.header"
+  medium={true}
+  onRequestClose={[MockFunction]}
+  shouldCloseOnOverlayClick={false}
+>
+  <div
+    className="modal-simple-head text-center"
+  >
+    <h1>
+      onboarding.header
+    </h1>
+    <p
+      className="spacer-top"
+    >
+      onboarding.header.description
+    </p>
+  </div>
+  <div
+    className="modal-simple-body text-center onboarding-choices"
+  >
+    <Button
+      className="onboarding-choice"
+      onClick={[MockFunction]}
+    >
+      <OnboardingProjectIcon />
+      <span>
+        onboarding.analyze_public_code
+      </span>
+      <p
+        className="note"
+      >
+        onboarding.analyze_public_code.note
+      </p>
+    </Button>
+    <Button
+      className="onboarding-choice"
+      onClick={[MockFunction]}
+    >
+      <OnboardingPrivateIcon />
+      <span>
+        onboarding.analyze_private_code
+      </span>
+      <p
+        className="note"
+      >
+        onboarding.analyze_private_code.note
+      </p>
+    </Button>
+    <Button
+      className="onboarding-choice"
+      onClick={[MockFunction]}
+    >
+      <OnboardingTeamIcon />
+      <span>
+        onboarding.contribute_existing_project
+      </span>
+      <p
+        className="note"
+      >
+        onboarding.contribute_existing_project.note
+      </p>
+    </Button>
+  </div>
+  <div
+    className="modal-simple-footer text-center"
+  >
+    <ResetButtonLink
+      className="spacer-bottom"
+      onClick={[MockFunction]}
+    >
+      not_now
+    </ResetButtonLink>
+    <p
+      className="note"
+    >
+      onboarding.footer
+    </p>
+  </div>
+</Modal>
+`;
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
deleted file mode 100644 (file)
index a91ff54..0000000
+++ /dev/null
@@ -1,201 +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 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
deleted file mode 100644 (file)
index 25b7544..0000000
+++ /dev/null
@@ -1,178 +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 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
deleted file mode 100644 (file)
index 5244898..0000000
+++ /dev/null
@@ -1,168 +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 { 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
deleted file mode 100644 (file)
index f09b4d1..0000000
+++ /dev/null
@@ -1,150 +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 { 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
deleted file mode 100644 (file)
index 934965c..0000000
+++ /dev/null
@@ -1,273 +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 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={import(/* webpackMode: "eager" */ 'Docs/tooltips/organizations/organization.md')}
-            />
-          </span>
-        }
-      />
-    );
-  }
-}
index 384e1726e39da7b9e92c49449b52af49511ab65e..e3511631aa473e8bd1d55b189df1db64d5d0be91 100644 (file)
@@ -21,10 +21,10 @@ 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 ProjectAnalysisStep from '../components/ProjectAnalysisStep';
+import OrganizationStep from '../components/OrganizationStep';
+import TokenStep from '../components/TokenStep';
 import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
 import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer';
 import { CurrentUser, isLoggedIn } from '../../../app/types';
@@ -161,7 +161,7 @@ export class ProjectOnboarding extends React.PureComponent<Props, State> {
             stepNumber={stepNumber++}
           />
 
-          <AnalysisStep
+          <ProjectAnalysisStep
             onFinish={this.handleFinish}
             onReset={this.handleReset}
             open={step === 'analysis'}
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
deleted file mode 100644 (file)
index 7fda387..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.
- */
-/* 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
deleted file mode 100644 (file)
index e0cd2c5..0000000
+++ /dev/null
@@ -1,292 +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 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
deleted file mode 100644 (file)
index 629044f..0000000
+++ /dev/null
@@ -1,131 +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 { 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
deleted file mode 100644 (file)
index bb16f90..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.
- */
-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
deleted file mode 100644 (file)
index 2c8c18c..0000000
+++ /dev/null
@@ -1,55 +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 { 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
deleted file mode 100644 (file)
index b2192a0..0000000
+++ /dev/null
@@ -1,102 +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 { 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__/Step-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/Step-test.tsx
deleted file mode 100644 (file)
index 8a9664f..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.
- */
-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
deleted file mode 100644 (file)
index 25b5f0c..0000000
+++ /dev/null
@@ -1,110 +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 { 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
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/projectOnboarding/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewOrganizationForm-test.tsx.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/projectOnboarding/__tests__/__snapshots__/NewProjectForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewProjectForm-test.tsx.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/projectOnboarding/__tests__/__snapshots__/OrganizationStep-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/OrganizationStep-test.tsx.snap
deleted file mode 100644 (file)
index 8ece74c..0000000
+++ /dev/null
@@ -1,286 +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={Promise {}}
-        />
-      </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={Promise {}}
-            />
-          </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>
-`;
index 0c5b93e108a0eee66735c0bc9f47524d08febda1..655ed26af50911901d99fb41132e36adbc835a4e 100644 (file)
@@ -36,7 +36,7 @@ exports[`guides for on-premise 1`] = `
       open={true}
       stepNumber={1}
     />
-    <AnalysisStep
+    <ProjectAnalysisStep
       onFinish={[Function]}
       onReset={[Function]}
       open={false}
@@ -97,7 +97,7 @@ exports[`guides for on-premise 2`] = `
       open={false}
       stepNumber={1}
     />
-    <AnalysisStep
+    <ProjectAnalysisStep
       onFinish={[Function]}
       onReset={[Function]}
       open={true}
@@ -172,7 +172,7 @@ exports[`guides for sonarcloud 1`] = `
       open={false}
       stepNumber={2}
     />
-    <AnalysisStep
+    <ProjectAnalysisStep
       onFinish={[Function]}
       onReset={[Function]}
       open={false}
@@ -246,7 +246,7 @@ exports[`guides for sonarcloud 2`] = `
       open={true}
       stepNumber={2}
     />
-    <AnalysisStep
+    <ProjectAnalysisStep
       onFinish={[Function]}
       onReset={[Function]}
       open={false}
@@ -321,7 +321,7 @@ exports[`guides for sonarcloud 3`] = `
       open={false}
       stepNumber={2}
     />
-    <AnalysisStep
+    <ProjectAnalysisStep
       onFinish={[Function]}
       onReset={[Function]}
       open={true}
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
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/projectOnboarding/__tests__/__snapshots__/TokenStep-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/TokenStep-test.tsx.snap
deleted file mode 100644 (file)
index 5484797..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"
-              />
-              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
deleted file mode 100644 (file)
index ed03adb..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.
- */
-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
deleted file mode 100644 (file)
index 6ebe42a..0000000
+++ /dev/null
@@ -1,78 +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 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
deleted file mode 100644 (file)
index 6dc321d..0000000
+++ /dev/null
@@ -1,70 +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 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 = [
-    'SonarScanner.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 = ['SonarScanner.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
deleted file mode 100644 (file)
index 02da9fd..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.
- */
-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
deleted file mode 100644 (file)
index 5663b1f..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.
- */
-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
deleted file mode 100644 (file)
index 65c4005..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 { 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
deleted file mode 100644 (file)
index cf6120a..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.
- */
-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
deleted file mode 100644 (file)
index 3d0b3b2..0000000
+++ /dev/null
@@ -1,66 +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 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
deleted file mode 100644 (file)
index 78b90f8..0000000
+++ /dev/null
@@ -1,52 +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 { 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
deleted file mode 100644 (file)
index b19e334..0000000
+++ /dev/null
@@ -1,28 +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 { 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
deleted file mode 100644 (file)
index a6f7c4f..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.
- */
-
-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
deleted file mode 100644 (file)
index 91ecab8..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.
- */
-
-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
deleted file mode 100644 (file)
index f0d130a..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.
- */
-
-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
deleted file mode 100644 (file)
index dbda16c..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.
- */
-
-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
deleted file mode 100644 (file)
index e541134..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.
- */
-
-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
deleted file mode 100644 (file)
index 78470bb..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.
- */
-
-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
deleted file mode 100644 (file)
index 16cc44a..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.
- */
-
-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
deleted file mode 100644 (file)
index 2be4e54..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.
- */
-
-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
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/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
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/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
deleted file mode 100644 (file)
index e072eef..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 [
-        "SonarScanner.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 [
-        "SonarScanner.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 [
-        "SonarScanner.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 [
-        "SonarScanner.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
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/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
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/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
deleted file mode 100644 (file)
index 740e26e..0000000
+++ /dev/null
@@ -1,29 +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"
-      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
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/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
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/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
deleted file mode 100644 (file)
index cf6a8de..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.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>
-`;
index 5c8f9c886418f14ce10c679b69042e0c9c237dbd..94c25460e0a03e3f0da972ee023b519b520f05e5 100644 (file)
@@ -26,7 +26,7 @@ const routes = [
       component: lazyLoad(
         () =>
           isSonarCloud()
-            ? import('./createProjectOnboarding/CreateProjectOnboarding')
+            ? import('./onboarding/OnboardingPage')
             : import('./projectOnboarding/ProjectOnboardingPage')
       )
     }
index 798fce21bcc1435ea71d9b69782b76f9d3943068..da2571a369d4a4c9e0db5df69352f3b568ce880f 100644 (file)
@@ -58,8 +58,7 @@
 .onboarding-choices {
   display: flex;
   justify-content: space-around;
-  padding-top: 44px;
-  padding-bottom: 44px;
+  padding: 44px 0;
   background-color: var(--barBackgroundColor);
 }
 
diff --git a/server/sonar-web/src/main/js/apps/tutorials/utils.ts b/server/sonar-web/src/main/js/apps/tutorials/utils.ts
new file mode 100644 (file)
index 0000000..f84785e
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+export interface LanguageConfig {
+  language?: string;
+  javaBuild?: string;
+  cFamilyCompiler?: string;
+  os?: string;
+  projectKey?: string;
+}
+
+export function isLanguageConfigured(config?: LanguageConfig) {
+  if (!config) {
+    return false;
+  }
+  const { language, javaBuild, cFamilyCompiler, os, projectKey } = config;
+  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;
+}
index 3592d9b0be049ba58d7c8b4b72b8af50f7272baa..7542f09a3aa95464d1d290e8208792224981c6f1 100644 (file)
@@ -26,7 +26,7 @@ export default function OnboardingPrivateIcon({
   size
 }: IconProps) {
   return (
-    <Icon className={className} size={size || 64} viewBox="">
+    <Icon className={className} size={size || 64} viewBox="0 0 64 64">
       <g fill="none" stroke={fill} strokeWidth="2">
         <path d="M2 59h60V13H2zm0-46h60V5H2zm3-4h2m2 0h2m2 0h2m2 0h42" />
         <path d="M59 34h-6l-2-4h-6l-2 5h-6l-2 2h-6l-2-4h-6l-2 5h-6l-2 4H5m1 14v-9m4 9v-6m4 6V43m4 13V45m4 11V42m4 14V39m4 17V41m4 15V46m4 10V40m4 16V44m4 12V37m4 19V38m4 18V43m4 13V39m-3-18h-2m-2 0h-2m-2 0h-2M9 29h14M9 33h7m17-12h8m-14 4h8m-8-4h4m-21 4h12v-4H10z" />
index d2090eed867250f6fa86b91220e379c9bb7d4122..d97972db6e7116c9d1c7947c0487dd423275d7f1 100644 (file)
@@ -26,7 +26,7 @@ export default function OnboardingProjectIcon({
   size
 }: IconProps) {
   return (
-    <Icon className={className} size={size || 64} viewBox="">
+    <Icon className={className} size={size || 64} viewBox="0 0 64 64">
       <g fill="none" fillRule="evenodd" stroke={fill} strokeWidth="2">
         <path d="M2 59h60V13H2zm0-46h60V5H2zm3-4h2m2 0h2m2 0h2m2 0h42" />
         <path d="M59 34h-6l-2-4h-6l-2 5h-6l-2 2h-6l-2-4h-6l-2 5h-6l-2 4H5m1 14v-9m4 9v-6m4 6V43m4 13V45m4 11V42m4 14V39m4 17V41m4 15V46m4 10V40m4 16V44m4 12V37m4 19V38m4 18V43m4 13V39m-3-18h-2m-2 0h-2m-2 0h-2M9 29h14M9 33h7m17-12h8m-14 4h8m-8-4h4m-21 4h12v-4H10z" />
index 6ce74838a0d707b4b3f1dc188bef90ddfd383327..023067e74a895d7a06b69808b2b4c2f57c9e2d30 100644 (file)
@@ -22,7 +22,7 @@ import Icon, { IconProps } from './Icon';
 
 export default function OnboardingTeamIcon({ className, fill = 'currentColor', size }: IconProps) {
   return (
-    <Icon className={className} size={size || 64} viewBox="">
+    <Icon className={className} size={size || 64} viewBox="0 0 64 64">
       <g fill="none" fillRule="evenodd" stroke={fill} strokeWidth="2">
         <path d="M32 9v5M11.5195 43.0898l7.48-4.091m33.481-18.0994l-7.48 4.1m-33.481-4.1l7.48 4.1M45 38.999l7.48 4.101M32 50v5m15-23c0 8.284-6.715 15-15 15s-15-6.716-15-15c0-8.285 6.715-15 15-15s15 6.715 15 15z" />
         <path d="M40 38c0 1.656-3.58 2-8 2s-8-.344-8-2m16 0v-3l-5-3-1-1m-10 7v-3l5-3 1-1m6-4c0 2.2-1.8 4-4 4s-4-1.8-4-4v-1c0-2.2 1.8-4 4-4s4 1.8 4 4v1zm-.0098-21.71c7.18 1.069 13.439 4.96 17.609 10.51m-17.609 42.91c7.18-1.07 13.439-4.96 17.609-10.51M6.6299 41.25c-1.06-2.88-1.63-6-1.63-9.25s.57-6.37 1.63-9.25m3.7705-6.9502c4.17-5.55 10.43-9.44 17.609-10.51m-17.609 42.9104c4.17 5.55 10.43 9.439 17.609 10.51M57.3701 22.75c1.06 2.88 1.63 6 1.63 9.25s-.57 6.37-1.63 9.25" />
index cc24ba79bafee8516cfa0d010ec9d9009e87c367..032c0255117828725ff319cf2ee16f4cf0e9afee 100644 (file)
   transition: all 0.2s ease;
 }
 
+.dropdown .button-link {
+  border-bottom: none;
+}
+
 .button-link:hover {
   background: transparent;
   color: var(--blue);
index 7f1c21325dfd44c1c152ec6c93c2b7896f0a2b42..633c34502892a3e8a1003178e2dd8e2c23f8bb52 100644 (file)
@@ -72,7 +72,7 @@ function parseFrontMatter(lines) {
  * @returns {string}
  */
 function filterContent(content) {
-  const { isSonarCloud } = require('../helpers/system');
+  const { isSonarCloud } = require('./system');
   const contentWithoutStatic = cutConditionalContent(content, 'static');
   return isSonarCloud()
     ? cutConditionalContent(contentWithoutStatic, 'sonarqube')
index 4d17bde0b09b990874bfd2cfb0c5d447cf0e49d3..9d3959260bf4b3a5bff6e831085ca1e65769af2e 100644 (file)
@@ -49,10 +49,6 @@ export function getPathUrlAsString(path: Location): string {
   return `${getBaseUrl()}${path.pathname}?${stringify(omitBy(path.query, isNil))}`;
 }
 
-export function getSonarCloudUrlAsString(location: Location) {
-  return 'https://sonarcloud.io' + getPathUrlAsString(location);
-}
-
 export function getProjectUrl(project: string, branch?: string): Location {
   return { pathname: '/dashboard', query: { id: project, branch } };
 }
index 2265fcc9f90de062faf3b105887ed959eeeebf9c..a2e9ef14fc60c8afa160c870bf2c8e17b3e33fdd 100644 (file)
@@ -1496,9 +1496,10 @@ my_account.create_new_organization=Create new organization
 #------------------------------------------------------------------------------
 provisioning.create_new_project=Create new project
 provisioning.no_analysis=No analysis has been performed since creation. The only available section is the configuration.
-provisioning.no_analysis.delete=Either you should retry analysis or simply {0}.
+provisioning.no_analysis.delete=Either you should retry analysis or simply {link}.
 provisioning.no_analysis.delete_project=delete the project
-provisioning.no_analysis_on_main_branch={branch} has not been analyzed yet.
+provisioning.no_analysis_on_main_branch="{branchName}" branch has not been analyzed yet.
+provisioning.no_analysis_on_main_branch.bad_configuration="{branchName}" branch has not been analyzed yet and you have multiple branches already. It looks like it is not your {branchType}, check your configuration.
 provisioning.only_provisioned=Only Provisioned
 provisioning.only_provisioned.tooltip=Provisioned projects are projects that have been created, but have not been analyzed yet.
 
@@ -2659,13 +2660,21 @@ onboarding.footer=Don't worry you can do all of this later. Just click the "+" i
 onboarding.project.header=Analyze a project
 onboarding.project.header.description=Want to quickly analyze a first project? Follow these {0} easy steps.
 
+onboarding.project_analysis.header=Analyze your project
+onboarding.project_analysis.description=We initialized your project on SonarCloud, now it's up to you to launch analyses!
+onboarding.project_analysis.commands_for_analysis=Bellow are the commands to use to do an analysis.
+onboarding.project_analysis.guide_to_integrate_piplines=follow the guide to integrating with Pipelines
+onboarding.project_analysis.guide_to_integrate_travis=follow the guide to integrating with Travis CI
+onboarding.project_analysis.guide_to_integrate_vsts=follow the guide to integrating with VSTS
+onboarding.project_analysis.simply_link=Simply {link}.
+onboarding.project_analysis.suggestions.bitbucket=If you are using Bitbucket Cloud Pipelines, the SonarCloud App makes it easier to run these commands with your CI process.
+onboarding.project_analysis.suggestions.github=If you are using Travis CI, the SonarCloud Travis Add-on makes it easier to run these commands with your CI process.
+
 onboarding.create_project.header=Create project(s)
 onboarding.create_project.already_imported=Repository already imported
-onboarding.create_project.beta_feature_x=This feature is being beta tested. We offer to create projects from your {0} repositories only for public personal projects on your personal SonarCloud organization. For other kind of projects please create them maually.
+onboarding.create_project.beta_feature_x=This feature is being beta tested. We offer to create projects from your {0} repositories only for public personal projects on your personal SonarCloud organization. For other kind of projects please create them manually.
 onboarding.create_project.create_manually=Create manually
 onboarding.create_project.create_new_org=I want to create another organization
-onboarding.create_project.create_project=Create project
-onboarding.create_project.create_projects=Create projects
 onboarding.create_project.install_app_x=We need you to install the Sonarcloud {0} application in order to select which repositories you want to analyze.
 onboarding.create_project.install_app_x.button=Install SonarCloud {0} application
 onboarding.create_project.organization=Organization
@@ -2706,9 +2715,11 @@ onboarding.organization.key_requirement=2 to 32 characters. All chars must be lo
 onboarding.project_key_requirement=Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit. 400 characters max.
 
 onboarding.analysis.header=Run analysis on your project
+onboarding.analysis.auto_refresh_after_analysis=Once the analysis is completed, this page will automatically refresh and you will be able to browse the analysis results.
 onboarding.analysis.browse_url_after_analysis=Once the analysis is completed, you will be able to browse your project at the URL displayed at the end of the logs.
 
 onboarding.language=What is your project's main language?
+onboarding.language.header=Which primary language are you using?
 onboarding.language.java=Java
 onboarding.language.java.build_technology=You are developing primarily in Java: what is your build technology?
 onboarding.language.java.build_technology.maven=Maven