Browse Source

Update configure analysis screen for GitHub on SonarCloud (#1728)

tags/8.0
Siegfried Ehret 4 years ago
parent
commit
f8422ab807
100 changed files with 5654 additions and 1079 deletions
  1. 1
    1
      server/sonar-docs/src/pages/sonarcloud/autoscan.md
  2. 6
    1
      server/sonar-web/.eslintrc
  3. 14
    13
      server/sonar-web/package.json
  4. 1
    0
      server/sonar-web/public/images/sonarcloud/analysis/Waiting-for-analysis.svg
  5. 1
    0
      server/sonar-web/public/images/sonarcloud/analysis/galaxy.svg
  6. 1
    0
      server/sonar-web/public/images/sonarcloud/analysis/helmet.svg
  7. 1
    0
      server/sonar-web/public/images/sonarcloud/analysis/manual.svg
  8. 1
    0
      server/sonar-web/public/images/sonarcloud/analysis/rocket.svg
  9. 8
    1
      server/sonar-web/src/main/js/api/alm-integration.ts
  10. 4
    0
      server/sonar-web/src/main/js/app/styles/components/columns.css
  11. 2
    1
      server/sonar-web/src/main/js/app/styles/style.css
  12. 1
    0
      server/sonar-web/src/main/js/app/utils/getHistory.ts
  13. 1
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPageSonarCloud-test.tsx
  14. 1
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPageSonarCloud-test.tsx.snap
  15. 3
    1
      server/sonar-web/src/main/js/apps/overview/components/App.tsx
  16. 18
    10
      server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx
  17. 0
    8
      server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx
  18. 44
    0
      server/sonar-web/src/main/js/apps/tutorials/__tests__/utils-test.ts
  19. 1
    1
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx
  20. 119
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSonarCloud.css
  21. 364
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSonarCloud.tsx
  22. 73
    61
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx
  23. 177
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSonarCloud-test.tsx
  24. 26
    4
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSuggestion-test.tsx
  25. 207
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorialSonarCloud-test.tsx.snap
  26. 4
    4
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorialSuggestion-test.tsx.snap
  27. 59
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/utils-test.tsx
  28. 71
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/AutoScanAlert.tsx
  29. 126
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/ConfigureWithAutoScan.tsx
  30. 54
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/ConfigureWithLocalScanner.tsx
  31. 54
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/ConfigureWithOtherCI.tsx
  32. 119
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/ConfigureWithTravis.tsx
  33. 30
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/AutoScanAlert-test.tsx
  34. 41
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/ConfigureOtherCI-test.tsx
  35. 41
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/ConfigureWithAutoScan-test.tsx
  36. 41
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/ConfigureWithLocalScanner-test.tsx
  37. 71
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/ConfigureWithTravis-test.tsx
  38. 37
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/__snapshots__/AutoScanAlert-test.tsx.snap
  39. 53
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/__snapshots__/ConfigureOtherCI-test.tsx.snap
  40. 40
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/__snapshots__/ConfigureWithAutoScan-test.tsx.snap
  41. 53
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/__snapshots__/ConfigureWithLocalScanner-test.tsx.snap
  42. 84
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/__snapshots__/ConfigureWithTravis-test.tsx.snap
  43. 93
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/CreateSonarPropertiesStep.tsx
  44. 235
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/EditTokenModal.tsx
  45. 111
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/EditTravisYmlStep.tsx
  46. 112
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/EncryptYourTokenStep.tsx
  47. 39
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/CreateSonarPropertiesStep-test.tsx
  48. 132
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/EditTokenModal-test.tsx
  49. 49
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/EditTravisYmlStep-test.tsx
  50. 43
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/EncryptYourTokenStep-test.tsx
  51. 27
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/__snapshots__/CreateSonarPropertiesStep-test.tsx.snap
  52. 56
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/__snapshots__/EditTokenModal-test.tsx.snap
  53. 59
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/__snapshots__/EditTravisYmlStep-test.tsx.snap
  54. 13
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/__snapshots__/EncryptYourTokenStep-test.tsx.snap
  55. 119
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/utils.ts
  56. 72
    0
      server/sonar-web/src/main/js/apps/tutorials/components/AnalyzeTutorialDone.tsx
  57. 39
    0
      server/sonar-web/src/main/js/apps/tutorials/components/BuildSystemForm.tsx
  58. 38
    42
      server/sonar-web/src/main/js/apps/tutorials/components/LanguageForm.tsx
  59. 1
    1
      server/sonar-web/src/main/js/apps/tutorials/components/NewProjectForm.tsx
  60. 6
    6
      server/sonar-web/src/main/js/apps/tutorials/components/ProjectAnalysisStep.tsx
  61. 148
    0
      server/sonar-web/src/main/js/apps/tutorials/components/ProjectAnalysisStepFromBuildTool.tsx
  62. 55
    0
      server/sonar-web/src/main/js/apps/tutorials/components/RenderOptions.tsx
  63. 2
    2
      server/sonar-web/src/main/js/apps/tutorials/components/Step.tsx
  64. 4
    18
      server/sonar-web/src/main/js/apps/tutorials/components/TokenStep.tsx
  65. 26
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/AnalyzeTutorialDone-test.tsx
  66. 28
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/BuildSystemForm-test.tsx
  67. 0
    62
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/LanguageForm-test.tsx
  68. 3
    3
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/NewProjectForm-test.tsx
  69. 31
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/ProjectAnalysisStep-test.tsx
  70. 69
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/ProjectAnalysisStepFromBuildTool-test.tsx
  71. 1
    1
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TokenStep-test.tsx
  72. 64
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/AnalyzeTutorialDone-test.tsx.snap
  73. 19
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/BuildSystemForm-test.tsx.snap
  74. 25
    540
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/LanguageForm-test.tsx.snap
  75. 135
    279
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/NewProjectForm-test.tsx.snap
  76. 13
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/ProjectAnalysisStep-test.tsx.snap
  77. 91
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/ProjectAnalysisStepFromBuildTool-test.tsx.snap
  78. 9
    12
      server/sonar-web/src/main/js/apps/tutorials/components/commands/AnalysisCommand.tsx
  79. 175
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/AnalysisCommandCustom.tsx
  80. 174
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/AnalysisCommandOtherCI.tsx
  81. 207
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/AnalysisCommandTravis.tsx
  82. 1
    1
      server/sonar-web/src/main/js/apps/tutorials/components/commands/ClangGCC.tsx
  83. 132
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/ClangGCCCustom.tsx
  84. 69
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/JavaGradleCustom.tsx
  85. 130
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/JavaMavenCustom.tsx
  86. 96
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/OtherCustom.tsx
  87. 58
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/ClangGCCCustom-test.tsx
  88. 67
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/JavaGradleCustom-test.tsx
  89. 80
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/JavaMavenCustom-test.tsx
  90. 43
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/OtherCustom-test.tsx
  91. 85
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/__snapshots__/ClangGCCCustom-test.tsx.snap
  92. 68
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/__snapshots__/JavaGradleCustom-test.tsx.snap
  93. 83
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/__snapshots__/JavaMavenCustom-test.tsx.snap
  94. 54
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/__snapshots__/OtherCustom-test.tsx.snap
  95. 1
    1
      server/sonar-web/src/main/js/apps/tutorials/components/commands/DotNet.tsx
  96. 1
    1
      server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaGradle.tsx
  97. 1
    1
      server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaMaven.tsx
  98. 1
    1
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Other.tsx
  99. 108
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/OtherCI/ClangGCCOtherCI.tsx
  100. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/OtherCI/OtherOtherCI.tsx

+ 1
- 1
server/sonar-docs/src/pages/sonarcloud/autoscan.md View File

@@ -49,7 +49,7 @@ If you're starting from scratch:

1. Do the [setup for your project](/#sonarcloud#/projects/create) (from the `+ > Analyze new project` top right menu)
* ![](/images/exclamation.svg) Remember that your project must absolutely be created by selecting a GitHub repository - otherwise it won't work.
1. Once the setup is done on SonarCloud, you end up on the project home page which shows a tutorial. Ignore it and simply add a `.sonarcloud.properties` file in the base directory of your default branch (or on a PR which targets this default branch).
1. Once the setup is done on SonarCloud, you end up on the project home page which shows a tutorial. Ignore it and simply add a `.sonarcloud.properties` file in the base directory of your default branch or on a PR.
1. After a while, the analysis results will be visible in SonarCloud (and your PR will be annotated with comments if you pushed the file on a PR)

Here are the supported optional settings for the `.sonarcloud.properties` file:

+ 6
- 1
server/sonar-web/.eslintrc View File

@@ -1,6 +1,11 @@
{
"extends": "sonarqube",
"plugins": [
"react-hooks"
],
"rules": {
"camelcase": "off"
"camelcase": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}

+ 14
- 13
server/sonar-web/package.json View File

@@ -5,7 +5,6 @@
"repository": "SonarSource/sonarqube",
"license": "LGPL-3.0",
"dependencies": {
"@types/dompurify": "^0.0.32",
"classnames": "2.2.6",
"clipboard": "2.0.1",
"core-js": "3.0.0",
@@ -17,7 +16,7 @@
"d3-shape": "1.2.2",
"d3-zoom": "1.7.3",
"date-fns": "1.29.0",
"dompurify": "^1.0.11",
"dompurify": "1.0.11",
"formik": "1.2.0",
"history": "3.3.0",
"intl-relativeformat": "2.1.0",
@@ -26,10 +25,10 @@
"lunr": "2.3.4",
"mdast-util-toc": "2.1.0",
"prop-types": "15.7.2",
"react": "16.8.5",
"react": "16.8.6",
"react-countup": "4.1.1",
"react-day-picker": "7.3.0",
"react-dom": "16.8.5",
"react-dom": "16.8.6",
"react-draggable": "3.2.1",
"react-helmet": "5.2.0",
"react-intl": "2.8.0",
@@ -66,13 +65,14 @@
"@types/d3-selection": "1.3.2",
"@types/d3-shape": "1.2.4",
"@types/d3-zoom": "1.7.2",
"@types/enzyme": "3.9.1",
"@types/jest": "24.0.11",
"@types/dompurify": "0.0.32",
"@types/enzyme": "3.9.3",
"@types/jest": "24.0.15",
"@types/keymaster": "1.6.28",
"@types/lodash": "4.14.123",
"@types/prop-types": "15.7.0",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/react": "16.8.22",
"@types/react-dom": "16.8.4",
"@types/react-helmet": "5.0.8",
"@types/react-intl": "2.3.17",
"@types/react-modal": "3.8.1",
@@ -85,7 +85,7 @@
"@typescript-eslint/parser": "1.5.0",
"autoprefixer": "9.5.0",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "24.5.0",
"babel-jest": "24.8.0",
"babel-loader": "8.0.5",
"babel-plugin-dynamic-import-node": "2.2.0",
"babel-plugin-lodash": "3.3.4",
@@ -94,8 +94,8 @@
"copy-webpack-plugin": "5.0.1",
"css-loader": "2.1.1",
"cssnano": "4.1.10",
"enzyme": "3.9.0",
"enzyme-adapter-react-16": "1.10.0",
"enzyme": "3.10.0",
"enzyme-adapter-react-16": "1.14.0",
"enzyme-to-json": "3.3.5",
"escape-string-regexp": "1.0.5",
"eslint": "5.15.3",
@@ -104,12 +104,13 @@
"eslint-plugin-jsx-a11y": "6.2.1",
"eslint-plugin-promise": "4.0.1",
"eslint-plugin-react": "7.12.4",
"eslint-plugin-react-hooks": "1.6.0",
"eslint-plugin-sonarjs": "0.3.0",
"expose-loader": "0.7.5",
"glob": "7.1.3",
"glob-promise": "3.4.0",
"html-webpack-plugin": "3.2.0",
"jest": "24.5.0",
"jest": "24.8.0",
"lint-staged": "7.3.0",
"lodash-webpack-plugin": "0.11.5",
"mini-css-extract-plugin": "0.6.0",
@@ -120,7 +121,7 @@
"raw-loader": "2.0.0",
"react-dev-utils": "5.0.1",
"react-error-overlay": "1.0.7",
"react-test-renderer": "16.8.5",
"react-test-renderer": "16.8.6",
"remark": "9.0.0",
"remark-react": "4.0.3",
"style-loader": "0.23.1",

+ 1
- 0
server/sonar-web/public/images/sonarcloud/analysis/Waiting-for-analysis.svg View File

@@ -0,0 +1 @@
<svg width="90" height="90" xmlns="http://www.w3.org/2000/svg"><g stroke="#236A97" stroke-width="2" fill="none" fill-rule="evenodd"><path d="M32.087 83H12V12h66v71H57.913"/><path d="M57.79 78H72V18H18v60h14.21M12 12h66V1H12zM17 7h3M23 7h3M28 7h3M34 7h40"/><path d="M56.355 48.275c0 6.258-5.083 11.327-11.355 11.327s-11.355-5.069-11.355-11.327S38.728 36.948 45 36.948s11.355 5.069 11.355 11.327z"/><path d="M61.336 55.028a7.07 7.07 0 0 1-1.276 8.27 7.114 7.114 0 0 1-8.29 1.273A7.101 7.101 0 0 1 45 69.513a7.098 7.098 0 0 1-6.77-4.942 7.114 7.114 0 0 1-8.29-1.274 7.064 7.064 0 0 1-1.277-8.269c-2.867-.906-4.954-3.58-4.954-6.754 0-3.171 2.087-5.846 4.954-6.752a7.07 7.07 0 0 1 1.277-8.27 7.114 7.114 0 0 1 8.29-1.273A7.1 7.1 0 0 1 45 27.036a7.102 7.102 0 0 1 6.77 4.943 7.114 7.114 0 0 1 8.29 1.274 7.07 7.07 0 0 1 1.276 8.269c2.869.906 4.954 3.58 4.954 6.752a7.084 7.084 0 0 1-4.954 6.754z"/><path d="M36.484 69.514V87.92L45 83.673l8.516 4.247V69.514M45 83.673v-7.08M45 42.611V53.94M45 42.611a4.254 4.254 0 0 1-4.258 4.248M81.903 18.54H89v59.47h-7.097M89 29.868h-7.097M84.742 24.205h-2.839M8.097 18.54H1v59.47h7.097M8.097 29.868H1M8.097 24.205H5.258"/></g></svg>

+ 1
- 0
server/sonar-web/public/images/sonarcloud/analysis/galaxy.svg View File

@@ -0,0 +1 @@
<svg width="58" height="56" xmlns="http://www.w3.org/2000/svg"><g fill="#4680A5" fill-rule="nonzero"><path d="M28.5 19a9.503 9.503 0 0 0-8.088 4.523 1.198 1.198 0 0 0-.102.172A9.438 9.438 0 0 0 19 28.5c0 1.353.286 2.64.798 3.806.022.061.049.12.08.177C21.388 35.737 24.684 38 28.5 38a9.498 9.498 0 0 0 7.925-4.269 1.2 1.2 0 0 0 .117-.183 9.44 9.44 0 0 0 1.453-4.822c.003-.037.005-.075.005-.114l-.002-.037L38 28.5c0-5.238-4.261-9.5-9.5-9.5zm7.127 8.464h-4.152a5.414 5.414 0 0 0-3.854 1.596l-.788.788a3.13 3.13 0 0 1-2.228.923h-2.938a7.16 7.16 0 0 1-.37-2.271 7.16 7.16 0 0 1 .73-3.156h7.28a1.15 1.15 0 0 0 0-2.298h-5.503a7.17 7.17 0 0 1 4.696-1.748c3.62 0 6.623 2.684 7.127 6.166zm-12.69 5.605h1.668a5.414 5.414 0 0 0 3.853-1.596l.788-.788a3.132 3.132 0 0 1 2.229-.923h4.114a7.147 7.147 0 0 1-.729 2.113h-2.545a1.15 1.15 0 0 0 0 2.298h.615a7.163 7.163 0 0 1-4.43 1.53 7.192 7.192 0 0 1-5.564-2.634z"/><path d="M42.177 15.924c-2.408-2.636-5.517-4.663-8.99-5.863a1.117 1.117 0 0 0-1.423.708c-.2.594.113 1.24.699 1.442 3.123 1.078 5.913 2.896 8.07 5.255 2.287 2.504 3.705 5.49 4.1 8.633.59 4.716-.915 9.5-4.13 13.127-3.111 3.509-7.465 5.503-11.985 5.503-.1 0-.2 0-.3-.003-3.427-.067-6.713-1.208-9.362-3.233.34-.625.535-1.343.535-2.107 0-2.414-1.94-4.379-4.323-4.379-.451 0-.92.072-1.33.202 0 0-1.028-1.91-1.404-5.157a16.581 16.581 0 0 1 .29-5.422c.788-3.618 2.794-6.913 5.643-9.24 1.657-1.352 3.607-2.362 5.643-2.981.593-.18.93-.813.752-1.414a1.118 1.118 0 0 0-1.395-.761c-7.101 2.158-12.34 8.447-13.154 15.923-.382 3.51.207 7.119 1.757 10.286a4.395 4.395 0 0 0-1.124 2.943c0 2.415 1.938 4.38 4.322 4.38.813 0 1.574-.23 2.225-.627 3.062 2.416 6.89 3.78 10.882 3.857.114.003.229.004.343.004 5.156 0 10.116-2.268 13.652-6.256 3.651-4.118 5.36-9.56 4.686-14.93-.454-3.617-2.071-7.037-4.679-9.89zm-29.19 23.462c0-1.162.934-2.107 2.08-2.107 1.148 0 2.082.945 2.082 2.107 0 1.163-.934 2.108-2.081 2.108-1.147 0-2.08-.945-2.08-2.108z"/><path d="M58 17.675c0-1.44-.559-2.793-1.573-3.811a5.329 5.329 0 0 0-3.799-1.579c-.51 0-1.008.072-1.486.21a28.132 28.132 0 0 0-9.442-8.84A27.836 27.836 0 0 0 27.905 0C20.45 0 13.444 2.912 8.173 8.201 2.903 13.489 0 20.521 0 28.001c0 7.478 2.902 14.51 8.173 19.798C13.443 53.088 20.451 56 27.904 56c7.454 0 14.462-2.912 19.732-8.2 5.27-5.29 8.173-12.32 8.173-19.8 0-1.89-.19-3.776-.564-5.617A5.402 5.402 0 0 0 58 17.675zm-4.93 5.373A26.04 26.04 0 0 1 53.543 28c0 6.872-2.667 13.332-7.51 18.192-4.843 4.859-11.281 7.535-18.13 7.535-6.848 0-13.286-2.676-18.13-7.535C4.934 41.332 2.267 34.872 2.267 28S4.933 14.668 9.775 9.808c4.843-4.859 11.281-7.535 18.13-7.535a25.57 25.57 0 0 1 12.674 3.358 25.852 25.852 0 0 1 8.56 7.947 5.364 5.364 0 0 0-1.882 4.097c0 1.44.558 2.793 1.573 3.811a5.39 5.39 0 0 0 4.24 1.562zm1.755-3.17c-.365.367-.798.621-1.26.767a3.13 3.13 0 0 1-3.499-1.208 3.159 3.159 0 0 1-.252-3.08 3.102 3.102 0 0 1 5.01-.885 3.127 3.127 0 0 1 0 4.407z"/><path d="M26.073 10.382c.158.385.558.639.975.617.414-.022.776-.301.905-.694a1.01 1.01 0 0 0-.344-1.1 1.013 1.013 0 0 0-1.166-.039 1.013 1.013 0 0 0-.37 1.216z"/></g></svg>

+ 1
- 0
server/sonar-web/public/images/sonarcloud/analysis/helmet.svg View File

@@ -0,0 +1 @@
<svg width="59" height="44" xmlns="http://www.w3.org/2000/svg"><g fill="#4680A5" fill-rule="nonzero"><path d="M57.025 34.892h-.064v-5.195c0-12.966-8.998-24.204-21.495-26.99A2.994 2.994 0 0 0 34.64.92 2.96 2.96 0 0 0 32.487 0h-5.974a2.96 2.96 0 0 0-2.154.92 2.995 2.995 0 0 0-.825 1.788C11.037 5.493 2.04 16.73 2.04 29.697v5.195h-.064C.885 34.892 0 35.784 0 36.88v1.309c0 .865.544 1.626 1.354 1.893C9.03 42.608 19.027 44 29.5 44c10.473 0 20.47-1.392 28.146-3.918A1.981 1.981 0 0 0 59 38.189V36.88a1.984 1.984 0 0 0-1.975-1.988zM25.735 2.257c.205-.215.482-.333.778-.333h5.974a1.085 1.085 0 0 1 1.083 1.128l-.88 25.656a1.082 1.082 0 0 1-1.082 1.053h-4.216a1.082 1.082 0 0 1-1.082-1.053l-.88-25.656c-.01-.298.098-.58.304-.795zm31.354 35.932c0 .026-.012.056-.037.064-7.49 2.465-17.275 3.823-27.552 3.823-10.277 0-20.062-1.358-27.552-3.823-.025-.009-.036-.038-.036-.064V36.88c0-.035.028-.064.063-.064h36.7c.528 0 .956-.43.956-.962a.959.959 0 0 0-.956-.962H3.95v-5.195c0-9.67 5.38-18.305 13.5-22.687l2.075 14.614a.958.958 0 0 0 1.08.816.961.961 0 0 0 .812-1.088l-2.16-15.227a25.222 25.222 0 0 1 4.317-1.452l.825 24.102a2.992 2.992 0 0 0 2.993 2.91h4.216a2.993 2.993 0 0 0 2.993-2.91l.826-24.102c1.498.358 2.941.846 4.317 1.453l-2.16 15.226a.961.961 0 0 0 .947 1.098.958.958 0 0 0 .945-.826L41.55 7.01c8.121 4.382 13.5 13.017 13.5 22.687v5.195h-6.18a.959.959 0 0 0-.956.962c0 .532.428.962.955.962h8.156c.035 0 .064.03.064.065v1.308z"/><path d="M45.032 35h-2.064c-.535 0-.968.448-.968 1s.433 1 .968 1h2.064c.535 0 .968-.448.968-1s-.433-1-.968-1zM22.987 27.896l-.397-2.065c-.102-.53-.76-.897-1.472-.821-.712.075-1.206.566-1.105 1.094l.397 2.065c.093.483.65.831 1.287.831.061 0 .123-.003.185-.01.712-.075 1.206-.565 1.105-1.094zM37.882 25.01c-.711-.076-1.37.292-1.472.82l-.397 2.066c-.101.529.393 1.019 1.105 1.094.062.007.124.01.185.01.637 0 1.194-.348 1.287-.83l.397-2.066c.101-.528-.393-1.019-1.105-1.094z"/></g></svg>

+ 1
- 0
server/sonar-web/public/images/sonarcloud/analysis/manual.svg View File

@@ -0,0 +1 @@
<svg width="62" height="76" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M0 76h62V0H0z"/></defs><g fill="none" fill-rule="evenodd"><path d="M24 37v4.667c0 .597.244 1.194.733 1.65A2.584 2.584 0 0 0 26.5 44c.64 0 1.279-.227 1.767-.684a2.246 2.246 0 0 0 .733-1.65V37" stroke="#236A97" stroke-width="2"/><path d="M28 37v5.714c0 .585.244 1.17.733 1.616.488.447 1.127.67 1.767.67.64 0 1.279-.223 1.767-.67A2.18 2.18 0 0 0 33 42.714V37M33 36.53v14.117c0 .602.244 1.205.733 1.664.488.46 1.127.689 1.767.689.64 0 1.279-.23 1.767-.69A2.273 2.273 0 0 0 38 50.648V33" stroke="#236A97" stroke-width="2"/><path d="M23.8 36.958v3.625c0 .619-.234 1.238-.703 1.709A2.382 2.382 0 0 1 21.4 43a2.382 2.382 0 0 1-1.697-.708A2.414 2.414 0 0 1 19 40.583V23.667L21.4 14h16.8v3.625l4.8 8.458v7.25c0 .619-.234 1.238-.703 1.709a2.382 2.382 0 0 1-1.697.708 2.382 2.382 0 0 1-1.697-.708 2.414 2.414 0 0 1-.703-1.709V28.5M40 0v14H19V0M36 10V8M56 47V29M60 41l-4.5 5-4.5-5M56 26v-2M56 21v-2M6 47V29M10 41l-4.5 5L1 41M6 26v-2M6 21v-2M41.8 54h18c.307 0 .614.114.848.342.234.227.352.526.352.825v18.666c0 .299-.118.598-.352.825A1.214 1.214 0 0 1 59.8 75H2.2c-.307 0-.614-.114-.848-.342A1.147 1.147 0 0 1 1 73.833V55.167c0-.299.118-.598.352-.825.234-.228.54-.342.848-.342h27.6M25 64h2M30 64h2M35 64h2" stroke="#236A97" stroke-width="2"/><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><path stroke="#236A97" stroke-width="2" mask="url(#b)" d="M6 70h50V58H6z"/></g></svg>

+ 1
- 0
server/sonar-web/public/images/sonarcloud/analysis/rocket.svg View File

@@ -0,0 +1 @@
<svg width="62" height="62" xmlns="http://www.w3.org/2000/svg"><g fill="#4680A5" fill-rule="nonzero"><path d="M32.561 29.44a1.5 1.5 0 1 0-2.122 2.12 1.5 1.5 0 0 0 2.122-2.12zM48.241 13.759a5.998 5.998 0 0 0-8.487 0 6.01 6.01 0 0 0 0 8.487 6.009 6.009 0 0 0 8.487 0 5.998 5.998 0 0 0 0-8.487zm-1.697 6.79a3.605 3.605 0 0 1-5.092 0 3.606 3.606 0 0 1 0-5.093 3.599 3.599 0 0 1 5.092 0 3.599 3.599 0 0 1 0 5.092z"/><path d="M61.648.352a1.198 1.198 0 0 0-.893-.351c-.272.01-6.743.27-14.435 2.988-6.164 2.177-11.301 5.293-15.27 9.262a45.74 45.74 0 0 0-2.583 2.812c-3.924-2.31-7.069-1.586-9.057-.522-4.577 2.45-7.41 9.1-7.41 13.858a1.202 1.202 0 0 0 2.052.85c2.414-2.414 5.392-2.263 6.364-2.135l.421.421a42.277 42.277 0 0 0-1.985 5.838c-.177.69-.117 1.42.146 2.088a11.383 11.383 0 0 0-3.246 2.288c-3.094 3.094-3.723 10.635-3.748 10.954a1.202 1.202 0 0 0 1.293 1.293c.32-.025 7.86-.653 10.954-3.747a11.383 11.383 0 0 0 2.288-3.245 3.394 3.394 0 0 0 2.09.144 42.282 42.282 0 0 0 5.836-1.985l.421.421c.128.973.279 3.95-2.135 6.364a1.202 1.202 0 0 0 .85 2.052c4.757 0 11.409-2.833 13.858-7.41 1.064-1.988 1.788-5.133-.522-9.057a45.756 45.756 0 0 0 2.813-2.583c3.969-3.969 7.084-9.107 9.26-15.27C61.73 7.988 61.99 1.517 62 1.245a1.202 1.202 0 0 0-.352-.893zm-46.96 25.462c.746-3.614 2.915-7.579 5.857-9.154 1.967-1.052 4.116-.949 6.401.302a48.6 48.6 0 0 0-5.05 8.232c-.018-.01-.234-.283-.636-.374-.166-.037-3.346-.72-6.573.994zm7.863 18.735c-1.653 1.653-5.53 2.524-7.98 2.88.356-2.45 1.228-6.327 2.88-7.98.926-.925 2-1.627 3.078-2.022l4.045 4.045c-.396 1.078-1.097 2.151-2.023 3.077zm5.48-3.73c-.348.09-.75-.041-1.05-.34a22337.974 22337.974 0 0 0-5.459-5.459c-.3-.3-.43-.703-.341-1.05a39.03 39.03 0 0 1 1.5-4.59l9.94 9.939a39.003 39.003 0 0 1-4.59 1.5zm17.309.636c-1.575 2.942-5.54 5.111-9.153 5.858 1.152-2.168 1.406-4.928.985-6.602-.098-.388-.351-.584-.366-.607a48.618 48.618 0 0 0 8.232-5.05c1.251 2.286 1.354 4.435.302 6.401zm2.71-12.205a43.628 43.628 0 0 1-3.467 3.11 46.18 46.18 0 0 1-9.581 5.94L23.7 27a46.172 46.172 0 0 1 5.94-9.583 43.619 43.619 0 0 1 3.11-3.467c3.56-3.56 8.156-6.395 13.662-8.435l10.072 10.072c-2.04 5.506-4.874 10.102-8.435 13.663zm9.3-16.198L48.948 4.65c4.578-1.422 8.56-1.954 10.547-2.146-.191 1.988-.724 5.97-2.145 10.547z"/><path d="M28.636 50.364a1.243 1.243 0 0 0-1.758 0l-3.514 3.515a1.242 1.242 0 1 0 1.758 1.757l3.514-3.515a1.242 1.242 0 0 0 0-1.757zM11.636 33.364a1.242 1.242 0 0 0-1.757 0l-3.515 3.514a1.243 1.243 0 0 0 1.757 1.758l3.515-3.514a1.243 1.243 0 0 0 0-1.758zM20.666 52.334a1.14 1.14 0 0 0-1.612 0l-7.72 7.72a1.14 1.14 0 0 0 1.612 1.612l7.72-7.72a1.14 1.14 0 0 0 0-1.612zM10.666 52.334a1.14 1.14 0 0 0-1.612 0l-7.72 7.72a1.14 1.14 0 0 0 1.612 1.612l7.72-7.72a1.14 1.14 0 0 0 0-1.612zM9.666 41.334a1.14 1.14 0 0 0-1.612 0l-7.72 7.72a1.14 1.14 0 1 0 1.612 1.612l7.72-7.72a1.14 1.14 0 0 0 0-1.612zM37.657 24.343a1.172 1.172 0 0 0-1.657 0L34.343 26A1.172 1.172 0 0 0 36 27.657L37.657 26c.457-.457.457-1.2 0-1.657z"/></g></svg>

+ 8
- 1
server/sonar-web/src/main/js/api/alm-integration.ts View File

@@ -17,8 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { getJSON, postJSON, post, requestTryAndRepeatUntil } from '../helpers/request';
import { getJSON, postJSON, post, requestTryAndRepeatUntil, getCorsJSON } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
import { AlmLanguagesStats } from '../apps/tutorials/analyzeProject/utils';

export function bindAlmOrganization(data: { installationId: string; organization: string }) {
return post('/api/alm_integration/bind_organization', data).catch(throwGlobalError);
@@ -75,3 +76,9 @@ export function provisionProject(data: {
installationKeys: data.installationKeys.join(',')
}).catch(throwGlobalError);
}

export function getGithubLanguages(url: string): Promise<AlmLanguagesStats> {
// We don't want to throwGlobalError
const apiUrl = url.replace('https://github.com/', 'https://api.github.com/repos/');
return getCorsJSON(`${apiUrl}/languages`);
}

+ 4
- 0
server/sonar-web/src/main/js/app/styles/components/columns.css View File

@@ -53,6 +53,10 @@
margin-left: 20px;
}

.flex-column-full {
width: 100%;
}

.flex-column-half {
width: 50%;
}

+ 2
- 1
server/sonar-web/src/main/js/app/styles/style.css View File

@@ -136,7 +136,8 @@
}

.rule-desc code,
.markdown code {
.markdown code,
code.rule {
padding: 0.2em 0.45em;
margin: 0;
background-color: rgba(0, 0, 0, 0.06);

+ 1
- 0
server/sonar-web/src/main/js/app/utils/getHistory.ts View File

@@ -23,6 +23,7 @@ import { createHistory, History } from 'history';
let history: History;

function ensureHistory() {
// eslint-disable-next-line react-hooks/rules-of-hooks
history = useRouterHistory(createHistory)({
// do not use `getBaseUrl` from `helpers/urls` to no import this file with all its dependecies
basename: (window as any).baseUrl

+ 1
- 1
server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPageSonarCloud-test.tsx View File

@@ -60,7 +60,7 @@ it('should render correctly', async () => {
expect(wrapper).toMatchSnapshot();
});

it('should render with Manual creation only', () => {
it('should render with Custom creation only', () => {
expect(getWrapper({ currentUser: { ...user, externalProvider: 'microsoft' } })).toMatchSnapshot();
});


+ 1
- 1
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPageSonarCloud-test.tsx.snap View File

@@ -100,7 +100,7 @@ exports[`should render correctly 2`] = `
</Fragment>
`;

exports[`should render with Manual creation only 1`] = `
exports[`should render with Custom creation only 1`] = `
<Fragment>
<HelmetWrapper
defer={true}

+ 3
- 1
server/sonar-web/src/main/js/apps/overview/components/App.tsx View File

@@ -20,13 +20,15 @@
import * as React from 'react';
import { Helmet } from 'react-helmet';
import OverviewApp from './OverviewApp';
import EmptyOverview from './EmptyOverview';
import ReviewApp from '../pullRequests/ReviewApp';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import { withRouter, Router } from '../../../components/hoc/withRouter';
import { getProjectUrl, getBaseUrl, getPathUrlAsString } from '../../../helpers/urls';
import { isSonarCloud } from '../../../helpers/system';
import { isShortLivingBranch, isPullRequest } from '../../../helpers/branches';
import { lazyLoad } from '../../../components/lazyLoad';

const EmptyOverview = lazyLoad(() => import('./EmptyOverview'));

interface Props {
branchLike?: T.BranchLike;

+ 18
- 10
server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx View File

@@ -21,12 +21,14 @@ import * as React from 'react';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import AnalyzeTutorial from '../../tutorials/analyzeProject/AnalyzeTutorial';
import AnalyzeTutorialSonarCloud from '../../tutorials/analyzeProject/AnalyzeTutorialSonarCloud';
import MetaContainer from '../meta/MetaContainer';
import { isLongLivingBranch, isBranch, isMainBranch } from '../../../helpers/branches';
import { translate } from '../../../helpers/l10n';
import { isLoggedIn } from '../../../helpers/users';
import { getCurrentUser, Store } from '../../../store/rootReducer';
import { Alert } from '../../../components/ui/Alert';
import { isSonarCloud } from '../../../helpers/system';

interface OwnProps {
branchLike?: T.BranchLike;
@@ -70,9 +72,13 @@ export function EmptyOverview({
}
/>
)}
{!hasBranches && !hasAnalyses && (
<AnalyzeTutorial component={component} currentUser={currentUser} />
)}
{!hasBranches &&
!hasAnalyses &&
(isSonarCloud() ? (
<AnalyzeTutorialSonarCloud component={component} currentUser={currentUser} />
) : (
<AnalyzeTutorial component={component} currentUser={currentUser} />
))}
</>
) : (
<WarningMessage
@@ -82,13 +88,15 @@ export function EmptyOverview({
)}
</div>

<div className="overview-sidebar page-sidebar-fixed">
<MetaContainer
branchLike={branchLike}
component={component}
onComponentChange={onComponentChange}
/>
</div>
{!isSonarCloud() && (
<div className="overview-sidebar page-sidebar-fixed">
<MetaContainer
branchLike={branchLike}
component={component}
onComponentChange={onComponentChange}
/>
</div>
)}
</div>
</div>
);

+ 0
- 8
server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx View File

@@ -47,14 +47,6 @@ it('should render OverviewApp', () => {
).toBeTruthy();
});

it('should render EmptyOverview', () => {
expect(
getWrapper({ component: { key: 'foo' } as T.Component })
.find('Connect(EmptyOverview)')
.exists()
).toBeTruthy();
});

function getWrapper(props = {}) {
return shallow(
<App

+ 44
- 0
server/sonar-web/src/main/js/apps/tutorials/__tests__/utils-test.ts View File

@@ -0,0 +1,44 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { getUniqueTokenName } from '../utils';

const initialTokenName = 'Analyze "lightsaber"';

it('should return the given name when the user has no token', () => {
const userTokens: T.UserToken[] = [];

expect(getUniqueTokenName(userTokens, initialTokenName)).toBe(initialTokenName);
});

it('should generate a token with the given name', () => {
const userTokens = [{ name: initialTokenName, createdAt: '2019-06-14T09:45:52+0200' }];

expect(getUniqueTokenName(userTokens, 'Analyze "project"')).toBe('Analyze "project"');
});

it('should generate a unique token when the name already exists', () => {
const userTokens = [
{ name: initialTokenName, createdAt: '2019-06-14T09:45:52+0200' },
{ name: `${initialTokenName} 1`, createdAt: '2019-06-14T09:45:52+0200' }
];

expect(getUniqueTokenName(userTokens, initialTokenName)).toBe('Analyze "lightsaber" 2');
});

+ 1
- 1
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx View File

@@ -27,7 +27,7 @@ import InstanceMessage from '../../../components/common/InstanceMessage';
import { isSonarCloud } from '../../../helpers/system';
import '../styles.css';

enum Steps {
export enum Steps {
ANALYSIS,
TOKEN
}

+ 119
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSonarCloud.css View File

@@ -0,0 +1,119 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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.
*/

.page-analysis {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 0 auto;
}

.page-analysis .page-header {
text-align: center;
}

.page-analysis h1 {
font-weight: bold;
}

.page-analysis-container-sonarcloud .onboarding-step {
padding: 0 calc(var(--gridSize) * 4);
}

.analysis-selector {
display: flex;
flex-direction: column;
justify-content: space-around;
}

.analysis-modes {
display: flex;
justify-content: space-around;
width: 80%;
}

.mode-type {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

.mode-type .icon {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100px;
height: 100px;
background-repeat: no-repeat;
background-position: center;
}

.mode-type .name {
font-weight: bold;
}

.mode-type-autoscan {
padding: calc(var(--gridSize) * 6) calc(var(--gridSize) * 10);
background: white;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.3);
}

.mode-type-autoscan .icon {
background-image: url(/images/sonarcloud-square-logo.svg);
}

.mode-type-autoscan .icon .badge {
position: absolute;
top: 12px;
right: 0;
}

.mode-type-travis .icon {
background-image: url(/images/sonarcloud/analysis/helmet.svg);
}

.mode-type-other .icon {
background-image: url(/images/sonarcloud/analysis/galaxy.svg);
}

.mode-type-manual .icon {
background-image: url(/images/sonarcloud/analysis/manual.svg);
}

.step-selector {
display: flex;
}

.page-analysis-waiting {
text-align: center;
}

.page-analysis-waiting .links {
padding: calc(var(--gridSize) * 6) calc(var(--gridSize) * 10);
background: white;
}

.onboarding-step hr.no-horizontal-margins {
margin-left: 0;
margin-right: 0;
}

+ 364
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSonarCloud.tsx View File

@@ -0,0 +1,364 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 classnames from 'classnames';
import * as React from 'react';
import ConfigureWithAutoScan from './configurations/ConfigureWithAutoScan';
import ConfigureWithTravis from './configurations/ConfigureWithTravis';
import ConfigureWithLocalScanner from './configurations/ConfigureWithLocalScanner';
import ConfigureOtherCI from './configurations/ConfigureWithOtherCI';
import { TutorialSuggestionBitbucket, TutorialSuggestionVSTS } from './AnalyzeTutorialSuggestion';
import {
Alm,
ALM_KEYS,
AlmLanguagesStats,
alms,
AnalysisMode,
autoScanMode,
isAutoScannable,
modes,
PROJECT_ONBOARDING_DONE,
PROJECT_ONBOARDING_MODE_ID,
PROJECT_STEP_PROGRESS,
TutorialProps
} from './utils';
import { getUniqueTokenName } from '../utils';
import AnalyzeTutorialDone from '../components/AnalyzeTutorialDone';
import InstanceMessage from '../../../components/common/InstanceMessage';
import BackButton from '../../../components/controls/BackButton';
import TokenStep from '../components/TokenStep';
import ProjectAnalysisStep from '../components/ProjectAnalysisStep';
import { generateToken, getTokens } from '../../../api/user-tokens';
import { getGithubLanguages } from '../../../api/alm-integration';
import { isBitbucket, isGithub, isVSTS } from '../../../helpers/almIntegrations';
import { translate } from '../../../helpers/l10n';
import { get, remove, save } from '../../../helpers/storage';
import { isSonarCloud } from '../../../helpers/system';
import '../styles.css';
import './AnalyzeTutorialSonarCloud.css';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface Props {
component: T.Component;
currentUser: T.LoggedInUser;
}

const tutorials: {
[k: string]: (props: Props & TutorialProps) => JSX.Element;
} = {
autoscan: ConfigureWithAutoScan,
manual: ConfigureWithLocalScanner,
other: ConfigureOtherCI,
travis: ConfigureWithTravis
};

enum Steps {
ANALYSIS = 'ANALYSIS',
TOKEN = 'TOKEN'
}

interface State {
alm?: Alm;
almLanguageStats?: AlmLanguagesStats;
isTutorialDone: boolean;
mode?: AnalysisMode;
loading: boolean;
step: Steps;
token?: string;
}

export default class AnalyzeTutorialSonarCloud extends React.PureComponent<Props, State> {
mounted = false;

constructor(props: Props) {
super(props);
this.state = {
loading: true,
isTutorialDone: get(PROJECT_ONBOARDING_DONE, props.component.key) === 'true',
step: Steps.TOKEN
};
}

componentDidMount() {
this.mounted = true;
const { component, currentUser } = this.props;

const almKey = (component.alm && component.alm.key) || currentUser.externalProvider;

if (!almKey) {
return;
}

if (isBitbucket(almKey)) {
this.configureBitbucket();
} else if (isGithub(almKey)) {
this.configureGithub();
} else if (isVSTS(almKey)) {
this.configureMicrosoft();
}

if (currentUser) {
getTokens(currentUser.login).then(
t => {
this.getNewToken(getUniqueTokenName(t, `Analyze "${component.name}"`));
},
() => {}
);
}
}

componentWillUnmount() {
this.mounted = false;
}

configureBitbucket = () => {
this.setState({ alm: alms[ALM_KEYS.BITBUCKET], loading: false });
};

configureGithub = () => {
const { component } = this.props;

this.setState({
alm: alms[ALM_KEYS.GITHUB]
});

const savedModeId = get(PROJECT_ONBOARDING_MODE_ID, component.key);
const mode =
autoScanMode.id === savedModeId ? autoScanMode : modes.find(m => m.id === savedModeId);
if (mode) {
this.setState({ mode });
}

if (!component.alm) {
return;
}

const { url } = component.alm;

if (!url) {
return;
}

getGithubLanguages(url).then(
almLanguagesStats => {
if (this.mounted) {
this.setState({
almLanguageStats: almLanguagesStats,
loading: false
});
}
},
() => {
this.setState({ loading: false });
}
);
};

configureMicrosoft = () => {
this.setState({ alm: alms[ALM_KEYS.MICROSOFT], loading: false });
};

getNewToken = (tokenName: string) => {
generateToken({ name: tokenName }).then(
({ token }: { token: string }) => {
this.setToken(token);
},
() => {}
);
};

setToken = (token: string) => {
if (this.mounted) {
this.setState({ token });
}
};

setTutorialDone = (value: boolean) => {
save(PROJECT_ONBOARDING_DONE, String(value), this.props.component.key);
this.setState({ isTutorialDone: value });
};

spinner = () => (
<div className="display-flex-justify-center spinner-margin">
<i className="spinner global-loading-spinner" />
</div>
);

renderBitbucket = () => {
const { component, currentUser } = this.props;
const { step, token } = this.state;

const handleTokenDone = (t: string) => {
if (this.mounted) {
this.setState({
step: Steps.ANALYSIS,
token: t
});
}
};

const handleTokenOpen = () => {
this.setState({ step: Steps.TOKEN });
};

return (
<>
<TutorialSuggestionBitbucket />

<TokenStep
currentUser={currentUser}
finished={Boolean(token)}
initialTokenName={`Analyze "${component.name}"`}
onContinue={handleTokenDone}
onOpen={handleTokenOpen}
open={step === Steps.TOKEN}
stepNumber={1}
/>

<ProjectAnalysisStep
component={component}
displayRowLayout={true}
open={step === Steps.ANALYSIS}
organization={isSonarCloud() ? component.organization : undefined}
stepNumber={2}
token={token}
/>
</>
);
};

renderGithub = () => {
const { almLanguageStats, isTutorialDone, mode, token } = this.state;

if (isTutorialDone) {
return <AnalyzeTutorialDone setTutorialDone={this.setTutorialDone} />;
}

const { component, currentUser } = this.props;
const Tutorial = mode && tutorials[mode.id];

const getClassnames = (item: AnalysisMode) =>
classnames(`mode-type mode-type-${item.id}`, {
[`mode-type-selected`]: mode && mode.id === item.id
});

const isAutoScanEnabled =
almLanguageStats && isAutoScannable(almLanguageStats).withAllowedLanguages;

const setMode = (mode: AnalysisMode | undefined) => {
if (mode) {
save(PROJECT_ONBOARDING_MODE_ID, mode.id, component.key);
remove(PROJECT_STEP_PROGRESS, component.key);
} else {
remove(PROJECT_ONBOARDING_MODE_ID, component.key);
}

this.setState({ mode });
};

if (!mode || !Tutorial) {
return (
<div className="page-analysis-container page-analysis-container-sonarcloud">
<div className="page-analysis big-spacer-top big-spacer-bottom huge-spacer-left huge-spacer-right">
<div className="page-header big-spacer-bottom">
<h1 className="big-spacer-bottom">
{translate('onboarding.project_analysis.header')}
</h1>
<p>
<InstanceMessage message={translate('onboarding.project_analysis.description')} />
</p>
</div>

{isAutoScanEnabled && (
<div className={`${getClassnames(autoScanMode)} huge-spacer-top huge-spacer-bottom`}>
<div className="icon">
<div className="badge badge-new">BETA</div>
</div>
<p>{autoScanMode.name}</p>
<button
className="button big-spacer-top big-spacer-bottom"
onClick={() => setMode(autoScanMode)}
type="button">
{translate('projects.configure_analysis')}
</button>
</div>
)}

<div className="analysis-modes">
{modes.map(el => (
<div className={getClassnames(el)} key={el.id}>
<div className="icon" />
<div className="name">{el.name}</div>
<button
className="button big-spacer-top big-spacer-bottom"
onClick={() => setMode(el)}
type="button">
{translate('projects.configure_analysis')}
</button>
</div>
))}
</div>
</div>
</div>
);
}

return (
<div className="page-analysis-container page-analysis-container-sonarcloud">
<BackButton
onClick={() => setMode(undefined)}
tooltip={translate('onboarding.tutorial.return_to_list')}>
{translate('back')}
</BackButton>

<Tutorial
component={component}
currentUser={currentUser}
onDone={() => this.setTutorialDone(true)}
setToken={this.setToken}
token={token}
/>
</div>
);
};

renderMicrosoft = () => {
return <TutorialSuggestionVSTS />;
};

render() {
const { alm, loading } = this.state;

if (!alm) {
return null;
}

return (
<DeferredSpinner customSpinner={<this.spinner />} loading={loading}>
{!loading && (
<>
{alm.id === ALM_KEYS.BITBUCKET && this.renderBitbucket()}
{alm.id === ALM_KEYS.GITHUB && this.renderGithub()}
{alm.id === ALM_KEYS.MICROSOFT && this.renderMicrosoft()}
</>
)}
</DeferredSpinner>
);
}
}

+ 73
- 61
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx View File

@@ -24,71 +24,83 @@ import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/urls';
import { Alert } from '../../../components/ui/Alert';

export function TutorialSuggestionBitbucket() {
return (
<Alert className="big-spacer-bottom" variant="info">
<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'
}
rel="noopener noreferrer"
target="_blank">
{translate('onboarding.project_analysis.guide_to_integrate_pipelines')}
</a>
)
}}
/>
</Alert>
);
}

export function TutorialSuggestionGithub() {
return (
<Alert className="big-spacer-bottom" variant="info">
<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>
)
}}
/>
</Alert>
);
}

export function TutorialSuggestionVSTS() {
return (
<Alert className="big-spacer-bottom" variant="info">
<FormattedMessage
defaultMessage={translate('onboarding.project_analysis.simply_link')}
id={'onboarding.project_analysis.simply_link'}
values={{
link: (
<a
href={getBaseUrl() + '/documentation/integrations/vsts/'}
rel="noopener noreferrer"
target="_blank">
{translate('onboarding.project_analysis.guide_to_integrate_vsts')}
</a>
)
}}
/>
</Alert>
);
}

export default function AnalyzeTutorialSuggestion({ almKey }: { almKey?: string }) {
if (isBitbucket(almKey)) {
return (
<Alert className="big-spacer-bottom" variant="info">
<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'
}
rel="noopener noreferrer"
target="_blank">
{translate('onboarding.project_analysis.guide_to_integrate_piplines')}
</a>
)
}}
/>
</Alert>
);
return <TutorialSuggestionBitbucket />;
} else if (isGithub(almKey)) {
return (
<Alert className="big-spacer-bottom" variant="info">
<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>
)
}}
/>
</Alert>
);
return <TutorialSuggestionGithub />;
} else if (isVSTS(almKey)) {
return (
<Alert className="big-spacer-bottom" variant="info">
<FormattedMessage
defaultMessage={translate('onboarding.project_analysis.simply_link')}
id={'onboarding.project_analysis.simply_link'}
values={{
link: (
<a
href={getBaseUrl() + '/documentation/integrations/vsts/'}
rel="noopener noreferrer"
target="_blank">
{translate('onboarding.project_analysis.guide_to_integrate_vsts')}
</a>
)
}}
/>
</Alert>
);
return <TutorialSuggestionVSTS />;
}
return null;
}

+ 177
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSonarCloud-test.tsx View File

@@ -0,0 +1,177 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 AnalyzeTutorialSonarCloud from '../AnalyzeTutorialSonarCloud';
import { mockComponent, mockLoggedInUser } from '../../../../helpers/testMocks';
import { get } from '../../../../helpers/storage';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { generateToken, getTokens } from '../../../../api/user-tokens';
import { getUniqueTokenName } from '../../utils';

jest.mock('../../../../helpers/storage', () => ({
get: jest.fn(),
remove: jest.fn(),
save: jest.fn()
}));

jest.mock('../../../../api/alm-integration', () => ({
getGithubLanguages: jest.fn().mockResolvedValue({
JavaScript: 512636,
TypeScript: 425475,
HTML: 390075,
CSS: 14099,
Makefile: 536,
Dockerfile: 319
})
}));

jest.mock('../../../../api/user-tokens', () => ({
generateToken: jest.fn().mockResolvedValue({
name: 'baz',
createdAt: '2019-01-21T08:06:00+0100',
login: 'luke',
token: 'token_value'
}),
getTokens: jest.fn().mockResolvedValue([
{
name: 'foo',
createdAt: '2019-01-15T15:06:33+0100',
lastConnectionDate: '2019-01-18T15:06:33+0100'
},
{ name: 'bar', createdAt: '2019-01-18T15:06:33+0100' }
]),
revokeToken: jest.fn().mockResolvedValue(Promise.resolve())
}));

jest.mock('../../utils', () => ({
getUniqueTokenName: jest.fn().mockReturnValue('lightsaber-9000')
}));

const component = mockComponent();

beforeEach(() => {
jest.clearAllMocks();
});

it('shows a loading screen', () => {
expect(shallowRender()
.find('DeferredSpinner')
.prop('loading') as boolean).toBe(true);
});

it('renders for GitHub', async () => {
const comp = {
...component,
alm: { key: 'github', url: 'https://github.com/luke/lightsaber' }
};
const wrapper = shallowRender({ component: comp });
await waitAndUpdate(wrapper);

expect(wrapper).toMatchSnapshot();

wrapper
.find('button')
.first()
.simulate('click');
expect(wrapper.state('mode')).toEqual({
id: 'autoscan',
name: 'SonarCloud Automatic Analysis'
});
});

it('renders for BitBucket', () => {
const comp = {
...component,
alm: { key: 'bitbucket', url: 'https://bitbucket.com/luke/lightsaber' }
};
const wrapper = shallowRender({ component: comp });

expect(wrapper).toMatchSnapshot();

(wrapper.find('TokenStep').prop('onContinue') as Function)('abc123');

expect(wrapper.state('token')).toBe('abc123');
expect(wrapper.state('step')).toBe('ANALYSIS');
});

it('renders for Azure', () => {
const comp = {
...component,
alm: { key: 'microsoft', url: 'https://azuredevops.com/luke/lightsaber' }
};
expect(shallowRender({ component: comp })).toMatchSnapshot();
});

it('renders for a non supported component', async () => {
const wrapper = shallowRender();
await waitAndUpdate(wrapper);

expect(wrapper).toMatchSnapshot();
});

it('renders the finished state', async () => {
(get as jest.Mock).mockReturnValue('true');

const comp = {
...component,
alm: { key: 'github', url: 'https://github.com/luke/lightsaber' }
};
const wrapper = shallowRender({ component: comp });
await waitAndUpdate(wrapper);
expect(wrapper.find('AnalyzeTutorialDone').exists()).toBe(true);
});

it('should get tokens and unique name', async () => {
const wrapper = shallowRender();
await waitAndUpdate(wrapper);

expect(getTokens).toHaveBeenCalled();
expect(getUniqueTokenName).toHaveBeenCalled();
expect(generateToken).toHaveBeenCalled();
expect(wrapper.state('token')).toBe('token_value');
});

it('should set tutorial done', () => {
const wrapper = shallowRender();
const instance = wrapper.instance();

instance.setTutorialDone(false);
expect(wrapper.state('isTutorialDone')).toBeFalsy();

instance.setTutorialDone(true);
expect(wrapper.state('isTutorialDone')).toBeTruthy();
});

it('should have a spinner', () => {
const wrapper = shallowRender();
const instance = wrapper.instance();
expect(instance.spinner()).toMatchSnapshot();
});

function shallowRender(props: Partial<AnalyzeTutorialSonarCloud['props']> = {}) {
return shallow<AnalyzeTutorialSonarCloud>(
<AnalyzeTutorialSonarCloud
component={component}
currentUser={mockLoggedInUser({ externalProvider: 'github' })}
{...props}
/>
);
}

+ 26
- 4
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSuggestion-test.tsx View File

@@ -19,20 +19,42 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import AnalyzeTutorialSuggestion from '../AnalyzeTutorialSuggestion';
import AnalyzeTutorialSuggestion, {
TutorialSuggestionBitbucket,
TutorialSuggestionGithub,
TutorialSuggestionVSTS
} from '../AnalyzeTutorialSuggestion';

it('should not render', () => {
expect(shallow(<AnalyzeTutorialSuggestion almKey={undefined} />).type()).toBeNull();
});

it('renders bitbucket suggestions correctly', () => {
expect(shallow(<AnalyzeTutorialSuggestion almKey="bitbucket" />)).toMatchSnapshot();
expect(
shallow(<AnalyzeTutorialSuggestion almKey="bitbucket" />).find(TutorialSuggestionBitbucket)
).toHaveLength(1);
});

it('renders github suggestions correctly', () => {
expect(shallow(<AnalyzeTutorialSuggestion almKey="github" />)).toMatchSnapshot();
expect(
shallow(<AnalyzeTutorialSuggestion almKey="github" />).find(TutorialSuggestionGithub)
).toHaveLength(1);
});

it('renders vsts suggestions correctly', () => {
expect(shallow(<AnalyzeTutorialSuggestion almKey="microsoft" />)).toMatchSnapshot();
expect(
shallow(<AnalyzeTutorialSuggestion almKey="microsoft" />).find(TutorialSuggestionVSTS)
).toHaveLength(1);
});

it('renders bitbucket tutorial correctly', () => {
expect(shallow(<TutorialSuggestionBitbucket />)).toMatchSnapshot();
});

it('renders github tutorial correctly', () => {
expect(shallow(<TutorialSuggestionGithub />)).toMatchSnapshot();
});

it('renders microsoft tutorial correctly', () => {
expect(shallow(<TutorialSuggestionVSTS />)).toMatchSnapshot();
});

+ 207
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorialSonarCloud-test.tsx.snap View File

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

exports[`renders for Azure 1`] = `
<DeferredSpinner
customSpinner={<Unknown />}
loading={false}
timeout={100}
>
<TutorialSuggestionVSTS />
</DeferredSpinner>
`;

exports[`renders for BitBucket 1`] = `
<DeferredSpinner
customSpinner={<Unknown />}
loading={false}
timeout={100}
>
<TutorialSuggestionBitbucket />
<TokenStep
currentUser={
Object {
"externalProvider": "github",
"groups": Array [],
"isLoggedIn": true,
"login": "luke",
"name": "Skywalker",
"scmAccounts": Array [],
}
}
finished={false}
initialTokenName="Analyze \\"MyProject\\""
onContinue={[Function]}
onOpen={[Function]}
open={true}
stepNumber={1}
/>
<ProjectAnalysisStep
component={
Object {
"alm": Object {
"key": "bitbucket",
"url": "https://bitbucket.com/luke/lightsaber",
},
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
displayRowLayout={true}
open={false}
stepNumber={2}
/>
</DeferredSpinner>
`;

exports[`renders for GitHub 1`] = `
<DeferredSpinner
customSpinner={<Unknown />}
loading={false}
timeout={100}
>
<div
className="page-analysis-container page-analysis-container-sonarcloud"
>
<div
className="page-analysis big-spacer-top big-spacer-bottom huge-spacer-left huge-spacer-right"
>
<div
className="page-header big-spacer-bottom"
>
<h1
className="big-spacer-bottom"
>
onboarding.project_analysis.header
</h1>
<p>
<InstanceMessage
message="onboarding.project_analysis.description"
/>
</p>
</div>
<div
className="mode-type mode-type-autoscan huge-spacer-top huge-spacer-bottom"
>
<div
className="icon"
>
<div
className="badge badge-new"
>
BETA
</div>
</div>
<p>
SonarCloud Automatic Analysis
</p>
<button
className="button big-spacer-top big-spacer-bottom"
onClick={[Function]}
type="button"
>
projects.configure_analysis
</button>
</div>
<div
className="analysis-modes"
>
<div
className="mode-type mode-type-travis"
key="travis"
>
<div
className="icon"
/>
<div
className="name"
>
With Travis CI
</div>
<button
className="button big-spacer-top big-spacer-bottom"
onClick={[Function]}
type="button"
>
projects.configure_analysis
</button>
</div>
<div
className="mode-type mode-type-other"
key="other"
>
<div
className="icon"
/>
<div
className="name"
>
With other CI tools
</div>
<button
className="button big-spacer-top big-spacer-bottom"
onClick={[Function]}
type="button"
>
projects.configure_analysis
</button>
</div>
<div
className="mode-type mode-type-manual"
key="manual"
>
<div
className="icon"
/>
<div
className="name"
>
Manually
</div>
<button
className="button big-spacer-top big-spacer-bottom"
onClick={[Function]}
type="button"
>
projects.configure_analysis
</button>
</div>
</div>
</div>
</div>
</DeferredSpinner>
`;

exports[`renders for a non supported component 1`] = `
<DeferredSpinner
customSpinner={<Unknown />}
loading={true}
timeout={100}
/>
`;

exports[`should have a spinner 1`] = `
<div
className="display-flex-justify-center spinner-margin"
>
<i
className="spinner global-loading-spinner"
/>
</div>
`;

+ 4
- 4
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorialSuggestion-test.tsx.snap View File

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

exports[`renders bitbucket suggestions correctly 1`] = `
exports[`renders bitbucket tutorial correctly 1`] = `
<Alert
className="big-spacer-bottom"
variant="info"
@@ -21,7 +21,7 @@ exports[`renders bitbucket suggestions correctly 1`] = `
rel="noopener noreferrer"
target="_blank"
>
onboarding.project_analysis.guide_to_integrate_piplines
onboarding.project_analysis.guide_to_integrate_pipelines
</a>,
}
}
@@ -29,7 +29,7 @@ exports[`renders bitbucket suggestions correctly 1`] = `
</Alert>
`;

exports[`renders github suggestions correctly 1`] = `
exports[`renders github tutorial correctly 1`] = `
<Alert
className="big-spacer-bottom"
variant="info"
@@ -59,7 +59,7 @@ exports[`renders github suggestions correctly 1`] = `
</Alert>
`;

exports[`renders vsts suggestions correctly 1`] = `
exports[`renders microsoft tutorial correctly 1`] = `
<Alert
className="big-spacer-bottom"
variant="info"

+ 59
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/utils-test.tsx View File

@@ -0,0 +1,59 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { isAutoScannable } from '../utils';

it('should work for supported languages', () => {
expect(
isAutoScannable({
JavaScript: 512636,
TypeScript: 425475,
HTML: 390075,
CSS: 14099,
Makefile: 536,
Dockerfile: 319
})
).toEqual({
withAllowedLanguages: true,
withNotAllowedLanguages: false
});
});

it('should work for non supported languages', () => {
expect(
isAutoScannable({
Java: 434
})
).toEqual({
withAllowedLanguages: false,
withNotAllowedLanguages: true
});
});

it('should work for mixed languages', () => {
expect(
isAutoScannable({
JavaScript: 512636,
Java: 434
})
).toEqual({
withAllowedLanguages: true,
withNotAllowedLanguages: true
});
});

+ 71
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/AutoScanAlert.tsx View File

@@ -0,0 +1,71 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { Alert } from '../../../../components/ui/Alert';
import DocTooltip from '../../../../components/docs/DocTooltip';
import { translate } from '../../../../helpers/l10n';

const caveats = {
default: `
No visual feedback (yet) in the UI.
Only Pull Requests from the same repository are analyzed.
Not supported: code coverage, import of external rule engine reports.

---

[Read more](https://sonarcloud.io/documentation/autoscan/) and join [the forum](https://community.sonarsource.com/tags/c/help/sc/autoscan) to ask your questions
`
};

const limitedScope = {
default: `
The following languages are currently supported:

ABAP, Apex, CSS, Flex, Go, HTML, JS, Kotlin, PHP, Python, Ruby, Scala, Swift, TypeScript, TSQL, XML.
`
};

export function AutoScanAlert() {
return (
<Alert className="big-spacer-top" variant="info">
<div>
<FormattedMessage
defaultMessage={translate('onboarding.analysis.with.autoscan.alert')}
id="onboarding.analysis.with.autoscan.alert"
values={{
caveats: (
<>
<strong>{translate('onboarding.analysis.with.autoscan.alert.caveats')}</strong>{' '}
<DocTooltip doc={Promise.resolve(caveats)} />
</>
),
scopes: (
<>
<strong>{translate('onboarding.analysis.with.autoscan.alert.scopes')}</strong>{' '}
<DocTooltip doc={Promise.resolve(limitedScope)} />
</>
)
}}
/>
</div>
</Alert>
);
}

+ 126
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/ConfigureWithAutoScan.tsx View File

@@ -0,0 +1,126 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { AutoScanAlert } from './AutoScanAlert';
import CodeSnippet from '../../../../components/common/CodeSnippet';
import { translate } from '../../../../helpers/l10n';
import { Button, ResetButtonLink } from '../../../../components/ui/buttons';
import Step from '../../components/Step';
import DropdownIcon from '../../../../components/icons-components/DropdownIcon';
import { TutorialProps } from '../utils';

export default function ConfigureWithAutoScan({ onDone }: TutorialProps) {
const [showCustomizationOptions, setCustomizationOptions] = React.useState<boolean>(false);

const command = `# Path to sources
#sonar.sources=.
#sonar.exclusions=
#sonar.inclusions=

# Path to tests
#sonar.tests=
#sonar.test.exclusions=
#sonar.test.inclusions=

# Source encoding
#sonar.sourceEncoding=UTF-8

# Exclusions for copy-paste detection
#sonar.cpd.exclusions=`;

const renderForm = () => (
<div className="boxed-group-inner">
<div className="flex-columns">
<div className="flex-column-full">
<p className="spacer-bottom">
Add this file in the base directory of your default branch or on a PR.
</p>
<p className="spacer-bottom">
You can push an empty <code>.sonarcloud.properties</code> file, this will work fine. In
this case, every file in the repository will be considered as a source file.
</p>

<ResetButtonLink onClick={() => setCustomizationOptions(!showCustomizationOptions)}>
{showCustomizationOptions ? 'Hide customization options' : 'Show customization options'}
<DropdownIcon className="little-spacer-left" turned={showCustomizationOptions} />
</ResetButtonLink>

<div hidden={!showCustomizationOptions}>
<p>
Here are the supported optional settings for the <code>.sonarcloud.properties</code>{' '}
file:
</p>

<CodeSnippet snippet={command} />

<p>
Please refer to the{' '}
<a
href="https://sieg.eu.ngrok.io/documentation/autoscan/"
rel="noopener noreferrer"
target="_blank">
documentation
</a>{' '}
for more details.
</p>
</div>
</div>
</div>
<div className="big-spacer-top">
<Button className="js-continue" onClick={onDone}>
{translate('onboarding.finish')}
</Button>
</div>
</div>
);

const renderResult = () => null;

return (
<>
<h1 className="spacer-bottom spacer-top">
{translate('onboarding.analysis.with.autoscan.title')}
</h1>

<p className="spacer-bottom">{translate('onboarding.analysis.with.autoscan.text')}</p>

<AutoScanAlert />

<Step
finished={false}
onOpen={() => {}}
open={true}
renderForm={renderForm}
renderResult={renderResult}
stepNumber={1}
stepTitle={
<FormattedMessage
defaultMessage={translate('onboarding.analysis.with.autoscan.filename')}
id="onboarding.analysis.with.autoscan.filename"
values={{
filename: <code className="rule">.sonarcloud.properties</code>
}}
/>
}
/>
</>
);
}

+ 54
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/ConfigureWithLocalScanner.tsx View File

@@ -0,0 +1,54 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 ProjectAnalysisStepFromBuildTool, {
ProjectAnalysisModes
} from '../../components/ProjectAnalysisStepFromBuildTool';
import { TutorialProps } from '../utils';
import { translate } from '../../../../helpers/l10n';

export default function ConfigureWithLocalScanner({
component,
currentUser,
onDone,
setToken,
token
}: TutorialProps) {
return (
<>
<h1 className="spacer-bottom spacer-top">
{translate('onboarding.analysis.with.local.title')}
</h1>

<ProjectAnalysisStepFromBuildTool
component={component}
currentUser={currentUser}
displayRowLayout={true}
mode={ProjectAnalysisModes.Custom}
onDone={onDone}
open={true}
organization={component.organization}
setToken={setToken}
stepNumber={1}
token={token}
/>
</>
);
}

+ 54
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/ConfigureWithOtherCI.tsx View File

@@ -0,0 +1,54 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 ProjectAnalysisStepFromBuildTool, {
ProjectAnalysisModes
} from '../../components/ProjectAnalysisStepFromBuildTool';
import { TutorialProps } from '../utils';
import { translate } from '../../../../helpers/l10n';

export default function ConfigureWithOtherCI({
component,
currentUser,
onDone,
setToken,
token
}: TutorialProps) {
return (
<>
<h1 className="spacer-bottom spacer-top">
{translate('onboarding.analysis.with.yourci.title')}
</h1>

<ProjectAnalysisStepFromBuildTool
component={component}
currentUser={currentUser}
displayRowLayout={true}
mode={ProjectAnalysisModes.CI}
onDone={onDone}
open={true}
organization={component.organization}
setToken={setToken}
stepNumber={1}
token={token}
/>
</>
);
}

+ 119
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/ConfigureWithTravis.tsx View File

@@ -0,0 +1,119 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 EncryptYourTokenStep from '../steps/EncryptYourTokenStep';
import CreateSonarPropertiesStep from '../steps/CreateSonarPropertiesStep';
import EditTravisYmlStep from '../steps/EditTravisYmlStep';
import { isSonarCloud } from '../../../../helpers/system';
import { PROJECT_STEP_PROGRESS, TutorialProps } from '../utils';
import { translate } from '../../../../helpers/l10n';
import { get, save } from '../../../../helpers/storage';

enum Steps {
ENCRYPT_TOKEN = 1,
EDIT_TRAVIS_YML = 2,
CREATE_SONAR_PROPERTIES = 3
}

export default function ConfigureWithTravis({
component,
currentUser,
onDone,
setToken,
token
}: TutorialProps) {
const [build, setBuild] = React.useState<string | undefined>(undefined);
const [step, setStep] = React.useState<Steps>(Steps.ENCRYPT_TOKEN);
const [hasStepAfterTravisYml, setHasStepAfterTravilYml] = React.useState<boolean>(false);

React.useEffect(() => {
const value = get(PROJECT_STEP_PROGRESS, component.key);
if (value) {
try {
const data = JSON.parse(value);
setBuild(data.build);
setStep(data.step);
setHasStepAfterTravilYml(data.hasStepAfterTravisYml);
} catch (e) {
// Let's start from scratch
}
}
}, [component.key]);

const saveAndFinish = () => {
save(
PROJECT_STEP_PROGRESS,
JSON.stringify({
build,
hasStepAfterTravisYml,
step
}),
component.key
);

onDone();
};

return (
<>
<h1 className="spacer-bottom spacer-top">
{translate('onboarding.analysis.with.travis.title')}
</h1>

<EncryptYourTokenStep
component={component}
currentUser={currentUser}
onContinue={() => setStep(Steps.EDIT_TRAVIS_YML)}
onOpen={() => setStep(Steps.ENCRYPT_TOKEN)}
open={step === Steps.ENCRYPT_TOKEN}
setToken={setToken}
stepNumber={1}
token={token}
/>

<EditTravisYmlStep
buildCallback={setBuild}
buildType={build}
component={component}
finished={step >= 2}
hasStepAfter={setHasStepAfterTravilYml}
onContinue={() =>
hasStepAfterTravisYml ? setStep(Steps.CREATE_SONAR_PROPERTIES) : saveAndFinish()
}
onOpen={() => setStep(Steps.EDIT_TRAVIS_YML)}
open={step === Steps.EDIT_TRAVIS_YML}
organization={isSonarCloud() ? component.organization : undefined}
stepNumber={2}
token={token}
/>

{hasStepAfterTravisYml && (
<CreateSonarPropertiesStep
component={component}
finished={step >= 3}
onContinue={saveAndFinish}
onOpen={() => setStep(Steps.CREATE_SONAR_PROPERTIES)}
open={step === Steps.CREATE_SONAR_PROPERTIES}
stepNumber={3}
/>
)}
</>
);
}

+ 30
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/AutoScanAlert-test.tsx View File

@@ -0,0 +1,30 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { AutoScanAlert } from '../AutoScanAlert';

it('should renders correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

function shallowRender() {
return shallow(<AutoScanAlert />);
}

+ 41
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/ConfigureOtherCI-test.tsx View File

@@ -0,0 +1,41 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 ConfigureWithOtherCI from '../ConfigureWithOtherCI';
import { TutorialProps } from '../../utils';
import { mockComponent, mockLoggedInUser } from '../../../../../helpers/testMocks';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

function shallowRender(props: Partial<TutorialProps> = {}) {
return shallow(
<ConfigureWithOtherCI
component={mockComponent()}
currentUser={mockLoggedInUser()}
onDone={jest.fn()}
setToken={jest.fn()}
token="token123"
{...props}
/>
);
}

+ 41
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/ConfigureWithAutoScan-test.tsx View File

@@ -0,0 +1,41 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 ConfigureWithAutoScan from '../ConfigureWithAutoScan';
import { TutorialProps } from '../../utils';
import { mockComponent, mockLoggedInUser } from '../../../../../helpers/testMocks';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

function shallowRender(props: Partial<TutorialProps> = {}) {
return shallow(
<ConfigureWithAutoScan
component={mockComponent()}
currentUser={mockLoggedInUser()}
onDone={jest.fn()}
setToken={jest.fn()}
token="token123"
{...props}
/>
);
}

+ 41
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/ConfigureWithLocalScanner-test.tsx View File

@@ -0,0 +1,41 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 ConfigureWithLocalScanner from '../ConfigureWithLocalScanner';
import { TutorialProps } from '../../utils';
import { mockComponent, mockLoggedInUser } from '../../../../../helpers/testMocks';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

function shallowRender(props: Partial<TutorialProps> = {}) {
return shallow(
<ConfigureWithLocalScanner
component={mockComponent()}
currentUser={mockLoggedInUser()}
onDone={jest.fn()}
setToken={jest.fn()}
token="token123"
{...props}
/>
);
}

+ 71
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/ConfigureWithTravis-test.tsx View File

@@ -0,0 +1,71 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 ConfigureWithTravis from '../ConfigureWithTravis';
import { TutorialProps } from '../../utils';
import { mockComponent, mockLoggedInUser } from '../../../../../helpers/testMocks';

jest.mock('../../../../../helpers/storage', () => ({
get: jest.fn().mockReturnValue(
JSON.stringify({
build: 'maven',
hasStepAfterTravisYml: true,
step: 3
})
),
save: jest.fn()
}));

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should react to EditTravisYmlStep onContinue', () => {
const wrapper = shallowRender();
expect(wrapper.find('EditTravisYmlStep').prop('open') as Boolean).toBeFalsy();

(wrapper.find('EncryptYourTokenStep').prop('onContinue') as Function)();

expect(wrapper.find('EditTravisYmlStep').prop('open') as Boolean).toBeTruthy();
});

it('should react to EditTravisYmlStep onOpen', () => {
const wrapper = shallowRender();
(wrapper.find('EncryptYourTokenStep').prop('onContinue') as Function)();
expect(wrapper.find('EncryptYourTokenStep').prop('open') as Boolean).toBeFalsy();

(wrapper.find('EncryptYourTokenStep').prop('onOpen') as Function)();

expect(wrapper.find('EncryptYourTokenStep').prop('open') as Boolean).toBeTruthy();
});

function shallowRender(props: Partial<TutorialProps> = {}) {
return shallow(
<ConfigureWithTravis
component={mockComponent()}
currentUser={mockLoggedInUser()}
onDone={jest.fn()}
setToken={jest.fn()}
token="token123"
{...props}
/>
);
}

+ 37
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/__snapshots__/AutoScanAlert-test.tsx.snap View File

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

exports[`should renders correctly 1`] = `
<Alert
className="big-spacer-top"
variant="info"
>
<div>
<FormattedMessage
defaultMessage="onboarding.analysis.with.autoscan.alert"
id="onboarding.analysis.with.autoscan.alert"
values={
Object {
"caveats": <React.Fragment>
<strong>
onboarding.analysis.with.autoscan.alert.caveats
</strong>
<DocTooltip
doc={Promise {}}
/>
</React.Fragment>,
"scopes": <React.Fragment>
<strong>
onboarding.analysis.with.autoscan.alert.scopes
</strong>
<DocTooltip
doc={Promise {}}
/>
</React.Fragment>,
}
}
/>
</div>
</Alert>
`;

+ 53
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/__snapshots__/ConfigureOtherCI-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<Fragment>
<h1
className="spacer-bottom spacer-top"
>
onboarding.analysis.with.yourci.title
</h1>
<ProjectAnalysisStepFromBuildTool
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
currentUser={
Object {
"groups": Array [],
"isLoggedIn": true,
"login": "luke",
"name": "Skywalker",
"scmAccounts": Array [],
}
}
displayRowLayout={true}
mode="CI"
onDone={[MockFunction]}
open={true}
organization="foo"
setToken={[MockFunction]}
stepNumber={1}
token="token123"
/>
</Fragment>
`;

+ 40
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/__snapshots__/ConfigureWithAutoScan-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<Fragment>
<h1
className="spacer-bottom spacer-top"
>
onboarding.analysis.with.autoscan.title
</h1>
<p
className="spacer-bottom"
>
onboarding.analysis.with.autoscan.text
</p>
<AutoScanAlert />
<Step
finished={false}
onOpen={[Function]}
open={true}
renderForm={[Function]}
renderResult={[Function]}
stepNumber={1}
stepTitle={
<FormattedMessage
defaultMessage="onboarding.analysis.with.autoscan.filename"
id="onboarding.analysis.with.autoscan.filename"
values={
Object {
"filename": <code
className="rule"
>
.sonarcloud.properties
</code>,
}
}
/>
}
/>
</Fragment>
`;

+ 53
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/__snapshots__/ConfigureWithLocalScanner-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<Fragment>
<h1
className="spacer-bottom spacer-top"
>
onboarding.analysis.with.local.title
</h1>
<ProjectAnalysisStepFromBuildTool
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
currentUser={
Object {
"groups": Array [],
"isLoggedIn": true,
"login": "luke",
"name": "Skywalker",
"scmAccounts": Array [],
}
}
displayRowLayout={true}
mode="Custom"
onDone={[MockFunction]}
open={true}
organization="foo"
setToken={[MockFunction]}
stepNumber={1}
token="token123"
/>
</Fragment>
`;

+ 84
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/configurations/__tests__/__snapshots__/ConfigureWithTravis-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<Fragment>
<h1
className="spacer-bottom spacer-top"
>
onboarding.analysis.with.travis.title
</h1>
<EncryptYourTokenStep
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
currentUser={
Object {
"groups": Array [],
"isLoggedIn": true,
"login": "luke",
"name": "Skywalker",
"scmAccounts": Array [],
}
}
onContinue={[Function]}
onOpen={[Function]}
open={true}
setToken={[MockFunction]}
stepNumber={1}
token="token123"
/>
<EditTravisYmlStep
buildCallback={[Function]}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
finished={false}
hasStepAfter={[Function]}
onContinue={[Function]}
onOpen={[Function]}
open={false}
stepNumber={2}
token="token123"
/>
</Fragment>
`;

+ 93
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/CreateSonarPropertiesStep.tsx View File

@@ -0,0 +1,93 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 CodeSnippet from '../../../../components/common/CodeSnippet';
import { Button } from '../../../../components/ui/buttons';
import { translate } from '../../../../helpers/l10n';
import Step from '../../components/Step';
import { StepProps } from '../../utils';

export default function CreateSonarPropertiesStep({
component,
finished,
onContinue,
onOpen,
open,
stepNumber
}: StepProps) {
const command = `sonar.projectKey=${component ? component.key : 'my:project'}
# this is the name and version displayed in the SonarCloud UI.
sonar.projectName=${component ? component.name : 'My project'}
sonar.projectVersion=1.0
# Path is relative to the sonar-project.properties file. Replace "\\" by "/" on Windows.
# This property is optional if sonar.modules is set.
sonar.sources=.
# Encoding of the source code. Default is default system encoding
#sonar.sourceEncoding=UTF-8`;

const renderForm = () => (
<div className="boxed-group-inner">
<div className="flex-columns">
<div className="flex-column-full">
<p>
<FormattedMessage
defaultMessage={translate('onboarding.analysis.with.travis.sonar.properties.text')}
id="onboarding.analysis.with.travis.sonar.properties.text"
values={{
code: <code>sonar-project.properties</code>
}}
/>
</p>
<CodeSnippet snippet={command} />
</div>
</div>
<div className="big-spacer-top">
<Button className="js-continue" onClick={onContinue}>
{translate('onboarding.finish')}
</Button>
</div>
</div>
);

const renderResult = () => null;

return (
<Step
finished={Boolean(finished)}
onOpen={onOpen}
open={open}
renderForm={renderForm}
renderResult={renderResult}
stepNumber={stepNumber}
stepTitle={
<FormattedMessage
defaultMessage={translate('onboarding.analysis.with.travis.sonar.properties.title')}
id="onboarding.analysis.with.travis.sonar.properties.title"
values={{
filename: <code className="rule">sonar.properties</code>
}}
/>
}
/>
);
}

+ 235
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/EditTokenModal.tsx View File

@@ -0,0 +1,235 @@
/*
* SonarQube
* Copyright (C) 2009-2019 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import { generateToken, getTokens, revokeToken } from '../../../../api/user-tokens';
import { getUniqueTokenName } from '../../utils';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
import ConfirmModal from '../../../../components/controls/ConfirmModal';
import { Button, DeleteButton } from '../../../../components/ui/buttons';
import { RenderOptions } from '../../components/RenderOptions';
import { Alert } from '../../../../components/ui/Alert';

export enum TokenMode {
use_existing_token = 'use_existing_token',
generate_token = 'generate_token'
}

interface State {
existingToken?: string;
mode: TokenMode;
token?: string;
tokenName: string;
}

interface Props {
component: T.Component;
currentUser: T.LoggedInUser;
onClose: VoidFunction;
onSave: (token: string) => void;
}

export default class EditTokenModal extends React.PureComponent<Props, State> {
mounted = false;
initialTokenName = '';
state = {
mode: TokenMode.use_existing_token,
existingToken: '',
token: '',
tokenName: ''
};

componentDidMount() {
this.mounted = true;
const { component } = this.props;
this.initialTokenName = `Analyze "${component.name}"`;
this.getTokensAndName();
}

componentWillUnmount() {
this.mounted = false;
}

getTokensAndName = () => {
const { currentUser } = this.props;

getTokens(currentUser.login).then(
t => {
if (this.mounted) {
this.setState({ tokenName: getUniqueTokenName(t, this.initialTokenName) });
}
},
() => {}
);
};

getNewToken = () => {
const { tokenName = this.initialTokenName } = this.state;

generateToken({ name: tokenName }).then(
({ token }: { token: string }) => {
if (this.mounted) {
this.setState({
token,
tokenName
});
}
},
() => {}
);
};

handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (this.mounted) {
this.setState({
tokenName: event.target.value
});
}
};

handleTokenRevoke = () => {
const { tokenName } = this.state;

if (tokenName) {
revokeToken({ name: tokenName }).then(
() => {
if (this.mounted) {
this.setState({
mode: TokenMode.use_existing_token,
token: '',
tokenName: ''
});
}
},
() => {}
);
}
};

setExistingToken = (event: React.ChangeEvent<HTMLInputElement>) => {
if (this.mounted) {
this.setState({
existingToken: event.target.value
});
}
};

setMode = (mode: TokenMode) => {
this.setState({ mode });
};

render() {
const { onClose, onSave } = this.props;
const { existingToken, mode, token, tokenName } = this.state;

const header = translate('onboarding.token.header');

const isConfirmEnabled =
(mode === TokenMode.generate_token && token) ||
(mode === TokenMode.use_existing_token && existingToken);

const onConfirm = () => {
if (mode === TokenMode.generate_token && token) {
onSave(token);
} else if (mode === TokenMode.use_existing_token && existingToken) {
onSave(existingToken);
}
};

return (
<ConfirmModal
confirmButtonText={translate('save')}
confirmDisable={!isConfirmEnabled}
header={header}
onClose={onClose}
onConfirm={onConfirm}>
<p className="spacer-bottom">
<FormattedMessage
defaultMessage={translate('onboarding.token.text')}
id="onboarding.token.text"
values={{
link: (
<Link target="_blank" to="/account/security">
{translate('onboarding.token.text.user_account')}
</Link>
)
}}
/>
</p>

{token ? (
<>
<span className="text-middle">
{tokenName}
{': '}
</span>
<strong className="spacer-right text-middle">{token}</strong>

<DeleteButton className="button-small text-middle" onClick={this.handleTokenRevoke} />

<Alert className="big-spacer-top" variant="warning">
{translateWithParameters('users.tokens.new_token_created', token)}
</Alert>
</>
) : (
<>
<RenderOptions
checked={mode}
name={'token-mode'}
onCheck={this.setMode}
optionLabelKey="onboarding.token"
options={[TokenMode.use_existing_token, TokenMode.generate_token]}
/>

<div className="big-spacer-top">
{mode === TokenMode.generate_token && (
<>
<input
className="input-super-large spacer-right text-middle"
onChange={this.handleChange}
placeholder={translate('onboarding.token.generate_token.placeholder')}
required={true}
type="text"
value={tokenName}
/>
<Button className="text-middle" disabled={!tokenName} onClick={this.getNewToken}>
{translate('onboarding.token.generate')}
</Button>
</>
)}

{mode === TokenMode.use_existing_token && (
<input
className="input-super-large spacer-right text-middle"
onChange={this.setExistingToken}
placeholder={translate('onboarding.token.use_existing_token.placeholder')}
required={true}
type="text"
value={existingToken}
/>
)}
</div>
</>
)}
</ConfirmModal>
);
}
}

+ 111
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/EditTravisYmlStep.tsx View File

@@ -0,0 +1,111 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 AnalysisCommandTravis from '../../components/commands/AnalysisCommandTravis';
import { Button } from '../../../../components/ui/buttons';
import { translate } from '../../../../helpers/l10n';
import Step from '../../components/Step';
import BuildSystemForm from '../../components/BuildSystemForm';
import { StepProps } from '../../utils';

interface BuildProps {
buildCallback: (build: string) => void;
buildType?: string;
}

export default function EditTravisYmlStep({
buildCallback,
buildType,
component,
finished,
hasStepAfter,
onContinue,
onOpen,
open,
organization,
stepNumber,
token
}: StepProps & BuildProps) {
const [build, setBuild] = React.useState<string | undefined>(buildType || undefined);

if (!build && buildType) {
setBuild(buildType);
}

const isJavaBuild = build && ['gradle', 'maven'].includes(build);

if (hasStepAfter && build) {
hasStepAfter(!isJavaBuild);
}

const chooseBuild = (build: string) => {
buildCallback(build);
setBuild(build);
};

const renderForm = () => (
<div className="boxed-group-inner">
<div className="flex-columns">
<div className="flex-column-full">
<BuildSystemForm build={build} setBuild={chooseBuild} />

{build && (
<AnalysisCommandTravis
buildType={build}
component={component}
organization={organization}
token={token}
/>
)}
</div>
</div>
<div className="big-spacer-top">
{build && (
<Button className="js-continue" onClick={onContinue}>
{translate(isJavaBuild ? 'onboarding.finish' : 'continue')}
</Button>
)}
</div>
</div>
);

const renderResult = () => null;

return (
<Step
finished={Boolean(finished)}
onOpen={onOpen}
open={open}
renderForm={renderForm}
renderResult={renderResult}
stepNumber={stepNumber}
stepTitle={
<FormattedMessage
defaultMessage={translate('onboarding.analysis.travis.sonarcloud')}
id="onboarding.analysis.travis.sonarcloud"
values={{
filename: <code className="rule">.travis.yml</code>
}}
/>
}
/>
);
}

+ 112
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/EncryptYourTokenStep.tsx View File

@@ -0,0 +1,112 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 EditTokenModal from './EditTokenModal';
import { Button, EditButton } from '../../../../components/ui/buttons';
import { translate } from '../../../../helpers/l10n';
import Step from '../../components/Step';
import CodeSnippet from '../../../../components/common/CodeSnippet';

export interface YourTokenProps {
component: T.Component;
currentUser: T.LoggedInUser;
hasStepAfter?: (hasStepAfter: boolean) => void;
onContinue: VoidFunction;
onOpen: VoidFunction;
open: boolean;
organization?: string;
setToken: (token: string) => void;
stepNumber: number;
token?: string;
}

export default function EncryptYourTokenStep({
component,
currentUser,
onContinue,
onOpen,
open,
setToken,
stepNumber,
token
}: YourTokenProps) {
const [showModal, toggleModal] = React.useState<boolean>(false);

const close = () => toggleModal(!showModal);

const save = (token: string) => {
setToken(token);
close();
};

const command = `travis encrypt {token}`;
const renderCommand = () => (
<div className="spacer-bottom">
travis encrypt {token}
<EditButton className="edit-token spacer-left" onClick={() => toggleModal(true)} />
</div>
);

const renderForm = () => (
<div className="boxed-group-inner">
{showModal && (
<EditTokenModal
component={component}
currentUser={currentUser}
onClose={close}
onSave={save}
/>
)}

<div className="display-flex-space-between">
<div className="display-inline-block">
<a
href="https://docs.travis-ci.com/user/encryption-keys/#usage"
rel="noopener noreferrer"
target="_blank">
{translate('onboarding.analysis.with.travis.encrypt.docs.link.label')}
</a>
<br />
<CodeSnippet isOneLine={true} render={renderCommand} snippet={command} wrap={true} />
</div>
</div>

<div className="big-spacer-top">
<Button className="js-continue" onClick={onContinue}>
{translate('continue')}
</Button>
</div>
</div>
);

const renderResult = () => null;

return (
<Step
finished={true}
onOpen={onOpen}
open={open}
renderForm={renderForm}
renderResult={renderResult}
stepNumber={stepNumber}
stepTitle={translate('onboarding.analysis.with.travis.encrypt.title')}
/>
);
}

+ 39
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/CreateSonarPropertiesStep-test.tsx View File

@@ -0,0 +1,39 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 CreateSonarPropertiesStep from '../CreateSonarPropertiesStep';
import { StepProps } from '../../../utils';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

function shallowRender(props: Partial<StepProps> = {}) {
return shallow(
<CreateSonarPropertiesStep
onContinue={jest.fn()}
onOpen={jest.fn()}
open={true}
stepNumber={1}
{...props}
/>
);
}

+ 132
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/EditTokenModal-test.tsx View File

@@ -0,0 +1,132 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 EditTokenModal, { TokenMode } from '../EditTokenModal';
import { mockComponent, mockEvent, mockLoggedInUser } from '../../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../../helpers/testUtils';
import { generateToken, getTokens, revokeToken } from '../../../../../api/user-tokens';
import { getUniqueTokenName } from '../../../utils';

jest.mock('../../../../../api/user-tokens', () => ({
generateToken: jest.fn().mockResolvedValue({
name: 'baz',
createdAt: '2019-01-21T08:06:00+0100',
login: 'luke',
token: 'token_value'
}),
getTokens: jest.fn().mockResolvedValue([
{
name: 'foo',
createdAt: '2019-01-15T15:06:33+0100',
lastConnectionDate: '2019-01-18T15:06:33+0100'
},
{ name: 'bar', createdAt: '2019-01-18T15:06:33+0100' }
]),
revokeToken: jest.fn().mockResolvedValue(Promise.resolve())
}));

jest.mock('../../../utils', () => ({
getUniqueTokenName: jest.fn().mockReturnValue('lightsaber-9000')
}));

beforeEach(() => {
jest.clearAllMocks();
});

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should get tokens and unique name', async () => {
const wrapper = shallowRender();
const { getTokensAndName } = wrapper.instance();

getTokensAndName();
await waitAndUpdate(wrapper);

expect(getTokens).toHaveBeenCalled();
expect(getUniqueTokenName).toHaveBeenCalled();
expect(wrapper.state('tokenName')).toBe('lightsaber-9000');
});

it('should get a new token', async () => {
const wrapper = shallowRender();
const { getNewToken } = wrapper.instance();

getNewToken();
await waitAndUpdate(wrapper);

expect(generateToken).toHaveBeenCalled();
expect(wrapper.state('token')).toBe('token_value');
});

it('should handle token revocation', async () => {
const wrapper = shallowRender();
const { getTokensAndName, handleTokenRevoke } = wrapper.instance();

getTokensAndName();
await waitAndUpdate(wrapper);
handleTokenRevoke();
await waitAndUpdate(wrapper);

expect(revokeToken).toHaveBeenCalled();
expect(wrapper.state('token')).toBe('');
expect(wrapper.state('tokenName')).toBe('');
});

it('should handle change on user input', () => {
const wrapper = shallowRender();
const instance = wrapper.instance();

instance.handleChange(mockEvent({ target: { value: 'my-token' } }));
expect(wrapper.state('tokenName')).toBe('my-token');
});

it('should set existing token', () => {
const wrapper = shallowRender();
const instance = wrapper.instance();

instance.setExistingToken(mockEvent({ target: { value: 'my-token' } }));
expect(wrapper.state('existingToken')).toBe('my-token');
});

it('should set mode', () => {
const wrapper = shallowRender();
const instance = wrapper.instance();

instance.setMode(TokenMode.generate_token);
expect(wrapper.state('mode')).toBe(TokenMode.generate_token);
instance.setMode(TokenMode.use_existing_token);
expect(wrapper.state('mode')).toBe(TokenMode.use_existing_token);
});

it('should call onSave with the token', () => {});

function shallowRender() {
return shallow<EditTokenModal>(
<EditTokenModal
component={mockComponent()}
currentUser={mockLoggedInUser()}
onClose={jest.fn()}
onSave={jest.fn()}
/>
);
}

+ 49
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/EditTravisYmlStep-test.tsx View File

@@ -0,0 +1,49 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 EditTravisYmlStep from '../EditTravisYmlStep';
import { StepProps } from '../../../utils';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should render the form correctly', () => {
expect(
shallowRender()
.find('Step')
.prop<Function>('renderForm')()
).toMatchSnapshot();
});

function shallowRender(props: Partial<StepProps> = {}) {
return shallow(
<EditTravisYmlStep
buildCallback={jest.fn()}
buildType="maven"
onContinue={jest.fn()}
onOpen={jest.fn()}
open={true}
stepNumber={1}
{...props}
/>
);
}

+ 43
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/EncryptYourTokenStep-test.tsx View File

@@ -0,0 +1,43 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 EncryptYourTokenStep from '../EncryptYourTokenStep';
import { StepProps } from '../../../utils';
import { mockComponent, mockLoggedInUser } from '../../../../../helpers/testMocks';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

function shallowRender(props: Partial<StepProps> = {}) {
return shallow(
<EncryptYourTokenStep
component={mockComponent()}
currentUser={mockLoggedInUser()}
onContinue={jest.fn()}
onOpen={jest.fn()}
open={true}
setToken={jest.fn()}
stepNumber={1}
{...props}
/>
);
}

+ 27
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/__snapshots__/CreateSonarPropertiesStep-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<Step
finished={false}
onOpen={[MockFunction]}
open={true}
renderForm={[Function]}
renderResult={[Function]}
stepNumber={1}
stepTitle={
<FormattedMessage
defaultMessage="onboarding.analysis.with.travis.sonar.properties.title"
id="onboarding.analysis.with.travis.sonar.properties.title"
values={
Object {
"filename": <code
className="rule"
>
sonar.properties
</code>,
}
}
/>
}
/>
`;

+ 56
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/__snapshots__/EditTokenModal-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<ConfirmModal
confirmButtonText="save"
confirmDisable={true}
header="onboarding.token.header"
onClose={[MockFunction]}
onConfirm={[Function]}
>
<p
className="spacer-bottom"
>
<FormattedMessage
defaultMessage="onboarding.token.text"
id="onboarding.token.text"
values={
Object {
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/account/security"
>
onboarding.token.text.user_account
</Link>,
}
}
/>
</p>
<RenderOptions
checked="use_existing_token"
name="token-mode"
onCheck={[Function]}
optionLabelKey="onboarding.token"
options={
Array [
"use_existing_token",
"generate_token",
]
}
/>
<div
className="big-spacer-top"
>
<input
className="input-super-large spacer-right text-middle"
onChange={[Function]}
placeholder="onboarding.token.use_existing_token.placeholder"
required={true}
type="text"
value=""
/>
</div>
</ConfirmModal>
`;

+ 59
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/__snapshots__/EditTravisYmlStep-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<Step
finished={false}
onOpen={[MockFunction]}
open={true}
renderForm={[Function]}
renderResult={[Function]}
stepNumber={1}
stepTitle={
<FormattedMessage
defaultMessage="onboarding.analysis.travis.sonarcloud"
id="onboarding.analysis.travis.sonarcloud"
values={
Object {
"filename": <code
className="rule"
>
.travis.yml
</code>,
}
}
/>
}
/>
`;

exports[`should render the form correctly 1`] = `
<div
className="boxed-group-inner"
>
<div
className="flex-columns"
>
<div
className="flex-column-full"
>
<BuildSystemForm
build="maven"
setBuild={[Function]}
/>
<AnalysisCommandTravis
buildType="maven"
/>
</div>
</div>
<div
className="big-spacer-top"
>
<Button
className="js-continue"
onClick={[MockFunction]}
>
onboarding.finish
</Button>
</div>
</div>
`;

+ 13
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/steps/__tests__/__snapshots__/EncryptYourTokenStep-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<Step
finished={true}
onOpen={[MockFunction]}
open={true}
renderForm={[Function]}
renderResult={[Function]}
stepNumber={1}
stepTitle="onboarding.analysis.with.travis.encrypt.title"
/>
`;

+ 119
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/utils.ts View File

@@ -0,0 +1,119 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 const PROJECT_ONBOARDING_DONE = 'sonarcloud.project.onboarding.finished';
export const PROJECT_ONBOARDING_MODE_ID = 'sonarcloud.project.onboarding.mode.id';
export const PROJECT_STEP_PROGRESS = 'sonarcloud.project.onboarding.step.progress';

export interface AlmLanguagesStats {
[k: string]: number;
}

export interface Alm {
id: string;
name: string;
}

export interface AnalysisMode {
icon?: string;
id: string;
name: string;
}

export enum ALM_KEYS {
BITBUCKET = 'BITBUCKET',
GITHUB = 'GITHUB',
MICROSOFT = 'MICROSOFT'
}

export const alms: { [k: string]: Alm } = {
[ALM_KEYS.BITBUCKET]: {
id: ALM_KEYS.BITBUCKET,
name: 'BitBucket'
},
[ALM_KEYS.GITHUB]: {
id: ALM_KEYS.GITHUB,
name: 'GitHub'
},
[ALM_KEYS.MICROSOFT]: {
id: ALM_KEYS.MICROSOFT,
name: 'Microsoft'
}
};

export const modes: AnalysisMode[] = [
{
id: 'travis',
name: 'With Travis CI'
},
{
id: 'other',
name: 'With other CI tools'
},
{
id: 'manual',
name: 'Manually'
}
];

export const autoScanMode: AnalysisMode = {
id: 'autoscan',
name: 'SonarCloud Automatic Analysis'
};

export interface TutorialProps {
component: T.Component;
currentUser: T.LoggedInUser;
onDone: VoidFunction;
setToken: (token: string) => void;
style?: object;
token: string | undefined;
}

interface AutoScannableProps {
[k: string]: number;
}

export function isAutoScannable(languages: AutoScannableProps = {}) {
const allowed = [
'ABAP',
'Apex',
'CSS',
'Flex',
'Go',
'HTML',
'JavaScript',
'Kotlin',
'PHP',
'Python',
'Ruby',
'Scala',
'Swift',
'TypeScript',
'TSQL',
'XML'
];
const notAllowed = ['Java', 'C#', 'Visual Basic', 'C', 'C++', 'Objective-C'];

const withAllowedLanguages = !!Object.keys(languages).find(l => allowed.includes(l));
const withNotAllowedLanguages = !!Object.keys(languages).find(l => notAllowed.includes(l));

return { withAllowedLanguages, withNotAllowedLanguages };
}

+ 72
- 0
server/sonar-web/src/main/js/apps/tutorials/components/AnalyzeTutorialDone.tsx View File

@@ -0,0 +1,72 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { getBaseUrl } from '../../../helpers/urls';
import { translate } from '../../../helpers/l10n';
import BackButton from '../../../components/controls/BackButton';

interface Props {
setTutorialDone: (done: boolean) => void;
}

export default function AnalyzeTutorialDone({ setTutorialDone }: Props) {
return (
<div className="page-analysis-container page-analysis-container-sonarcloud">
<BackButton
onClick={() => setTutorialDone(false)}
tooltip={translate('onboarding.tutorial.return_to_tutorial')}>
{translate('back')}
</BackButton>
<div className="page-analysis page-analysis-waiting huge-spacer-top huge-spacer-bottom">
<img
alt="SonarCloud"
src={`${getBaseUrl()}/images/sonarcloud/analysis/Waiting-for-analysis.svg`}
/>
<h1 className="big-spacer-bottom huge-spacer-top">
{translate('onboarding.finished.title')}
</h1>
<p>{translate('onboarding.finished.text')}</p>

<div className="links huge-spacer-top huge-spacer-bottom">
<h2 className="huge-spacer-bottom">{translate('onboarding.finished.links.title')}</h2>
<ul>
<li className="big-spacer-bottom">
<a
href="https://sonarcloud.io/documentation/user-guide/quality-gates/"
rel="noopener noreferrer"
target="_blank">
What is a Quality Gate?
</a>
</li>
<li className="big-spacer-bottom">
<a
href="https://sonarcloud.io/documentation/instance-administration/quality-profiles/"
rel="noopener noreferrer"
target="_blank">
Configure your Quality Profiles
</a>
.
</li>
</ul>
</div>
</div>
</div>
);
}

+ 39
- 0
server/sonar-web/src/main/js/apps/tutorials/components/BuildSystemForm.tsx View File

@@ -0,0 +1,39 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { RenderOptions } from './RenderOptions';

interface Props {
build: string | undefined;
setBuild: (build: string) => void;
}

export default function BuildSystemForm({ build, setBuild }: Props) {
return (
<RenderOptions
checked={build}
name="build"
onCheck={setBuild}
optionLabelKey={'onboarding.build'}
options={['maven', 'gradle', 'make', 'other']}
titleLabelKey="onboarding.build"
/>
);
}

+ 38
- 42
server/sonar-web/src/main/js/apps/tutorials/components/LanguageForm.tsx View File

@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { RenderOptions } from './RenderOptions';
import NewProjectForm from './NewProjectForm';
import RadioToggle from '../../../components/controls/RadioToggle';
import { translate } from '../../../helpers/l10n';
@@ -28,12 +29,29 @@ interface Props {
component?: T.Component;
config?: LanguageConfig;
onDone: (config: LanguageConfig) => void;
onReset: () => void;
onReset: VoidFunction;
organization?: string;
}

type State = LanguageConfig;

export interface RenderOSProps {
os: string | undefined;
setOS: (os: string) => void;
}
export function RenderOS(props: RenderOSProps) {
return (
<RenderOptions
checked={props.os}
name="os"
onCheck={props.setOS}
optionLabelKey={'onboarding.language.os'}
options={['linux', 'win', 'mac']}
titleLabelKey={'onboarding.language.os'}
/>
);
}

export default class LanguageForm extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
@@ -76,48 +94,25 @@ export default class LanguageForm extends React.PureComponent<Props, State> {
};

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>
<RenderOptions
checked={this.state.javaBuild}
name="java-build"
onCheck={this.handleJavaBuildChange}
optionLabelKey="onboarding.language.java.build_technology"
options={['maven', 'gradle']}
titleLabelKey="onboarding.language.java.build_technology"
/>
);

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>
<RenderOptions
checked={this.state.cFamilyCompiler}
name="c-family-compiler"
onCheck={this.handleCFamilyCompilerChange}
optionLabelKey={'onboarding.language.c-family.compiler'}
options={['msvc', 'clang-gcc']}
titleLabelKey={'onboarding.language.c-family.compiler'}
/>
);

renderProjectKey = () => {
@@ -164,8 +159,9 @@ export default class LanguageForm extends React.PureComponent<Props, State> {
</div>
{language === 'java' && this.renderJavaBuild()}
{language === 'c-family' && this.renderCFamilyCompiler()}
{((language === 'c-family' && cFamilyCompiler === 'clang-gcc') || language === 'other') &&
this.renderOS()}
{((language === 'c-family' && cFamilyCompiler === 'clang-gcc') || language === 'other') && (
<RenderOS os={this.state.os} setOS={this.handleOSChange} />
)}
{this.renderProjectKey()}
</>
);

+ 1
- 1
server/sonar-web/src/main/js/apps/tutorials/components/NewProjectForm.tsx View File

@@ -23,7 +23,7 @@ import { DeleteButton, SubmitButton } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';

interface Props {
onDelete: () => void;
onDelete: VoidFunction;
onDone: (projectKey: string) => void;
organization?: string;
projectKey?: string;

+ 6
- 6
server/sonar-web/src/main/js/apps/tutorials/components/ProjectAnalysisStep.tsx View File

@@ -28,7 +28,7 @@ interface Props {
component?: T.Component;
displayRowLayout?: boolean;
onFinish?: (projectKey?: string) => void;
onReset?: () => void;
onReset?: VoidFunction;
open: boolean;
organization?: string;
stepNumber: number;
@@ -39,17 +39,17 @@ interface State {
config?: LanguageConfig;
}

export function getProjectKey(config?: LanguageConfig, component?: T.Component) {
return (component && component.key) || (config && config.projectKey);
}

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;
const projectKey = config.language !== 'java' ? getProjectKey(config) : undefined;
this.props.onFinish(projectKey);
}
};

+ 148
- 0
server/sonar-web/src/main/js/apps/tutorials/components/ProjectAnalysisStepFromBuildTool.tsx View File

@@ -0,0 +1,148 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 BuildSystemForm from './BuildSystemForm';
import AnalysisCommandCustom from './commands/AnalysisCommandCustom';
import AnalysisCommandOtherCI from './commands/AnalysisCommandOtherCI';
import { translate } from '../../../helpers/l10n';
import { get, save } from '../../../helpers/storage';
import { PROJECT_STEP_PROGRESS } from '../analyzeProject/utils';

export enum ProjectAnalysisModes {
CI = 'CI',
Custom = 'Custom'
}

export interface Props {
component: T.Component;
currentUser: T.LoggedInUser;
displayRowLayout?: boolean;
mode: ProjectAnalysisModes;
onDone: VoidFunction;
onReset?: VoidFunction;
open: boolean;
organization?: string;
setToken: (token: string) => void;
stepNumber: number;
token?: string;
}

export default function ProjectAnalysisStepFromBuildTool({
component,
currentUser,
displayRowLayout,
mode,
onDone,
open,
organization,
setToken,
stepNumber,
token
}: Props) {
const [build, setBuild] = React.useState<string | undefined>(undefined);
const [os, setOS] = React.useState<string | undefined>(undefined);

React.useEffect(() => {
const value = get(PROJECT_STEP_PROGRESS, component.key);
if (value) {
try {
const data = JSON.parse(value);
setBuild(data.build);
setOS(data.os);
} catch (e) {
// Let's start from scratch
}
}
}, [component.key]);

const saveAndFinish = (data: object) => {
save(
PROJECT_STEP_PROGRESS,
JSON.stringify({
...data,
build
}),
component.key
);

onDone();
};

const renderForm = () => {
const languageComponent = <BuildSystemForm build={build} setBuild={setBuild} />;

let AnalysisComponent = null;

if (mode === ProjectAnalysisModes.Custom) {
AnalysisComponent = AnalysisCommandCustom;
} else if (mode === ProjectAnalysisModes.CI) {
AnalysisComponent = AnalysisCommandOtherCI;
}

if (displayRowLayout) {
return (
<div className="boxed-group-inner">
<div className="display-flex-column">
{languageComponent}
{AnalysisComponent && (
<div className="huge-spacer-top">
<AnalysisComponent
buildType={build}
component={component}
currentUser={currentUser}
onDone={saveAndFinish}
organization={organization}
os={os}
setToken={setToken}
small={true}
token={token}
/>
</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>
);
};

const renderResult = () => null;

return (
<Step
finished={false}
onOpen={() => {}}
open={open}
renderForm={renderForm}
renderResult={renderResult}
stepNumber={stepNumber}
stepTitle={translate('onboarding.analysis.header')}
/>
);
}

+ 55
- 0
server/sonar-web/src/main/js/apps/tutorials/components/RenderOptions.tsx View File

@@ -0,0 +1,55 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 RadioToggle from '../../../components/controls/RadioToggle';

interface RenderOptionsProps {
checked: string | undefined;
name: string;
onCheck: (checked: string) => void;
optionLabelKey: string;
options: string[];
titleLabelKey?: string;
}

export function RenderOptions({
checked,
onCheck,
optionLabelKey,
options,
titleLabelKey
}: RenderOptionsProps) {
return (
<div className="big-spacer-top">
{titleLabelKey && <h4 className="spacer-bottom">{translate(titleLabelKey)}</h4>}

<RadioToggle
name={name}
onCheck={onCheck}
options={options.map(build => ({
label: translate(optionLabelKey, build),
value: build
}))}
value={checked}
/>
</div>
);
}

+ 2
- 2
server/sonar-web/src/main/js/apps/tutorials/components/Step.tsx View File

@@ -22,8 +22,8 @@ import * as React from 'react';
import * as classNames from 'classnames';

interface Props {
finished: boolean;
onOpen: () => void;
finished?: boolean;
onOpen: VoidFunction;
open: boolean;
renderForm: () => React.ReactNode;
renderResult: () => React.ReactNode;

+ 4
- 18
server/sonar-web/src/main/js/apps/tutorials/components/TokenStep.tsx View File

@@ -27,14 +27,15 @@ import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessI
import { DeleteButton, SubmitButton, Button } from '../../../components/ui/buttons';
import { getTokens, generateToken, revokeToken } from '../../../api/user-tokens';
import { translate } from '../../../helpers/l10n';
import { getUniqueTokenName } from '../utils';

interface Props {
currentUser: { login: string };
currentUser: Pick<T.LoggedInUser, 'login'>;
finished: boolean;
initialTokenName?: string;
open: boolean;
onContinue: (token: string) => void;
onOpen: () => void;
onOpen: VoidFunction;
stepNumber: number;
}

@@ -70,7 +71,7 @@ export default class TokenStep extends React.PureComponent<Props, State> {
this.props.initialTokenName !== undefined &&
this.props.initialTokenName === this.state.tokenName
) {
this.setState({ tokenName: this.getUniqueTokenName(tokens) });
this.setState({ tokenName: getUniqueTokenName(tokens) });
}
}
},
@@ -85,21 +86,6 @@ export default class TokenStep extends React.PureComponent<Props, State> {
getToken = () =>
this.state.selection === 'generate' ? this.state.token : this.state.existingToken;

getUniqueTokenName = (tokens: T.UserToken[]) => {
const { initialTokenName = '' } = this.props;
const hasToken = (name: string) => tokens.find(token => token.name === name) !== undefined;

if (!hasToken(initialTokenName)) {
return initialTokenName;
}

let i = 1;
while (hasToken(`${initialTokenName} ${i}`)) {
i++;
}
return `${initialTokenName} ${i}`;
};

canContinue = () => {
const { existingToken, selection, token } = this.state;
const validExistingToken = existingToken.match(/^[a-z0-9]+$/) != null;

+ 26
- 0
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/AnalyzeTutorialDone-test.tsx View File

@@ -0,0 +1,26 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 AnalyzeTutorialDone from '../AnalyzeTutorialDone';

it('should render correctly', () => {
expect(shallow(<AnalyzeTutorialDone setTutorialDone={jest.fn()} />)).toMatchSnapshot();
});

+ 28
- 0
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/BuildSystemForm-test.tsx View File

@@ -0,0 +1,28 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 BuildSystemForm from '../BuildSystemForm';

it('should render correctly', () => {
const build = 'maven';
const setBuild = jest.fn();
expect(shallow(<BuildSystemForm build={build} setBuild={setBuild} />)).toMatchSnapshot();
});

+ 0
- 62
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/LanguageForm-test.tsx View File

@@ -35,22 +35,6 @@ it('selects java', () => {
(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#', () => {
@@ -73,42 +57,6 @@ it('selects c-family', () => {
(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', () => {
@@ -118,14 +66,4 @@ it('selects other', () => {
(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' });
});

+ 3
- 3
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/NewProjectForm-test.tsx View File

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { mount } from 'enzyme';
import { shallow } from 'enzyme';
import NewProjectForm from '../NewProjectForm';
import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils';

@@ -31,7 +31,7 @@ jest.mock('../../../../components/icons-components/DeleteIcon');

it('creates new project', async () => {
const onDone = jest.fn();
const wrapper = mount(<NewProjectForm onDelete={jest.fn()} onDone={onDone} />);
const wrapper = shallow(<NewProjectForm onDelete={jest.fn()} onDone={onDone} />);
expect(wrapper).toMatchSnapshot();
change(wrapper.find('input'), 'foo');
submit(wrapper.find('form'));
@@ -43,7 +43,7 @@ it('creates new project', async () => {

it('deletes project', async () => {
const onDelete = jest.fn();
const wrapper = mount(<NewProjectForm onDelete={onDelete} onDone={jest.fn()} />);
const wrapper = shallow(<NewProjectForm onDelete={onDelete} onDone={jest.fn()} />);
wrapper.setState({ done: true, loading: false, projectKey: 'foo' });
expect(wrapper).toMatchSnapshot();
(wrapper.find('DeleteButton').prop('onClick') as Function)();

+ 31
- 0
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/ProjectAnalysisStep-test.tsx View File

@@ -0,0 +1,31 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { mockComponent } from '../../../../helpers/testMocks';
import ProjectAnalysisStep from '../ProjectAnalysisStep';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

function shallowRender() {
return shallow(<ProjectAnalysisStep component={mockComponent()} open={true} stepNumber={1} />);
}

+ 69
- 0
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/ProjectAnalysisStepFromBuildTool-test.tsx View File

@@ -0,0 +1,69 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 ProjectAnalysisStepFromBuildTool, {
ProjectAnalysisModes,
Props
} from '../ProjectAnalysisStepFromBuildTool';
import { mockComponent, mockLoggedInUser } from '../../../../helpers/testMocks';

jest.mock('../../../../helpers/storage', () => ({
get: jest.fn().mockReturnValue(
JSON.stringify({
build: 'maven',
os: 'linux'
})
),
save: jest.fn()
}));

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should render the form correctly', () => {
expect(
shallowRender({ mode: ProjectAnalysisModes.CI })
.find('Step')
.prop<Function>('renderForm')()
).toMatchSnapshot();

expect(
shallowRender({ displayRowLayout: true })
.find('Step')
.prop<Function>('renderForm')()
).toMatchSnapshot();
});

function shallowRender(props: Partial<Props> = {}) {
return shallow(
<ProjectAnalysisStepFromBuildTool
component={mockComponent()}
currentUser={mockLoggedInUser()}
mode={ProjectAnalysisModes.Custom}
onDone={jest.fn()}
open={true}
setToken={jest.fn()}
stepNumber={1}
{...props}
/>
);
}

+ 1
- 1
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TokenStep-test.tsx View File

@@ -28,7 +28,7 @@ jest.mock('../../../../api/user-tokens', () => ({
revokeToken: () => Promise.resolve()
}));

const currentUser = { login: 'user' };
const currentUser: Pick<T.LoggedInUser, 'login'> = { login: 'user' };

it('generates token', async () => {
const wrapper = shallow(

+ 64
- 0
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/AnalyzeTutorialDone-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<div
className="page-analysis-container page-analysis-container-sonarcloud"
>
<BackButton
onClick={[Function]}
tooltip="onboarding.tutorial.return_to_tutorial"
>
back
</BackButton>
<div
className="page-analysis page-analysis-waiting huge-spacer-top huge-spacer-bottom"
>
<img
alt="SonarCloud"
src="/images/sonarcloud/analysis/Waiting-for-analysis.svg"
/>
<h1
className="big-spacer-bottom huge-spacer-top"
>
onboarding.finished.title
</h1>
<p>
onboarding.finished.text
</p>
<div
className="links huge-spacer-top huge-spacer-bottom"
>
<h2
className="huge-spacer-bottom"
>
onboarding.finished.links.title
</h2>
<ul>
<li
className="big-spacer-bottom"
>
<a
href="https://sonarcloud.io/documentation/user-guide/quality-gates/"
rel="noopener noreferrer"
target="_blank"
>
What is a Quality Gate?
</a>
</li>
<li
className="big-spacer-bottom"
>
<a
href="https://sonarcloud.io/documentation/instance-administration/quality-profiles/"
rel="noopener noreferrer"
target="_blank"
>
Configure your Quality Profiles
</a>
.
</li>
</ul>
</div>
</div>
</div>
`;

+ 19
- 0
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/BuildSystemForm-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<RenderOptions
checked="maven"
name="build"
onCheck={[MockFunction]}
optionLabelKey="onboarding.build"
options={
Array [
"maven",
"gradle",
"make",
"other",
]
}
titleLabelKey="onboarding.build"
/>
`;

+ 25
- 540
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/LanguageForm-test.tsx.snap View File

@@ -73,298 +73,17 @@ exports[`selects c-family 1`] = `
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>
</Fragment>
`;

exports[`selects c-family 2`] = `
<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]}
/>
</Fragment>
`;

exports[`selects c-family 3`] = `
<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>
</Fragment>
`;

exports[`selects c-family 4`] = `
<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"
<RenderOptions
name="c-family-compiler"
onCheck={[Function]}
optionLabelKey="onboarding.language.c-family.compiler"
options={
Array [
"msvc",
"clang-gcc",
]
}
titleLabelKey="onboarding.language.c-family.compiler"
/>
</Fragment>
`;
@@ -400,155 +119,18 @@ exports[`selects java 1`] = `
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>
</Fragment>
`;

exports[`selects java 2`] = `
<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>
</Fragment>
`;

exports[`selects java 3`] = `
<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>
<RenderOptions
name="java-build"
onCheck={[Function]}
optionLabelKey="onboarding.language.java.build_technology"
options={
Array [
"maven",
"gradle",
]
}
titleLabelKey="onboarding.language.java.build_technology"
/>
</Fragment>
`;

@@ -583,105 +165,8 @@ exports[`selects other 1`] = `
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>
</Fragment>
`;

exports[`selects other 2`] = `
<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]}
<RenderOS
setOS={[Function]}
/>
</Fragment>
`;

+ 135
- 279
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/NewProjectForm-test.tsx.snap View File

@@ -1,321 +1,177 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`creates new project 1`] = `
<NewProjectForm
onDelete={[MockFunction]}
onDone={[MockFunction]}
<div
className="big-spacer-top"
>
<div
className="big-spacer-top"
<h4
className="spacer-bottom"
>
<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}
>
onboarding.language.project_key
</h4>
<form
onSubmit={[Function]}
Done
</SubmitButton>
<div
className="note spacer-top abs-width-300"
>
<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>
onboarding.project_key_requirement
</div>
</form>
</div>
`;

exports[`creates new project 2`] = `
<NewProjectForm
onDelete={[MockFunction]}
onDone={[MockFunction]}
<div
className="big-spacer-top"
>
<div
className="big-spacer-top"
<h4
className="spacer-bottom"
>
<h4
className="spacer-bottom"
>
onboarding.language.project_key
</h4>
<form
onSubmit={[Function]}
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"
>
<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>
onboarding.project_key_requirement
</div>
</form>
</div>
`;

exports[`creates new project 3`] = `
<NewProjectForm
onDelete={[MockFunction]}
onDone={
[MockFunction] {
"calls": Array [
Array [
"foo",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
}
}
<div
className="big-spacer-top"
>
<div
className="big-spacer-top"
<h4
className="spacer-bottom"
>
<h4
className="spacer-bottom"
onboarding.language.project_key
</h4>
<div>
<span
className="spacer-right text-middle"
>
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"
>
<MOCKDeleteIcon />
</button>
</Button>
</ButtonIcon>
</DeleteButton>
</div>
foo
</span>
<DeleteButton
className="button-small text-middle"
onClick={[Function]}
/>
</div>
</NewProjectForm>
</div>
`;

exports[`deletes project 1`] = `
<NewProjectForm
onDelete={[MockFunction]}
onDone={[MockFunction]}
<div
className="big-spacer-top"
>
<div
className="big-spacer-top"
<h4
className="spacer-bottom"
>
<h4
className="spacer-bottom"
onboarding.language.project_key
</h4>
<div>
<span
className="spacer-right text-middle"
>
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"
>
<MOCKDeleteIcon />
</button>
</Button>
</ButtonIcon>
</DeleteButton>
</div>
foo
</span>
<DeleteButton
className="button-small text-middle"
onClick={[Function]}
/>
</div>
</NewProjectForm>
</div>
`;

exports[`deletes project 2`] = `
<NewProjectForm
onDelete={[MockFunction]}
onDone={[MockFunction]}
<div
className="big-spacer-top"
>
<div
className="big-spacer-top"
<h4
className="spacer-bottom"
>
<h4
className="spacer-bottom"
onboarding.language.project_key
</h4>
<div>
<span
className="spacer-right text-middle"
>
onboarding.language.project_key
</h4>
<div>
<span
className="spacer-right text-middle"
>
foo
</span>
<i
className="spinner text-middle"
/>
</div>
foo
</span>
<i
className="spinner text-middle"
/>
</div>
</NewProjectForm>
</div>
`;

exports[`deletes project 3`] = `
<NewProjectForm
onDelete={
[MockFunction] {
"calls": Array [
Array [],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
}
}
onDone={[MockFunction]}
<div
className="big-spacer-top"
>
<div
className="big-spacer-top"
<h4
className="spacer-bottom"
>
<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}
>
onboarding.language.project_key
</h4>
<form
onSubmit={[Function]}
Done
</SubmitButton>
<div
className="note spacer-top abs-width-300"
>
<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>
onboarding.project_key_requirement
</div>
</form>
</div>
`;

+ 13
- 0
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/ProjectAnalysisStep-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<Step
finished={false}
onOpen={[Function]}
open={true}
renderForm={[Function]}
renderResult={[Function]}
stepNumber={1}
stepTitle="onboarding.analysis.header"
/>
`;

+ 91
- 0
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/ProjectAnalysisStepFromBuildTool-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<Step
finished={false}
onOpen={[Function]}
open={true}
renderForm={[Function]}
renderResult={[Function]}
stepNumber={1}
stepTitle="onboarding.analysis.header"
/>
`;

exports[`should render the form correctly 1`] = `
<div
className="boxed-group-inner"
>
<div
className="flex-columns"
>
<div
className="flex-column flex-column-half bordered-right"
>
<BuildSystemForm
setBuild={[Function]}
/>
</div>
<div
className="flex-column flex-column-half"
>
[Function]
</div>
</div>
</div>
`;

exports[`should render the form correctly 2`] = `
<div
className="boxed-group-inner"
>
<div
className="display-flex-column"
>
<BuildSystemForm
setBuild={[Function]}
/>
<div
className="huge-spacer-top"
>
<AnalysisCommandCustom
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
currentUser={
Object {
"groups": Array [],
"isLoggedIn": true,
"login": "luke",
"name": "Skywalker",
"scmAccounts": Array [],
}
}
onDone={[Function]}
setToken={[MockFunction]}
small={true}
/>
</div>
</div>
</div>
`;

+ 9
- 12
server/sonar-web/src/main/js/apps/tutorials/components/commands/AnalysisCommand.tsx View File

@@ -26,6 +26,7 @@ import ClangGCC from './ClangGCC';
import Other from './Other';
import { getHostUrl } from '../../../../helpers/urls';
import { LanguageConfig } from '../../utils';
import { getProjectKey } from '../ProjectAnalysisStep';

interface Props {
component?: T.Component;
@@ -36,10 +37,6 @@ interface Props {
}

export default class AnalysisCommand extends React.PureComponent<Props> {
getProjectKey = ({ component, languageConfig } = this.props) => {
return (component && component.key) || languageConfig.projectKey;
};

renderCommandForMaven = () => {
const { component, token } = this.props;
if (!token) {
@@ -71,8 +68,8 @@ export default class AnalysisCommand extends React.PureComponent<Props> {
};

renderCommandForDotNet = () => {
const { small, token } = this.props;
const projectKey = this.getProjectKey();
const { component, languageConfig, small, token } = this.props;
const projectKey = getProjectKey(languageConfig, component);
if (!projectKey || !token) {
return null;
}
@@ -88,8 +85,8 @@ export default class AnalysisCommand extends React.PureComponent<Props> {
};

renderCommandForMSVC = () => {
const { small, token } = this.props;
const projectKey = this.getProjectKey();
const { component, languageConfig, small, token } = this.props;
const projectKey = getProjectKey(languageConfig, component);
if (!projectKey || !token) {
return null;
}
@@ -105,8 +102,8 @@ export default class AnalysisCommand extends React.PureComponent<Props> {
};

renderCommandForClangGCC = () => {
const { languageConfig, small, token } = this.props;
const projectKey = this.getProjectKey();
const { component, languageConfig, small, token } = this.props;
const projectKey = getProjectKey(languageConfig, component);
if (!languageConfig || !projectKey || !languageConfig.os || !token) {
return null;
}
@@ -123,8 +120,8 @@ export default class AnalysisCommand extends React.PureComponent<Props> {
};

renderCommandForOther = () => {
const { languageConfig, token } = this.props;
const projectKey = this.getProjectKey();
const { component, languageConfig, token } = this.props;
const projectKey = getProjectKey(languageConfig, component);
if (!languageConfig || !projectKey || !languageConfig.os || !token) {
return null;
}

+ 175
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/AnalysisCommandCustom.tsx View File

@@ -0,0 +1,175 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 JavaMavenCustom from './Custom/JavaMavenCustom';
import JavaGradleCustom from './Custom/JavaGradleCustom';
import ClangGCCCustom from './Custom/ClangGCCCustom';
import OtherCustom from './Custom/OtherCustom';
import { AnalysisCommandProps, AnalysisCommandRenderProps } from './utils';
import { AnalysisCommandCommon } from './AnalysisCommandOtherCI';
import { getHostUrl } from '../../../../helpers/urls';
import { getProjectKey } from '../ProjectAnalysisStep';
import { ProjectAnalysisModes } from '../ProjectAnalysisStepFromBuildTool';
import { RenderOS, RenderOSProps } from '../LanguageForm';

export function RenderCommandForMaven({
component,
mode,
onDone,
organization,
toggleModal,
token
}: AnalysisCommandRenderProps) {
if (!token) {
return null;
}

return (
<JavaMavenCustom
host={getHostUrl()}
mode={mode}
onDone={onDone}
organization={organization}
projectKey={component && component.key}
toggleModal={toggleModal}
token={token}
/>
);
}

export function RenderCommandForGradle({
component,
mode,
onDone,
organization,
toggleModal,
token
}: AnalysisCommandRenderProps) {
if (!token) {
return null;
}

return (
<JavaGradleCustom
host={getHostUrl()}
mode={mode}
onDone={onDone}
organization={organization}
projectKey={component && component.key}
toggleModal={toggleModal}
token={token}
/>
);
}

export function RenderCommandForClangOrGCC({
component,
mode,
onDone,
organization,
os,
small,
toggleModal,
token
}: AnalysisCommandRenderProps) {
const projectKey = getProjectKey(undefined, component);
if (!projectKey || !os || !token) {
return null;
}
return (
<ClangGCCCustom
host={getHostUrl()}
mode={mode}
onDone={onDone}
organization={organization}
os={os}
projectKey={projectKey}
small={small}
toggleModal={toggleModal}
token={token}
/>
);
}

export function RenderCommandForOther({
component,
currentUser,
mode,
onDone,
organization,
os,
toggleModal,
token
}: AnalysisCommandRenderProps) {
const projectKey = getProjectKey(undefined, component);
if (!component || !projectKey || !os || !token) {
return null;
}
return (
<OtherCustom
component={component}
currentUser={currentUser}
host={getHostUrl()}
mode={mode}
onDone={onDone}
organization={organization}
os={os}
projectKey={projectKey}
toggleModal={toggleModal}
token={token}
/>
);
}

function getBuildOptions({
os,
setOS
}: RenderOSProps): { [k: string]: (props: AnalysisCommandRenderProps) => JSX.Element | null } {
return {
gradle: RenderCommandForGradle,
make: function make(props: AnalysisCommandRenderProps) {
return (
<>
<RenderOS os={os} setOS={setOS} />
<RenderCommandForClangOrGCC {...props} />
</>
);
},
maven: RenderCommandForMaven,
other: function other(props: AnalysisCommandRenderProps) {
return (
<>
<RenderOS os={os} setOS={setOS} />
<RenderCommandForOther {...props} />
</>
);
}
};
}

export default function AnalysisCommandCustom(props: AnalysisCommandProps) {
return (
<AnalysisCommandCommon
{...props}
getBuildOptions={getBuildOptions}
mode={ProjectAnalysisModes.Custom}
/>
);
}

+ 174
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/AnalysisCommandOtherCI.tsx View File

@@ -0,0 +1,174 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 OtherOtherCI from './OtherCI/OtherOtherCI';
import ClangGCCOtherCI from './OtherCI/ClangGCCOtherCI';
import { AnalysisCommandProps, AnalysisCommandRenderProps } from './utils';
import { RenderCommandForGradle, RenderCommandForMaven } from './AnalysisCommandCustom';
import { getHostUrl } from '../../../../helpers/urls';
import { getProjectKey } from '../ProjectAnalysisStep';
import { RenderOS, RenderOSProps } from '../LanguageForm';
import { ProjectAnalysisModes } from '../ProjectAnalysisStepFromBuildTool';
import EditTokenModal from '../../analyzeProject/steps/EditTokenModal';

export function RenderCommandForClangOrGCC({
component,
onDone,
organization,
os,
small,
toggleModal,
token
}: AnalysisCommandRenderProps) {
const projectKey = getProjectKey(undefined, component);
if (!projectKey || !os || !token) {
return null;
}
return (
<ClangGCCOtherCI
host={getHostUrl()}
onDone={onDone}
organization={organization}
os={os}
projectKey={projectKey}
small={small}
toggleModal={toggleModal}
token={token}
/>
);
}

export function RenderCommandForOther({
component,
currentUser,
onDone,
organization,
os,
toggleModal,
token
}: AnalysisCommandRenderProps) {
const projectKey = getProjectKey(undefined, component);
if (!component || !projectKey || !os || !token) {
return null;
}
return (
<OtherOtherCI
component={component}
currentUser={currentUser}
host={getHostUrl()}
onDone={onDone}
organization={organization}
os={os}
projectKey={projectKey}
toggleModal={toggleModal}
token={token}
/>
);
}

function getBuildOptions({
os,
setOS
}: RenderOSProps): { [k: string]: (props: AnalysisCommandRenderProps) => JSX.Element | null } {
return {
gradle: RenderCommandForGradle,
make: function make(props) {
return (
<>
<RenderOS os={os} setOS={setOS} />
<RenderCommandForClangOrGCC {...props} />
</>
);
},
maven: RenderCommandForMaven,
other: function other(props) {
return (
<>
<RenderOS os={os} setOS={setOS} />
<RenderCommandForOther {...props} />
</>
);
}
};
}

interface AnalysisCommandExtraProps {
mode: ProjectAnalysisModes;
getBuildOptions: (
props: RenderOSProps
) => { [k: string]: (props: AnalysisCommandRenderProps) => JSX.Element | null };
}

export function AnalysisCommandCommon(props: AnalysisCommandProps & AnalysisCommandExtraProps) {
const [os, setOS] = React.useState<string | undefined>(undefined);
const [isModalVisible, toggleModal] = React.useState<boolean>(false);
const { buildType } = props;

if (!os && props.os) {
setOS(props.os);
}

const toggleTokenModal = () => toggleModal(!isModalVisible);

const close = () => toggleModal(false);

const save = (t: string) => {
props.setToken(t);
close();
};

const callOnDone = () => {
props.onDone({ os });
};

const Build = (buildType && props.getBuildOptions({ os, setOS })[buildType]) || undefined;

return Build ? (
<>
{isModalVisible && (
<EditTokenModal
component={props.component}
currentUser={props.currentUser}
onClose={close}
onSave={save}
/>
)}

<Build
{...props}
mode={props.mode}
onDone={callOnDone}
os={os}
toggleModal={toggleTokenModal}
token={props.token}
/>
</>
) : null;
}

export default function AnalysisCommandOtherCI(props: AnalysisCommandProps) {
return (
<AnalysisCommandCommon
{...props}
getBuildOptions={getBuildOptions}
mode={ProjectAnalysisModes.CI}
/>
);
}

+ 207
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/AnalysisCommandTravis.tsx View File

@@ -0,0 +1,207 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { JavaMavenTravisSonarCloud } from './TravisSonarCloud/JavaMavenTravisSonarCloud';
import { JavaGradleTravisSonarCloud } from './TravisSonarCloud/JavaGradleTravisSonarCloud';
import { OtherTravisSonarCloud } from './TravisSonarCloud/OtherTravisSonarCloud';
import { ClangGCCTravisSonarCloud } from './TravisSonarCloud/ClangGCCTravisSonarCloud';
import { getHostUrl } from '../../../../helpers/urls';
import CodeSnippet from '../../../../components/common/CodeSnippet';
import { translate } from '../../../../helpers/l10n';
import { getProjectKey } from '../ProjectAnalysisStep';

interface Props {
buildType: string | undefined;
component?: T.Component;
organization?: string;
small?: boolean;
token?: string;
}

interface RenderProps {
component?: T.Component;
organization?: string;
small?: boolean;
token?: string;
}

export function getSonarcloudAddonYml(organization: string = '') {
return `addons:
sonarcloud:
organization: ${organization ? `"${organization}"` : `"Add your organization key"`}
token:
secure: "**************************" # encrypted value of your token`;
}

export function getSonarcloudAddonYmlRender(organization: string = '') {
return (
<>
{`addons:
sonarcloud:
organization: ${organization ? `"${organization}"` : `"Add your organization key"`}
token:
secure: `}
{
<span className="highlight">
{'"**************************"'} # encrypted value of your token
</span>
}
<br />
</>
);
}

export function RequirementJavaBuild() {
return (
<>
<p className="spacer-bottom">{translate('onboarding.analysis.with.travis.environments')}</p>

<div className="flex-columns">
<div className="flex-column flex-column-half">
<a
href="https://docs.travis-ci.com/user/reference/precise/"
rel="noopener noreferrer"
target="_blank">
{translate('onboarding.analysis.with.travis.environment.image.java')}
</a>
<CodeSnippet isOneLine={true} noCopy={true} snippet={'language: java'} />
</div>

<div className="display-flex-stretch">
<div className="vertical-pipe-separator">
<div className="vertical-separator " />
<span className="note">{translate('or')}</span>
<div className="vertical-separator" />
</div>
</div>

<div className="flex-column flex-column-half">
<a
href="https://docs.travis-ci.com/user/reference/trusty/"
rel="noopener noreferrer"
target="_blank">
{translate('onboarding.analysis.with.travis.environment.image.ci')}
</a>
<CodeSnippet isOneLine={true} noCopy={true} snippet={'dist: trusty'} />
</div>
</div>
</>
);
}

export function RequirementOtherBuild() {
return (
<>
<p>
{translate('onboarding.analysis.with.travis.environment')}{' '}
<a
href="https://docs.travis-ci.com/user/reference/trusty/"
rel="noopener noreferrer"
target="_blank">
{translate('onboarding.analysis.with.travis.environment.image.ci')}
</a>
</p>

<CodeSnippet isOneLine={true} noCopy={true} snippet={'dist: trusty'} />
</>
);
}

export function RenderCommandForClangOrGCC({ component, organization, small, token }: RenderProps) {
const projectKey = getProjectKey(undefined, component);
if (!projectKey || !token) {
return null;
}
return (
<ClangGCCTravisSonarCloud
host={getHostUrl()}
organization={organization}
os="linux"
projectKey={projectKey}
small={small}
token={token}
/>
);
}

export function RenderCommandForGradle({ component, organization, token }: RenderProps) {
if (!token) {
return null;
}

return (
<JavaGradleTravisSonarCloud
host={getHostUrl()}
organization={organization}
projectKey={component && component.key}
token={token}
/>
);
}

export function RenderCommandForMaven({ component, organization, token }: RenderProps) {
if (!token) {
return null;
}

return (
<JavaMavenTravisSonarCloud
host={getHostUrl()}
organization={organization}
projectKey={component && component.key}
token={token}
/>
);
}

export function RenderCommandForOther({ component, organization, token }: RenderProps) {
const projectKey = getProjectKey(undefined, component);
if (!projectKey || !token) {
return null;
}
return (
<OtherTravisSonarCloud
host={getHostUrl()}
organization={organization}
os={'linux'}
projectKey={projectKey}
token={token}
/>
);
}

function getBuildOptions(): {
[k: string]: (props: Props) => JSX.Element | null;
} {
return {
gradle: RenderCommandForGradle,
make: RenderCommandForClangOrGCC,
maven: RenderCommandForMaven,
other: RenderCommandForOther
};
}

export default function AnalysisCommandTravis(props: Props) {
const { buildType } = props;

const Build = (buildType && getBuildOptions()[buildType]) || undefined;

return Build ? <Build {...props} /> : null;
}

+ 1
- 1
server/sonar-web/src/main/js/apps/tutorials/components/commands/ClangGCC.tsx View File

@@ -26,7 +26,7 @@ import InstanceMessage from '../../../../components/common/InstanceMessage';
import { translate } from '../../../../helpers/l10n';
import { quote } from '../../utils';

interface Props {
export interface Props {
host: string;
os: string;
organization?: string;

+ 132
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/ClangGCCCustom.tsx View File

@@ -0,0 +1,132 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { quote } from '../../../utils';
import SQScanner from '../SQScanner';
import BuildWrapper from '../BuildWrapper';
import { translate } from '../../../../../helpers/l10n';
import CodeSnippet from '../../../../../components/common/CodeSnippet';
import { Button, EditButton } from '../../../../../components/ui/buttons';
import { ProjectAnalysisModes } from '../../ProjectAnalysisStepFromBuildTool';

export interface Props {
host: string;
mode: ProjectAnalysisModes;
onDone: VoidFunction;
os: string;
organization?: string;
projectKey: string;
small?: boolean;
toggleModal: VoidFunction;
token: string;
}

const executables: T.Dict<string> = {
linux: 'build-wrapper-linux-x86-64',
win: 'build-wrapper-win-x86-64.exe',
mac: 'build-wrapper-macosx-x86'
};

interface ClangGCCProps extends Pick<Props, 'small' | 'onDone' | 'os'> {
command1: string;
command2: (string | undefined)[];
renderCommand2: () => JSX.Element;
}

export function ClangGCCCommon(props: ClangGCCProps) {
return (
<>
<h4 className="huge-spacer-top spacer-bottom">
{translate('onboarding.analysis.sq_scanner.execute')}
</h4>

<p className="spacer-bottom markdown">
{translate('onboarding.analysis.sq_scanner.execute.text.custom')}
</p>

<CodeSnippet isOneLine={props.small} snippet={props.command1} />

<CodeSnippet
isOneLine={props.os === 'win'}
render={props.renderCommand2}
snippet={props.command2}
wrap={true}
/>

<FormattedMessage
defaultMessage={translate('onboarding.analysis.sq_scanner.docs')}
id="onboarding.analysis.sq_scanner.docs"
values={{
link: (
<a
href="http://redirect.sonarsource.com/doc/install-configure-scanner.html"
rel="noopener noreferrer"
target="_blank">
{translate('onboarding.analysis.sq_scanner.docs_link')}
</a>
)
}}
/>

<div className="big-spacer-top">
<Button className="js-continue" onClick={props.onDone}>
{translate('onboarding.finish')}
</Button>
</div>
</>
);
}

export default function ClangGCCCustom(props: Props) {
const command1 = `${executables[props.os]} --out-dir bw-output make clean all`;

const q = quote(props.os);
const command2 = [
props.os === 'win' ? 'sonar-scanner.bat' : 'sonar-scanner',
'-D' + q(`sonar.projectKey=${props.projectKey}`),
props.organization && '-D' + q(`sonar.organization=${props.organization}`),
'-D' + q('sonar.sources=.'),
'-D' + q('sonar.cfamily.build-wrapper-output=bw-output'),
'-D' + q(`sonar.host.url=${props.host}`),
'-D' + q(`sonar.login=${props.token}`)
];

const renderCommand2 = () => (
<>
{command2.join(' \\\n ')}{' '}
<EditButton className="edit-token spacer-left" onClick={props.toggleModal} />
</>
);

return (
<div className="huge-spacer-top">
<SQScanner os={props.os} />
<BuildWrapper className="huge-spacer-top" os={props.os} />
<ClangGCCCommon
command1={command1}
command2={command2}
onDone={props.onDone}
os={props.os}
renderCommand2={renderCommand2}
/>
</div>
);
}

+ 69
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/JavaGradleCustom.tsx View File

@@ -0,0 +1,69 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { JavaCustomProps, RenderCustomContent } from './JavaMavenCustom';
import { translate } from '../../../../../helpers/l10n';
import CodeSnippet from '../../../../../components/common/CodeSnippet';
import { ProjectAnalysisModes } from '../../ProjectAnalysisStepFromBuildTool';

export default function JavaGradleCustom(props: JavaCustomProps) {
const suffix = props.mode === ProjectAnalysisModes.CI ? '.ci' : '';
const config = 'plugins {\n id "org.sonarqube" version "2.7"\n}';

const command = [
'./gradlew sonarqube',
props.projectKey && `-Dsonar.projectKey=${props.projectKey}`,
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${suffix}`)}
</h4>

<FormattedMessage
defaultMessage={translate('onboarding.analysis.java.gradle.text.1.sonarcloud')}
id="onboarding.analysis.java.gradle.text.1.sonarcloud"
values={{
file: <code>build.gradle</code>,
plugin: <code>org.sonarqube</code>
}}
/>

<CodeSnippet snippet={config} />

<p className="spacer-top spacer-bottom markdown">
{translate('onboarding.analysis.java.gradle.text.2')}
</p>

<RenderCustomContent
command={command}
linkText="onboarding.analysis.java.gradle.docs_link"
linkUrl="http://redirect.sonarsource.com/doc/gradle.html"
onDone={props.onDone}
toggleModal={props.toggleModal}
/>
</div>
);
}

+ 130
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/JavaMavenCustom.tsx View File

@@ -0,0 +1,130 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 InstanceMessage from '../../../../../components/common/InstanceMessage';
import CodeSnippet from '../../../../../components/common/CodeSnippet';
import { Button, EditButton } from '../../../../../components/ui/buttons';
import { ProjectAnalysisModes } from '../../ProjectAnalysisStepFromBuildTool';

export interface JavaCustomProps {
host: string;
mode: ProjectAnalysisModes;
onDone: VoidFunction;
organization?: string;
projectKey?: string;
toggleModal: VoidFunction;
token: string;
}

interface RenderCustomCommandProps {
command: (string | undefined)[];
toggleModal: VoidFunction;
}

export function RenderCustomCommand({
command,
toggleModal
}: RenderCustomCommandProps): JSX.Element {
return (
<>
{command.join(' \\\n ')}{' '}
<EditButton className="edit-token spacer-left" onClick={toggleModal} />
</>
);
}

interface RenderCustomContent {
linkText: string;
linkUrl: string;
onDone: VoidFunction;
}

export function RenderCustomContent({
command,
linkText,
linkUrl,
onDone,
toggleModal
}: RenderCustomCommandProps & RenderCustomContent) {
return (
<>
<CodeSnippet
render={() => <RenderCustomCommand command={command} toggleModal={toggleModal} />}
snippet={command}
wrap={true}
/>

<p className="big-spacer-top markdown">
<FormattedMessage
defaultMessage={translate('onboarding.analysis.docs')}
id="onboarding.analysis.docs"
values={{
link: (
<a href={linkUrl} rel="noopener noreferrer" target="_blank">
{translate(linkText)}
</a>
)
}}
/>
</p>

<div className="big-spacer-top">
<Button className="js-continue" onClick={onDone}>
{translate('onboarding.finish')}
</Button>
</div>
</>
);
}

export default function JavaMavenCustom(props: JavaCustomProps) {
const suffix = props.mode === ProjectAnalysisModes.CI ? '.ci' : '';
const command = [
'mvn sonar:sonar',
props.projectKey && `-Dsonar.projectKey=${props.projectKey}`,
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${suffix}`)}
</h4>

<p className="spacer-bottom markdown">
<InstanceMessage
message={translate(`onboarding.analysis.java.maven.text.custom${suffix}`)}
/>
</p>

<RenderCustomContent
command={command}
linkText="onboarding.analysis.java.maven.docs_link"
linkUrl="http://redirect.sonarsource.com/doc/install-configure-scanner-maven.html"
onDone={props.onDone}
toggleModal={props.toggleModal}
/>
</div>
);
}

+ 96
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/OtherCustom.tsx View File

@@ -0,0 +1,96 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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';
import { quote } from '../../../utils';
import { Button, EditButton } from '../../../../../components/ui/buttons';
import { ProjectAnalysisModes } from '../../ProjectAnalysisStepFromBuildTool';

export interface Props {
component: T.Component;
currentUser: T.LoggedInUser;
host: string;
mode: ProjectAnalysisModes;
onDone: VoidFunction;
organization?: string;
os: string;
projectKey: string;
toggleModal: VoidFunction;
token: string;
}

export default function OtherCustom(props: Props) {
const q = quote(props.os);
const command = [
props.os === 'win' ? 'sonar-scanner.bat' : 'sonar-scanner',
'-D' + q(`sonar.projectKey=${props.projectKey}`),
props.organization && '-D' + q(`sonar.organization=${props.organization}`),
'-D' + q('sonar.sources=.'),
'-D' + q(`sonar.host.url=${props.host}`),
'-D' + q(`sonar.login=${props.token}`)
];

const renderCommand = () => (
<>
{command.join(' \\\n ')}{' '}
<EditButton className="edit-token spacer-left" onClick={props.toggleModal} />
</>
);

return (
<div className="huge-spacer-top">
<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.custom')}>
{transformedMessage => (
<p
className="spacer-bottom markdown"
dangerouslySetInnerHTML={{ __html: transformedMessage }}
/>
)}
</InstanceMessage>

<CodeSnippet
isOneLine={props.os === 'win'}
render={renderCommand}
snippet={command}
wrap={true}
/>

<p
className="big-spacer-top markdown"
dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.standard.docs') }}
/>

<div className="big-spacer-top">
<Button className="js-continue" onClick={props.onDone}>
{translate('onboarding.finish')}
</Button>
</div>
</div>
);
}

+ 58
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/ClangGCCCustom-test.tsx View File

@@ -0,0 +1,58 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 ClangGCCCustom, { ClangGCCCommon } from '../ClangGCCCustom';
import { ProjectAnalysisModes } from '../../../ProjectAnalysisStepFromBuildTool';

it('should render correctly', () => {
expect(
shallow(
<ClangGCCCustom
host="https://sonarcloud.io"
mode={ProjectAnalysisModes.Custom}
onDone={jest.fn()}
organization="use-the-force"
os="linux"
projectKey="luke-lightsaber"
small={true}
toggleModal={jest.fn()}
token="sonarsource123"
/>
)
).toMatchSnapshot();
});

it('should render common elements correctly', () => {
const command1 = `command1`;
const command2 = [`command2`];
const renderCommand2 = () => <>render command 2</>;
expect(
shallow(
<ClangGCCCommon
command1={command1}
command2={command2}
renderCommand2={renderCommand2}
onDone={jest.fn()}
os="linux"
/>
)
).toMatchSnapshot();
});

+ 67
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/JavaGradleCustom-test.tsx View File

@@ -0,0 +1,67 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { ProjectAnalysisModes } from '../../../ProjectAnalysisStepFromBuildTool';
import JavaGradleCustom from '../JavaGradleCustom';
import { RenderCustomContent } from '../JavaMavenCustom';

const host = 'https://sonarcloud.io';
const organization = 'use-the-force';
const projectKey = 'luke-lightsaber';
const token = 'sonarsource123';

it('should render correctly', () => {
expect(
shallow(
<JavaGradleCustom
host={host}
mode={ProjectAnalysisModes.Custom}
onDone={jest.fn()}
organization={organization}
projectKey={projectKey}
toggleModal={jest.fn()}
token={token}
/>
)
).toMatchSnapshot();
});

it('should render gradle custom content', () => {
const command = [
'./gradlew sonarqube',
projectKey && `-Dsonar.projectKey=${projectKey}`,
organization && `-Dsonar.organization=${organization}`,
`-Dsonar.host.url=${host}`,
`-Dsonar.login=${token}`
];
const onDone = jest.fn();
const toggleModal = jest.fn();

expect(
<RenderCustomContent
command={command}
linkText="onboarding.analysis.java.gradle.docs_link"
linkUrl="http://redirect.sonarsource.com/doc/gradle.html"
onDone={onDone}
toggleModal={toggleModal}
/>
).toMatchSnapshot();
});

+ 80
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/JavaMavenCustom-test.tsx View File

@@ -0,0 +1,80 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { ProjectAnalysisModes } from '../../../ProjectAnalysisStepFromBuildTool';
import JavaMavenCustom, { RenderCustomContent } from '../JavaMavenCustom';

const host = 'https://sonarcloud.io';
const organization = 'use-the-force';
const projectKey = 'luke-lightsaber';
const token = 'sonarsource123';

it('should render correctly', () => {
expect(
shallow(
<JavaMavenCustom
host={host}
mode={ProjectAnalysisModes.Custom}
onDone={jest.fn()}
organization={organization}
projectKey={projectKey}
toggleModal={jest.fn()}
token={token}
/>
)
).toMatchSnapshot();
});

it('should render common elements correctly', () => {
expect(
shallow(
<JavaMavenCustom
host="sonarcloud"
mode={ProjectAnalysisModes.Custom}
onDone={jest.fn()}
toggleModal={jest.fn()}
token="123"
/>
)
).toMatchSnapshot();
});

it('should render maven custom content', () => {
const command = [
'mvn sonar:sonar',
projectKey && `-Dsonar.projectKey=${projectKey}`,
organization && `-Dsonar.organization=${organization}`,
`-Dsonar.host.url=${host}`,
`-Dsonar.login=${token}`
];
const onDone = jest.fn();
const toggleModal = jest.fn();

expect(
<RenderCustomContent
command={command}
linkText="onboarding.analysis.java.maven.docs_link"
linkUrl="http://redirect.sonarsource.com/doc/install-configure-scanner-maven.html"
onDone={onDone}
toggleModal={toggleModal}
/>
).toMatchSnapshot();
});

+ 43
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/OtherCustom-test.tsx View File

@@ -0,0 +1,43 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { ProjectAnalysisModes } from '../../../ProjectAnalysisStepFromBuildTool';
import OtherCustom from '../OtherCustom';
import { mockComponent, mockLoggedInUser } from '../../../../../../helpers/testMocks';

it('should render correctly', () => {
expect(
shallow(
<OtherCustom
component={mockComponent()}
currentUser={mockLoggedInUser()}
host="https://sonarcloud.io"
mode={ProjectAnalysisModes.Custom}
onDone={jest.fn()}
organization="use-the-force"
os="linux"
projectKey="luke-lightsaber"
toggleModal={jest.fn()}
token="sonarsource123"
/>
)
).toMatchSnapshot();
});

+ 85
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/__snapshots__/ClangGCCCustom-test.tsx.snap View File

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

exports[`should render common elements correctly 1`] = `
<Fragment>
<h4
className="huge-spacer-top spacer-bottom"
>
onboarding.analysis.sq_scanner.execute
</h4>
<p
className="spacer-bottom markdown"
>
onboarding.analysis.sq_scanner.execute.text.custom
</p>
<CodeSnippet
snippet="command1"
/>
<CodeSnippet
isOneLine={false}
render={[Function]}
snippet={
Array [
"command2",
]
}
wrap={true}
/>
<FormattedMessage
defaultMessage="onboarding.analysis.sq_scanner.docs"
id="onboarding.analysis.sq_scanner.docs"
values={
Object {
"link": <a
href="http://redirect.sonarsource.com/doc/install-configure-scanner.html"
rel="noopener noreferrer"
target="_blank"
>
onboarding.analysis.sq_scanner.docs_link
</a>,
}
}
/>
<div
className="big-spacer-top"
>
<Button
className="js-continue"
onClick={[MockFunction]}
>
onboarding.finish
</Button>
</div>
</Fragment>
`;

exports[`should render correctly 1`] = `
<div
className="huge-spacer-top"
>
<SQScanner
os="linux"
/>
<BuildWrapper
className="huge-spacer-top"
os="linux"
/>
<ClangGCCCommon
command1="build-wrapper-linux-x86-64 --out-dir bw-output make clean all"
command2={
Array [
"sonar-scanner",
"-Dsonar.projectKey=luke-lightsaber",
"-Dsonar.organization=use-the-force",
"-Dsonar.sources=.",
"-Dsonar.cfamily.build-wrapper-output=bw-output",
"-Dsonar.host.url=https://sonarcloud.io",
"-Dsonar.login=sonarsource123",
]
}
onDone={[MockFunction]}
os="linux"
renderCommand2={[Function]}
/>
</div>
`;

+ 68
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/__snapshots__/JavaGradleCustom-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<div>
<h4
className="spacer-bottom"
>
onboarding.analysis.java.gradle.header
</h4>
<FormattedMessage
defaultMessage="onboarding.analysis.java.gradle.text.1.sonarcloud"
id="onboarding.analysis.java.gradle.text.1.sonarcloud"
values={
Object {
"file": <code>
build.gradle
</code>,
"plugin": <code>
org.sonarqube
</code>,
}
}
/>
<CodeSnippet
snippet="plugins {
id \\"org.sonarqube\\" version \\"2.7\\"
}"
/>
<p
className="spacer-top spacer-bottom markdown"
>
onboarding.analysis.java.gradle.text.2
</p>
<RenderCustomContent
command={
Array [
"./gradlew sonarqube",
"-Dsonar.projectKey=luke-lightsaber",
"-Dsonar.organization=use-the-force",
"-Dsonar.host.url=https://sonarcloud.io",
"-Dsonar.login=sonarsource123",
]
}
linkText="onboarding.analysis.java.gradle.docs_link"
linkUrl="http://redirect.sonarsource.com/doc/gradle.html"
onDone={[MockFunction]}
toggleModal={[MockFunction]}
/>
</div>
`;

exports[`should render gradle custom content 1`] = `
<RenderCustomContent
command={
Array [
"./gradlew sonarqube",
"-Dsonar.projectKey=luke-lightsaber",
"-Dsonar.organization=use-the-force",
"-Dsonar.host.url=https://sonarcloud.io",
"-Dsonar.login=sonarsource123",
]
}
linkText="onboarding.analysis.java.gradle.docs_link"
linkUrl="http://redirect.sonarsource.com/doc/gradle.html"
onDone={[MockFunction]}
toggleModal={[MockFunction]}
/>
`;

+ 83
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/__snapshots__/JavaMavenCustom-test.tsx.snap View File

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

exports[`should render common elements 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.custom"
/>
</p>
<RenderCustomContent
command={
Array [
"mvn sonar:sonar",
undefined,
undefined,
"-Dsonar.host.url=sonarcloud",
"-Dsonar.login=123",
]
}
linkText="onboarding.analysis.java.maven.docs_link"
linkUrl="http://redirect.sonarsource.com/doc/install-configure-scanner-maven.html"
onDone={[MockFunction]}
toggleModal={[MockFunction]}
/>
</div>
`;

exports[`should render 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.custom"
/>
</p>
<RenderCustomContent
command={
Array [
"mvn sonar:sonar",
"-Dsonar.projectKey=luke-lightsaber",
"-Dsonar.organization=use-the-force",
"-Dsonar.host.url=https://sonarcloud.io",
"-Dsonar.login=sonarsource123",
]
}
linkText="onboarding.analysis.java.maven.docs_link"
linkUrl="http://redirect.sonarsource.com/doc/install-configure-scanner-maven.html"
onDone={[MockFunction]}
toggleModal={[MockFunction]}
/>
</div>
`;

exports[`should render maven custom content 1`] = `
<RenderCustomContent
command={
Array [
"mvn sonar:sonar",
"-Dsonar.projectKey=luke-lightsaber",
"-Dsonar.organization=use-the-force",
"-Dsonar.host.url=https://sonarcloud.io",
"-Dsonar.login=sonarsource123",
]
}
linkText="onboarding.analysis.java.maven.docs_link"
linkUrl="http://redirect.sonarsource.com/doc/install-configure-scanner-maven.html"
onDone={[MockFunction]}
toggleModal={[MockFunction]}
/>
`;

+ 54
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/Custom/__tests__/__snapshots__/OtherCustom-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<div
className="huge-spacer-top"
>
<SQScanner
os="linux"
/>
<h4
className="huge-spacer-top spacer-bottom"
>
onboarding.analysis.sq_scanner.execute
</h4>
<InstanceMessage
message="onboarding.analysis.sq_scanner.execute.text.custom"
>
<Component />
</InstanceMessage>
<CodeSnippet
isOneLine={false}
render={[Function]}
snippet={
Array [
"sonar-scanner",
"-Dsonar.projectKey=luke-lightsaber",
"-Dsonar.organization=use-the-force",
"-Dsonar.sources=.",
"-Dsonar.host.url=https://sonarcloud.io",
"-Dsonar.login=sonarsource123",
]
}
wrap={true}
/>
<p
className="big-spacer-top markdown"
dangerouslySetInnerHTML={
Object {
"__html": "onboarding.analysis.standard.docs",
}
}
/>
<div
className="big-spacer-top"
>
<Button
className="js-continue"
onClick={[MockFunction]}
>
onboarding.finish
</Button>
</div>
</div>
`;

+ 1
- 1
server/sonar-web/src/main/js/apps/tutorials/components/commands/DotNet.tsx View File

@@ -24,7 +24,7 @@ import CodeSnippet from '../../../../components/common/CodeSnippet';
import InstanceMessage from '../../../../components/common/InstanceMessage';
import { translate } from '../../../../helpers/l10n';

interface Props {
export interface Props {
host: string;
organization?: string;
projectKey: string;

+ 1
- 1
server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaGradle.tsx View File

@@ -23,7 +23,7 @@ import CodeSnippet from '../../../../components/common/CodeSnippet';
import InstanceMessage from '../../../../components/common/InstanceMessage';
import { translate } from '../../../../helpers/l10n';

interface Props {
export interface Props {
host: string;
organization?: string;
projectKey?: string;

+ 1
- 1
server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaMaven.tsx View File

@@ -23,7 +23,7 @@ import CodeSnippet from '../../../../components/common/CodeSnippet';
import InstanceMessage from '../../../../components/common/InstanceMessage';
import { translate } from '../../../../helpers/l10n';

interface Props {
export interface Props {
host: string;
organization?: string;
projectKey?: string;

+ 1
- 1
server/sonar-web/src/main/js/apps/tutorials/components/commands/Other.tsx View File

@@ -25,7 +25,7 @@ import InstanceMessage from '../../../../components/common/InstanceMessage';
import { translate } from '../../../../helpers/l10n';
import { quote } from '../../utils';

interface Props {
export interface Props {
host: string;
organization?: string;
os: string;

+ 108
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/OtherCI/ClangGCCOtherCI.tsx View File

@@ -0,0 +1,108 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { quote } from '../../../utils';
import SQScanner from '../SQScanner';
import BuildWrapper from '../BuildWrapper';
import { translate } from '../../../../../helpers/l10n';
import CodeSnippet from '../../../../../components/common/CodeSnippet';
import { EditButton } from '../../../../../components/ui/buttons';
import { ClangGCCCommon } from '../Custom/ClangGCCCustom';

export interface Props {
host: string;
onDone: VoidFunction;
os: string;
organization?: string;
projectKey: string;
small?: boolean;
toggleModal: VoidFunction;
token: string;
}

const executables: T.Dict<string> = {
linux: 'build-wrapper-linux-x86-64',
win: 'build-wrapper-win-x86-64.exe',
mac: 'build-wrapper-macosx-x86'
};

export default function ClangGCCOtherCI(props: Props) {
const command1 = `${executables[props.os]} --out-dir bw-output make clean all`;

const q = quote(props.os);
const command2 = [
props.os === 'win' ? 'sonar-scanner.bat' : 'sonar-scanner',
'-D' + q(`sonar.projectKey=${props.projectKey}`),
props.organization && '-D' + q(`sonar.organization=${props.organization}`),
'-D' + q('sonar.sources=.'),
'-D' + q('sonar.cfamily.build-wrapper-output=bw-output'),
'-D' + q(`sonar.host.url=${props.host}`),
'-D' + q(`sonar.login=${props.token}`)
];

const renderCommand2 = () => (
<>
{command2.join(' \\\n ')}{' '}
<EditButton className="edit-token spacer-left" onClick={props.toggleModal} />
</>
);

const commandLinuxMac = `
local SONAR_SCANNER_VERSION=${translate('onboarding.analysis.sonar_scanner_version')}
export SONAR_SCANNER_HOME=$HOME/.sonar/sonar-scanner-$SONAR_SCANNER_VERSION
rm -rf $SONAR_SCANNER_HOME
mkdir -p $SONAR_SCANNER_HOME
curl -sSLo $HOME/.sonar/sonar-scanner.zip http://repo1.maven.org/maven2/org/sonarsource/scanner/cli/sonar-scanner-cli/$SONAR_SCANNER_VERSION/sonar-scanner-cli-$SONAR_SCANNER_VERSION.zip
unzip $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/
rm $HOME/.sonar/sonar-scanner.zip
export PATH=$SONAR_SCANNER_HOME/bin:$PATH
export SONAR_SCANNER_OPTS="-server"

curl -LsS https://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip > build-wrapper-linux-x86.zip
unzip build-wrapper-linux-x86.zip`;

return (
<div className="huge-spacer-top">
{props.os === 'win' ? (
<>
<SQScanner os={'ci'} />

<BuildWrapper className="huge-spacer-top" os={'ci'} />
</>
) : (
<>
<h4 className="huge-spacer-top spacer-bottom">
{translate('onboarding.analysis.sq_scanner.header.ci')}
</h4>

<CodeSnippet snippet={commandLinuxMac} />
</>
)}

<ClangGCCCommon
command1={command1}
command2={command2}
renderCommand2={renderCommand2}
onDone={props.onDone}
os={props.os}
/>
</div>
);
}

+ 0
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/OtherCI/OtherOtherCI.tsx View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save