]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-13296 Add new Jenkins tutorial for Bitbucket Server
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Tue, 21 Apr 2020 15:42:30 +0000 (17:42 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 4 May 2020 20:03:53 +0000 (20:03 +0000)
69 files changed:
server/sonar-web/public/images/tutorials/commit.svg [new file with mode: 0644]
server/sonar-web/public/images/tutorials/jenkins.svg [new file with mode: 0644]
server/sonar-web/public/images/tutorials/refresh.svg [new file with mode: 0644]
server/sonar-web/src/main/js/api/alm-settings.ts
server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/EmptyOverview-test.tsx.snap
server/sonar-web/src/main/js/components/common/CodeSnippet.css
server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelectionRenderer-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/__tests__/__snapshots__/TutorialSelection-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/__tests__/__snapshots__/TutorialSelectionRenderer-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/components/LabelActionPair.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/components/LabelValuePair.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/components/SentenceWithFilename.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/components/SentenceWithHighlights.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/components/Step.css
server/sonar-web/src/main/js/components/tutorials/components/Step.tsx
server/sonar-web/src/main/js/components/tutorials/components/__tests__/LabelActionPair-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/components/__tests__/LabelValuePair-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/components/__tests__/SentenceWithFilename-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/components/__tests__/SentenceWithHighlights-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/LabelActionPair-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/LabelValuePair-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/SentenceWithFilename-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/SentenceWithHighlights-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/BitbucketWebhookStep.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/JenkinsTutorial.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/JenkinsfileStep.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/MultiBranchPipelineStep.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/PreRequisitesStep.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/BitbucketWebhookStep-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsfileStep-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/MultiBranchPipelineStep-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/PreRequisitesStep-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/BitbucketWebhookStep-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/JenkinsTutorial-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/JenkinsfileStep-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/MultiBranchPipelineStep-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/PreRequisitesStep-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/CreateJenkinsfileBulletPoint.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/Gradle.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/MSBuild.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/Maven.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/Other.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/CreateJenkinsfileBulletPoint-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/Gradle-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/MSBuild-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/Maven-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/Other-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/CreateJenkinsfileBulletPoint-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/Gradle-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/MSBuild-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/Maven-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/Other-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/jenkins/test-utils.ts [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/manual/ManualTutorial.tsx
server/sonar-web/src/main/js/components/tutorials/manual/__tests__/ManualTutorial-test.tsx
server/sonar-web/src/main/js/components/tutorials/manual/__tests__/__snapshots__/ManualTutorial-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/styles.css [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/types.ts [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/utils.ts
server/sonar-web/src/main/js/helpers/__tests__/alm-settings.ts [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/alm-settings.ts [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts
server/sonar-web/src/main/js/types/alm-settings.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

diff --git a/server/sonar-web/public/images/tutorials/commit.svg b/server/sonar-web/public/images/tutorials/commit.svg
new file mode 100644 (file)
index 0000000..f60f7e5
--- /dev/null
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="27" height="27"><g fill="none" fill-rule="evenodd" transform="rotate(-90 13.5 18.5)"><path fill="#236A97" fill-rule="nonzero" d="M23.755102 18.5408163L19.7526885 14.5v3.0306122H12v2.0204082h7.7526885v3.0306123z"/><path d="M0 0h36v36H0z"/><circle cx="18.5" cy="18.5" r="12" stroke="#236A97" stroke-width="3"/></g></svg>
\ No newline at end of file
diff --git a/server/sonar-web/public/images/tutorials/jenkins.svg b/server/sonar-web/public/images/tutorials/jenkins.svg
new file mode 100644 (file)
index 0000000..3ffa20b
--- /dev/null
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="56" height="76"><path fill="#236A97" fill-rule="nonzero" d="M17.0487743 26.4874701c-.2633309-.4728576-1.091713.3613828-1.79162.7607377-2.0687996.6851018-3.9790932-1.532657-4.4992041-4.0925528-.3969505-1.9537243.6064908-3.6941751 1.7771685-4.1474922 2.1227488-.8219843 3.5884107-.2877 4.1680091 2.8376465 1.1349544.0049922 1.9983633-.1127172 2.3897069-.6850429-.0684018-1.529288-.7478783-2.8505691-.7226231-4.0184879.0659164-3.0482709 1.46717-5.1785694.4644218-8.35489055.0664461-.87419458 2.0685712-3.12052482 4.7722246-4.86588792 1.9747258-1.27479955 3.7726433-2.2680363 6.5660309-2.29279903 2.423728-.02148573 4.703752.44235801 8.5545414 2.25344223.9477408.4457359 2.8432176 2.82299404 4.1298563 4.4905583.0345611.08007787.014399.0857984-.192986.05719856-1.8308574-.32752321-2.8607584.55403556-2.7853569 1.76171361.9885263-.77163072 2.8770115-.60270742 4.1852372.7550201 3.0931417 2.8159886 1.0480218 16.8814333-1.4373276 20.0309168-2.0538241 3.9080016-4.3234108 5.2699709-8.6354801 6.2460792-1.8609157.2797775-3.8362101-.0940405-5.7140137-1.1865931-2.1407315-1.2455341-4.4144694-3.503981-6.0553618-6.1948739.0187808 2.0723495 3.7764384 6.0576603 5.9342381 7.2376698-.9367737.0237142-2.4691968-.3932156-3.1200741.7386627-5.9998256-.1512471-6.5604195-1.8928926-8.2563742-5.9540435-.3390647-.8119272-.7232751-1.9810477-.756441-2.6943729-.4627059-1.3040079 1.3941192-1.5492592 1.0254278-2.6826088zm1.4114021-18.4179207c-.7504505.44015027-1.1164727-.15117563-2.4875059.28615942-1.4904753.47543417-1.7618711.51125029-1.7438222.42310144 1.1400862-3.0500199 7.9781043-6.31313247 8.6498836-5.70269013h.0028816c.2534771.20019524.0555727.54255567-.3888558.69496208-.269705.09248869-1.768848 1.30622226-2.1944501 1.75372426-.4601907.48386846-.9379839 1.74136047-1.8381312 2.54474293zm-6.552946 9.7466289c-.2453848-3.0207694-.3236691-5.386481 1.5237371-6.8752622 1.8657474-1.5035618 4.4617843-2.02415494 4.5452929-1.6015585h-.0028736c.7186455 2.3687677-.3154615 4.9265824-.2585473 7.8946002.0184245.960804.6820579 2.3650825.4947415 3.0188752-.1190424.2205283-.3172893.2902448-.6567364.3431914-.1298428-2.3835495-2.5874789-4.2332759-5.6456142-2.7798461zm32.4737865 34.630842c1.0554108-3.1369598 4.2353252-11.7545206 4.0642596-13.2528985.040128-1.658918-2.5599197-2.6618238-5.334522-5.7627521-.2239439-.3911208.1247224-1.0979212.5605312-1.8527667 1.9562556-3.0924062 2.7360681-8.5637912 3.0284629-13.0388633 9.9973171 10.8607839 7.8274932 25.1614774 3.1482943 32.2027661-3.3426731.4176008-5.4734514 2.0281428-5.467026 1.7045145zm-3.9864971-16.3044329c.0779954-.1555206 1.260896-1.1362674 1.4862967-1.3212856.4301278.2716838 1.38033 1.2429404 2.383406 2.1319682 1.1109469.9846328 2.2727537 1.9048948 2.5593899 2.4582061.1526616.3031561.1526616.3088738.1238546.8408196-.836367 4.9767902-3.0647228 10.5983295-4.0252504 12.2770078-.2847638.4976759-1.1681317.5097189-2.4528022.5583352-1.1838459.0486209-1.5813443.1058206-1.9413954.2859939-.2246724.1143994-.2880451.1201171-.2880451.0200188 0-.1000938 4.6173036-7.6760398 4.8074125-8.0478265.2808776-.2329702-.8649593-1.0602052-.8785271-1.5129012.5691258-1.4867022.7019038-3.7086756.1826514-5.4432341-.6463905-2.1592441-1.4993073-1.6584963-1.9569909-2.2471022zM7.42243884 42.1312658c2.96225546-2.586478 6.14876306-4.4512718 9.48232206-5.8428294.5016567.0859968 1.2529385 1.2522493 2.1113377 2.1363665.9583995.987113 3.0928106 2.0244973 5.5447976 3.1401973 1.0727713 4.0182114 1.3969963 6.8164438 3.2692673 6.0230073.1814641-.0657785.3600511-.1201171.3888559-.1201171.4755212.2040689-1.4584967 1.587195-.8468409 2.5596351.927405.9815222 3.901969 2.1249765 6.2677861 3.2202814-4.3627853.216851-9.7240752.8353422-12.6450164 1.0381506-.3479927-.3262174-.5738478-1.5709715-.1526594-6.5864073 0-1.1983142-.5789648-1.1897309-.8007555.0142966-.3498102.8189554.7433036 5.0602025-.2010874 6.2322951-.7221824-.1516801-1.6541154-1.166919-1.9736252-.9986391-.1152167.1773168-.0057598.4747507.308206.8379585.4796679.6176388.3551967 1.0049462-.4579872 1.1296701-.6279317.1344181-.9015687.3317491-.9015687.6663639-.1395094.3793679 1.7505102-.2829514 3.5342674.2173543.2443345 2.3134203.7231506 6.0003224 1.2126529 9.7494889-.9334102.1562189-2.6924146-.144593-5.3689015-1.2736192-1.3569528-.5724096-2.9659282-1.5385969-4.7903192-2.4042461-.200343-6.1900766-1.0420005-13.0804379-3.98073156-19.7392074zm33.34942636-4.1568963c1.0761987.6798942 1.2887029 4.6628109.5588004 5.5525415-1.6636188-.111969-3.5862152-.3158021-4.634591-.6463406l.0144058-1.5100401c.2330021-.2415718.7888386-.3189489 1.4641343-.335567.1722553-.0263259.0618314-.1123771-.7209905-.256435-.3190395-.0503213-.3024234-.0644636-.5357543-.0228799-.296683.0571997-.3686937.0085833-.4464653-.2659706-.2473759-.6561483-.3081684-.5911451-.0748878-.6806605 1.7038609-1.0030418 3.0355483-2.2834828 4.3753484-1.8346478zm-5.570723 1.5515014c-2.185086.1358646-2.8748468-.4421992-3.5372803-.9069699-.1847635-.1519839-.0382396-.2665147.2881729-.1941026 1.500481.5652363 3.0031449-.2086077 5.104091-.5147882.1092616.0026072.1216968.0972554-.0086357.1887613l-1.8463479 1.4270994zm-10.7900283.366069c-.0288047.2345164-.0432083.2516741-.1584226.2516741-2.0120338-.3476962-3.7995849-1.358682-3.5745951-1.9418921.0921728-.2950715.0477019-.2362622.6610565.0114399 1.2486549.4270956 3.4091261.2227999 3.2937519.5605479l-.2217907 1.1182302zm1.5928594-1.3184178c.528331-.508549 1.3135619-.2434308 3.3405929.4603 2.1563543.7486335 2.3527608 1.4729544 3.6588196 1.62458.0561572.2824799.1364588.3496232-.1526616.5033482-.6745057.2826975-.9914879.3036775-1.4488495.4375697-.2534725.0714962-.3110842.1916133-.0662497.1429924.2794002-.0571997 1.6764011.3346103 1.8204201.5090659.0483778.1974263.0158878.6200102-.0057632 1.0124142-1.0173949.6903864-1.5958346 1.4047092-2.4893247 1.9470792-1.6727499 1.0154068-3.4106559 1.5628416-3.8879125 1.1702382-1.2952561-2.9770501-1.932367-5.7144963-.7690714-7.8075878zm8.583643 4.0925506c-.3825812 0-.5086758.0036728-.592898-.1884484l-.3086707-1.4931862c-.0375089-.2543493.0086426-.3002905.1353811-.3632124.8101859-.491804 1.505968-.2981457 1.7599291.2945727.3145116.7340378.0616898 1.7664571-.9937415 1.7502743zm2.6931749 9.5121068c-1.4713976-2.4595278-2.2944343-5.4311004-4.0585054-7.8190368l.4925506-.6492017c1.0905427.2081633 1.2475496-.429535 2.1902373-.3751193.5379942.0310551 1.0523054.9250978 2.2427228 1.0128856-.1457864 3.9609981-.3897989 6.9955018-.8670053 7.8304722zm1.6090782-7.7096341c1.3227297-.7345955 2.8101361.0347732 3.3798098 1.1032127-1.610471 2.9352215-3.4830136 5.9623388-4.3829794 6.8766044.5539163-2.3396918.9406874-5.2615781 1.0031696-7.9798171zm-10.4749847 4.7267303c.558451-2.0553211 2.0712576-2.9049145 3.9116025-4.0982773 1.4246776 1.5691577 2.6177963 4.4670215 3.8050284 7.3986337-.2971831.24412-5.4596849-1.8637622-7.7166309-3.3003564zm23.1182041 12.895413c-1.3330231.5543042-4.0072895.5021466-4.1852372.1315615.0739037-.3542844 3.1411702-.7231918 3.1396495-.8608383-.6133912-.3748428-2.7728487-.3046524-5.4785479-.077223-.270648-2.6463211-.537597-5.3210721-.9793426-6.6521858.7334235-.8522005 3.4006662-1.4868246 4.5191437-1.9646812.815803-.3485441 2.1127077-1.3390668 3.4019951.8207101.9678298 2.8423689 2.7158877 6.8163441-.4176606 8.6026567zm-9.3987815-.1401404c-.1433729-1.4407521-.5979511-4.2829396-.9707023-7.1269364.0110378-.0756541-.1462499-.1899945 1.5642693-.1007512.9756754 1.6290689 1.2601698 4.0207551 1.2412544 6.7472193-.8914739.3505799-1.4238694.4422762-1.8348214.4804683zM24.6559484 73.4016815c-1.8657839-5.9032529-3.0647183-11.7852764-3.5861148-17.6457396 4.4640711-.8617996 10.0507435-1.5304034 15.1941792-.643475.7175837 8.3497085.7617578 15.8041862 1.3480363 18.5580554-3.4600635.9393037-7.5809416 1.0867443-12.9561007-.2688408zm12.9647364-9.9096299c-.2218158-2.9470242-.3887851-5.8165669-.3226039-8.3567048.6615704-.1614876 1.3503996-.2862705 2.2747495-.1747186.5406201 2.071853.6619904 5.487357.888204 7.8893586.0043704.0464353.0030917.1411424-.0233041.1901941-1.1417039.2140125-2.6601893.5458614-2.8170455.4518707zm2.7940109 6.8009004c-.8881082.3303344-1.7105329.5320546-2.4109114.4947649-.1643206-2.1891635-.2022245-4.4026532-.2534748-5.9429278 1.1125659-.0275274 1.9020826-.6572183 1.8549904.0600654.2541758 1.6986744.8236669 3.3178768.8093958 5.3880975zm.8612375-52.6483682c-.1872274.0486197-.2073895.0657774-.2073895.1944744 0 .4575884.3312441.8751371.7949877 1.0124141.7517909.2144932 2.0422154-.1773156 2.7219934-.8179397.4176606-.4003899.4118974-.37179.0720084-.9323357-.2765209-.4518684-.8324396-1.5557987-1.4142792-2.8084481-.1296224-.2859928-.2506-.514787-.2678805-.514787-.0230438 0-.0360042.1673074-.0360042.3675006 0 .6463429.1598679 1.2855387.5861688 2.3437103.1296179.3231715.2304333.632043.2189114.6892427-.047622.3048632-1.7124007.2862909-2.4685163.4661684zm-8.8054178-.674945c-.6048834-.6291842-.7345035-.8379596-1.2068942-1.9418898-.1785871-.4146899-.5357612-1.1697088-.5357612-1.1697088-.3586149 1.3439377 1.1471681 3.4797556 1.3221155 4.0782541-.1473871.3515298-.8972714.4688277-1.686263.3515298-1.1793841-.1753364-1.6776272-.344411-1.750074-.0969969-.1583062.3134421-.1793109.5622358.0695743.8514627.5033395.584924 2.0079877.7810739 3.0671136.5706162.5009488-.0995429.7407462-.3381674 1.0283939-.5784016.4119202-.3440245.5241525-.9569113.2809826-1.3840749-.1231057-.2162525-.3522101-.4109649-.5891875-.6807908zm2.4483473 10.2070751c3.5064365.196941 5.3796962-1.1430348 7.4055398-2.3394175.0751549-.4534056.3155985-1.0462102.4608666-1.5929808.0950567-.4575862.0576094-.5290869-.7517864-1.3184268-2.0854214-2.0276848-4.3580678-5.1621667-4.6288254-6.3947939-.1719059-.8159288-.6864408-.820938-.599127-.2087743-.0130198 2.2932077 2.1214256 4.9591893 4.0786677 7.0954765.5821227.5774902.8298593 1.3924554.146903 2.2450436-1.2912055.6016759-2.579438 1.2011618-3.3412803 1.4128052-2.725544.4427795-2.5725262.0994839-3.0445949-1.5701032 0-.0085788-.0172852-.0200187-.0489669-.0200187-.2396513.1542918-.8132822 1.5595995.3226038 2.6911899zm-18.9272026-3.7121806c.5120563-3.2591491-3.2341125-4.7353352-3.8154246-2.0384129.0684612.2191362.3203958-.6146734 1.1193578-.3667876-.4047299 1.4989357-.30906 2.6578608.2144074 3.5278289.1771645.219805.0729697-1.9784269.2378166-2.4439163.2146791-.578057.6621457-.8122356 1.0242599-.5554809.3348872.2374501.7232739 1.3000631 1.2195829 1.8767688zM37.977868 29.985162c.0058957-.6745097-.0058591-1.3032075-.1008131-1.7159567-2.5228263-.0387385-5.4137344-.0493554-7.1319052-.8579772-.0777716-.0343199-.0720107-.0042895.0489646.2216437.6669865 1.320313 3.4760563 2.5471818 7.1837537 2.3522902zm-8.3186418-.5205058c.605251.5591037 1.2321735 1.5441173 1.797381 2.2164415 1.5968598 1.9539169 5.8118059 1.4710364 7.0598944.6377641.1497801-.1229782.4925506-.6005854.4925506-.6806627-4.1099886-.0213768-7.289903.2138719-8.6412433-2.3079588-.1630898-.3833694-.2429941-.3504598-.5720645-.2768302-.3105339.0694807-.2772858.2201542-.1365182.4112461zm19.8505261 24.8374977c-.7082744-.0782477-2.9402106.3236555-3.846942 1.6425538 1.1316115.177081 2.5202484-1.1793474 3.846942-1.6425538zm.879815 2.3975761c.1807562-.0576803-1.3810242-.4067458-2.5775565-.3445675-.6858425.0356393-1.3818187.3913883-1.2303536.5447597.7647057.0825235 2.1757607-.0261355 3.8079101-.2001922zm.37445 2.0334071c-1.2844649-.396648-3.5702567-.7666799-4.297579-.0858018.204348.1373155 4.7274557.2154227 4.297579.0858018zM10.00905 17.2442234c.4131133-.5564388.4900949.7719747.3360872 1.1800536-2.26958619 2.8168025-.64580712 9.8906178 4.1429491 10.7114866-.0527059 2.1839014 1.7782406 4.0514339 1.7922055 5.0245428.0131558.9233499-2.643008 1.7219601-8.20474079 5.4054705-1.01301658.6709164-2.16982393.9905273-2.26615024 2.0049455-.14060831 1.7961928 3.26377063 7.4421808 3.76758284 14.0451018-7.74279379-6.602921-13.29829129-25.1133417.43206639-38.3716008zm11.3344206 49.95143c.7744808 2.7034618 1.5656256 5.539773 2.540527 7.8562177 4.1938386 1.2252114 8.3597722 1.0468518 12.3713795.6577851 1.7400089-.2462784 2.3531033-.8003196 2.67046-1.6274548.0736981-.1920849-.1473962-.978711-.1212927-1.8530705 1.3465269-.278195 2.5070002-.3087876 2.9177925-1.6649395.3717465-1.2272791-1.1306023-4.8176195-.5932976-6.1483841.7907772-.1828803 1.3238805-.563613 1.2385829-1.3870664.8995479-.5029854 2.2333473-.6780895 3.8107871-.6692205.1209867.2266949.2695702.4509186.4522262.6720862.1785871.2059143.1872274.2087754.679778.2945727 1.5064589.2573964 2.3908406.2226684 4.2313293.0142965 2.9667936-.3240363 3.6401873-5.0599803 2.8055191-7.278503-.4205377-1.4499839-.9678207-2.9257087-1.376841-3.7121852-.6888338-1.1635772-1.1668804-1.1161126-1.3509111-1.3699042 6.1624679-10.1759497 5.1983554-25.3322172-5.0119272-34.8939595-.1491178-6.56000758-2.9313374-10.88118462-8.5286046-14.55627608-3.0926211-2.03057428-12.370489-2.17915292-16.7556673.40250145-3.8552272 2.07365627-7.4997837 3.46897675-8.5519475 7.22703241-2.0096215 1.14435482-2.545466 3.32852382-2.6701461 5.80279012-13.67288175 11.6898471-13.14913114 31.9336859-.34277054 43.1705844.19339699 2.1792606-.08949447 4.8812579.69418034 5.3966763 1.3862439.792693 2.8089087 1.2135222 3.9831199 1.8179844 1.9821925 1.020399 3.7363561 1.9764568 6.9077238 1.8484365zm7.9065686-56.6769982c.2992861-.1857233.3606471-1.38743884-1.4839585-1.54769376-.5022504-.04363432-1.4704433.40739137-2.2695867 1.07719506-.9602103.8048006-1.3603391 1.5809877-1.5712519 2.2658242-.0901726.2927942-.1088871.5476445-.0862246.748247.0238726.2113237.2608089.4837171.3231587.4775267.457599-.0454344.9477407-3.6401765 5.087863-3.0210992zm9.4623346 17.6904915c-.043206.0714985-.0518462 1.2183285-.0086449 1.4757226.0288071.1773168.0374473.1801733.2304333.1801733 2.3590241-.1859341 3.7712802-1.9131607 4.6201829-3.6149456-4.1535211 2.3084802-4.3352318 1.6143485-4.8419713 1.9590497zm-1.955767 17.3298377c0-.4171746-.4541626-.6793274-.8155312-.4707378-.3613687.2085896-.3613664.7328906 0 .9414802.3613686.2085895.8155312-.0535633.8155312-.4707424zm.5558502 2.590917c0-.4113662-.4514727-.6698689-.8107019-.4641858-.3592291.2056831-.3592291.722684 0 .9283671.3592292.2056831.8107019-.0528151.8107019-.4641813z"/></svg>
\ No newline at end of file
diff --git a/server/sonar-web/public/images/tutorials/refresh.svg b/server/sonar-web/public/images/tutorials/refresh.svg
new file mode 100644 (file)
index 0000000..04579ae
--- /dev/null
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28"><g fill="none" fill-rule="evenodd" transform="translate(-4 -4)"><path d="M0 0h36v36H0z"/><path fill="#236A97" fill-rule="nonzero" d="M31.3333333 15.2148148H21.28821l4.0595336-4.1777778c-4.0447178-3.99999996-10.5933086-4.14814811-14.6380264-.1481481-4.04471779 4.0148148-4.04471779 10.4888889 0 14.5037037 4.0447178 4.0148148 10.5933086 4.0148148 14.6380264 0 2.014951-1.9851852 3.0224265-4.3111111 3.0224265-7.2444445h2.9631632c0 2.9333334-1.3037918 6.7407408-3.9113754 9.3185186-5.2003515 5.1555555-13.6453667 5.1555555-18.84571821 0-5.18553566-5.1407408-5.2299831-13.4962963-.02963163-18.63703707 5.20035144-5.14074074 13.54165594-5.14074074 18.74200744 0l4.0447178-4.16296296V15.2148148zm-12.5934437-3.1407407v6.2962963l5.1855356 3.0814815-1.0667387 1.7925925-6.3411693-3.7629629v-7.4074074h2.2223724z"/></g></svg>
\ No newline at end of file
index 5b70298a7c425b0378a435c1ad981e710ab7856f..56691948c8cd0d4867901ca13ee9b75ce56611f5 100644 (file)
@@ -34,7 +34,11 @@ import {
 } from '../types/alm-settings';
 
 export function getAlmDefinitions(): Promise<AlmSettingsBindingDefinitions> {
-  return getJSON('/api/alm_settings/list_definitions').catch(throwGlobalError);
+  return getAlmDefinitionsNoCatch().catch(throwGlobalError);
+}
+
+export function getAlmDefinitionsNoCatch(): Promise<AlmSettingsBindingDefinitions> {
+  return getJSON('/api/alm_settings/list_definitions');
 }
 
 export function getAlmSettings(project?: string): Promise<AlmSettingsInstance[]> {
index c60873f126c5af0d67412359475a3a75ad09cc52..278ab8ea5cb7d678ce4e34b4ee0fbb8c5ef70ab0 100644 (file)
@@ -22,7 +22,7 @@ import { FormattedMessage } from 'react-intl';
 import { connect } from 'react-redux';
 import { Alert } from 'sonar-ui-common/components/ui/Alert';
 import { translate } from 'sonar-ui-common/helpers/l10n';
-import AnalyzeTutorial from '../../../components/tutorials/manual/AnalyzeTutorial';
+import TutorialSelection from '../../../components/tutorials/TutorialSelection';
 import { getBranchLikeDisplayName, isBranch, isMainBranch } from '../../../helpers/branch-like';
 import { isLoggedIn } from '../../../helpers/users';
 import { getCurrentUser, Store } from '../../../store/rootReducer';
@@ -87,7 +87,7 @@ export function EmptyOverview(props: Props) {
       {isLoggedIn(currentUser) ? (
         <>
           {showWarning && <Alert variant="warning">{warning}</Alert>}
-          {showTutorial && <AnalyzeTutorial component={component} currentUser={currentUser} />}
+          {showTutorial && <TutorialSelection component={component} currentUser={currentUser} />}
         </>
       ) : (
         <Alert variant="warning">{warning}</Alert>
index b26d0feb600ea65f21dd557c9de77178d62811d1..7dc937d251db5997caef1afc07be096946216a05 100644 (file)
@@ -4,7 +4,7 @@ exports[`renders correctly 1`] = `
 <div
   className="page page-limited"
 >
-  <AnalyzeTutorial
+  <withRouter(TutorialSelection)
     component={
       Object {
         "breadcrumbs": Array [],
index 23ff4f0b9e325d0e96203a480289946e1c26bfd2..ae7c34ae69c0b6306e6ce5af175d273824523580 100644 (file)
@@ -40,6 +40,7 @@
   font-size: 11px;
   font-weight: normal;
   user-select: none;
+  background: var(--gray40);
 }
 
 .code-snippet > button:hover,
diff --git a/server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx b/server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx
new file mode 100644 (file)
index 0000000..44891c3
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { WithRouterProps } from 'react-router';
+import { getAlmDefinitionsNoCatch, getProjectAlmBinding } from '../../api/alm-settings';
+import { AlmBindingDefinition, AlmKeys, ProjectAlmBindingResponse } from '../../types/alm-settings';
+import { withRouter } from '../hoc/withRouter';
+import './styles.css';
+import TutorialSelectionRenderer from './TutorialSelectionRenderer';
+import { TutorialModes } from './types';
+
+interface Props extends Pick<WithRouterProps, 'router' | 'location'> {
+  component: T.Component;
+  currentUser: T.LoggedInUser;
+}
+
+interface State {
+  almBinding?: AlmBindingDefinition;
+  forceManual: boolean;
+  loading: boolean;
+  projectBinding?: ProjectAlmBindingResponse;
+}
+
+export class TutorialSelection extends React.PureComponent<Props, State> {
+  mounted = false;
+  state: State = {
+    forceManual: true,
+    loading: true
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.fetchAlmBindings();
+  }
+
+  fetchAlmBindings = async () => {
+    const { component } = this.props;
+
+    const [almDefinitions, projectBinding] = await Promise.all([
+      getAlmDefinitionsNoCatch().catch(() => undefined),
+      getProjectAlmBinding(component.key).catch(() => undefined)
+    ]);
+
+    if (this.mounted) {
+      // We only support Bitbucket for now.
+      if (projectBinding === undefined || projectBinding.alm !== AlmKeys.Bitbucket) {
+        this.setState({ loading: false, forceManual: true });
+      } else {
+        let almBinding;
+        if (almDefinitions !== undefined) {
+          almBinding = almDefinitions[projectBinding.alm].find(d => d.key === projectBinding.key);
+        }
+        this.setState({ almBinding, forceManual: false, projectBinding, loading: false });
+      }
+    }
+  };
+
+  handleSelectTutorial = (selectedTutorial: TutorialModes) => {
+    const {
+      router,
+      location: { pathname, query }
+    } = this.props;
+
+    router.push({
+      pathname,
+      query: { ...query, selectedTutorial }
+    });
+  };
+
+  render() {
+    const { component, currentUser, location } = this.props;
+    const { almBinding, forceManual, loading, projectBinding } = this.state;
+
+    const selectedTutorial: TutorialModes | undefined = forceManual
+      ? TutorialModes.Manual
+      : location.query?.selectedTutorial;
+
+    return (
+      <TutorialSelectionRenderer
+        almBinding={almBinding}
+        component={component}
+        currentUser={currentUser}
+        loading={loading}
+        onSelectTutorial={this.handleSelectTutorial}
+        projectBinding={projectBinding}
+        selectedTutorial={selectedTutorial}
+      />
+    );
+  }
+}
+
+export default withRouter(TutorialSelection);
diff --git a/server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx b/server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx
new file mode 100644 (file)
index 0000000..ca68491
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 'sonar-ui-common/helpers/l10n';
+import { getBaseUrl } from 'sonar-ui-common/helpers/urls';
+import { AlmBindingDefinition, ProjectAlmBindingResponse } from '../../types/alm-settings';
+import JenkinsTutorial from './jenkins/JenkinsTutorial';
+import ManualTutorial from './manual/ManualTutorial';
+import { TutorialModes } from './types';
+
+export interface TutorialSelectionRendererProps {
+  almBinding?: AlmBindingDefinition;
+  component: T.Component;
+  currentUser: T.LoggedInUser;
+  loading: boolean;
+  onSelectTutorial: (mode: TutorialModes) => void;
+  projectBinding?: ProjectAlmBindingResponse;
+  selectedTutorial?: TutorialModes;
+}
+
+export default function TutorialSelectionRenderer(props: TutorialSelectionRendererProps) {
+  const { almBinding, component, currentUser, loading, projectBinding, selectedTutorial } = props;
+
+  if (loading) {
+    return <i className="spinner" />;
+  }
+
+  return (
+    <>
+      {selectedTutorial === undefined && (
+        <div className="tutorial-selection">
+          <header className="spacer-top spacer-bottom padded">
+            <h1 className="text-center big-spacer-bottom">
+              {translate('onboarding.tutorial.choose_method')}
+            </h1>
+          </header>
+
+          <div className="display-flex-space-around">
+            <button
+              className="button button-huge display-flex-column tutorial-mode-jenkins"
+              onClick={() => props.onSelectTutorial(TutorialModes.Jenkins)}
+              type="button">
+              <img
+                alt="" // Should be ignored by screen readers
+                height={80}
+                src={`${getBaseUrl()}/images/tutorials/jenkins.svg`}
+              />
+              <div className="medium big-spacer-top">
+                {translate('onboarding.tutorial.choose_method.jenkins')}
+              </div>
+            </button>
+
+            <button
+              className="button button-huge display-flex-column tutorial-mode-manual"
+              onClick={() => props.onSelectTutorial(TutorialModes.Manual)}
+              type="button">
+              <img
+                alt="" // Should be ignored by screen readers
+                height={80}
+                src={`${getBaseUrl()}/images/sonarcloud/analysis/manual.svg`}
+              />
+              <div className="medium big-spacer-top">
+                {translate('onboarding.tutorial.choose_method.manual')}
+              </div>
+            </button>
+          </div>
+        </div>
+      )}
+
+      {selectedTutorial === TutorialModes.Manual && (
+        <ManualTutorial component={component} currentUser={currentUser} />
+      )}
+
+      {selectedTutorial === TutorialModes.Jenkins && projectBinding !== undefined && (
+        <JenkinsTutorial
+          almBinding={almBinding}
+          component={component}
+          projectBinding={projectBinding}
+        />
+      )}
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx b/server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx
new file mode 100644 (file)
index 0000000..7729070
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import { getAlmDefinitionsNoCatch, getProjectAlmBinding } from '../../../api/alm-settings';
+import { mockBitbucketBindingDefinition } from '../../../helpers/mocks/alm-settings';
+import {
+  mockComponent,
+  mockLocation,
+  mockLoggedInUser,
+  mockRouter
+} from '../../../helpers/testMocks';
+import { AlmKeys } from '../../../types/alm-settings';
+import { TutorialSelection } from '../TutorialSelection';
+import { TutorialModes } from '../types';
+
+jest.mock('../../../api/alm-settings', () => ({
+  getProjectAlmBinding: jest.fn().mockRejectedValue(null),
+  getAlmDefinitionsNoCatch: jest.fn().mockRejectedValue(null)
+}));
+
+beforeEach(jest.clearAllMocks);
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should select manual if project is not bound', async () => {
+  const wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state().forceManual).toBe(true);
+});
+
+it('should not select anything if project is bound to Bitbucket', async () => {
+  (getProjectAlmBinding as jest.Mock).mockResolvedValueOnce({ alm: AlmKeys.Bitbucket });
+  const wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state().forceManual).toBe(false);
+});
+
+it('should select manual if project is bound to any other ALM', async () => {
+  (getProjectAlmBinding as jest.Mock).mockResolvedValueOnce({ alm: AlmKeys.GitLab });
+  const wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state().forceManual).toBe(true);
+});
+
+it('should correctly find the global ALM binding definition', async () => {
+  const key = 'foo';
+  const almBinding = mockBitbucketBindingDefinition({ key });
+  (getProjectAlmBinding as jest.Mock).mockResolvedValueOnce({ alm: AlmKeys.Bitbucket, key });
+  (getAlmDefinitionsNoCatch as jest.Mock).mockResolvedValueOnce({
+    [AlmKeys.Bitbucket]: [almBinding]
+  });
+  const wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state().almBinding).toBe(almBinding);
+});
+
+it('should handle selection', () => {
+  const push = jest.fn();
+  const wrapper = shallowRender({ router: mockRouter({ push }) });
+  const instance = wrapper.instance();
+
+  instance.handleSelectTutorial(TutorialModes.Manual);
+  expect(push).toHaveBeenLastCalledWith(
+    expect.objectContaining({
+      query: { selectedTutorial: TutorialModes.Manual }
+    })
+  );
+
+  instance.handleSelectTutorial(TutorialModes.Jenkins);
+  expect(push).toHaveBeenLastCalledWith(
+    expect.objectContaining({
+      query: { selectedTutorial: TutorialModes.Jenkins }
+    })
+  );
+});
+
+function shallowRender(props: Partial<TutorialSelection['props']> = {}) {
+  return shallow<TutorialSelection>(
+    <TutorialSelection
+      component={mockComponent()}
+      currentUser={mockLoggedInUser()}
+      location={mockLocation()}
+      router={mockRouter()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelectionRenderer-test.tsx b/server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelectionRenderer-test.tsx
new file mode 100644 (file)
index 0000000..151db95
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { click } from 'sonar-ui-common/helpers/testUtils';
+import {
+  mockBitbucketBindingDefinition,
+  mockProjectBitbucketBindingGet
+} from '../../../helpers/mocks/alm-settings';
+import { mockComponent, mockLoggedInUser } from '../../../helpers/testMocks';
+import TutorialSelectionRenderer, {
+  TutorialSelectionRendererProps
+} from '../TutorialSelectionRenderer';
+import { TutorialModes } from '../types';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot('selection');
+  expect(shallowRender({ loading: true })).toMatchSnapshot('loading');
+  expect(shallowRender({ selectedTutorial: TutorialModes.Manual })).toMatchSnapshot(
+    'manual tutorial'
+  );
+  expect(
+    shallowRender({
+      selectedTutorial: TutorialModes.Jenkins,
+      projectBinding: mockProjectBitbucketBindingGet()
+    })
+  ).toMatchSnapshot('jenkins tutorial');
+});
+
+it('should allow mode selection', () => {
+  const onSelectTutorial = jest.fn();
+  const wrapper = shallowRender({ onSelectTutorial });
+
+  click(wrapper.find('button.tutorial-mode-jenkins'));
+  expect(onSelectTutorial).toHaveBeenLastCalledWith(TutorialModes.Jenkins);
+
+  click(wrapper.find('button.tutorial-mode-manual'));
+  expect(onSelectTutorial).toHaveBeenLastCalledWith(TutorialModes.Manual);
+});
+
+function shallowRender(props: Partial<TutorialSelectionRendererProps> = {}) {
+  return shallow<TutorialSelectionRendererProps>(
+    <TutorialSelectionRenderer
+      almBinding={mockBitbucketBindingDefinition()}
+      component={mockComponent()}
+      currentUser={mockLoggedInUser()}
+      loading={false}
+      onSelectTutorial={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/__tests__/__snapshots__/TutorialSelection-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/__tests__/__snapshots__/TutorialSelection-test.tsx.snap
new file mode 100644 (file)
index 0000000..ecdac5b
--- /dev/null
@@ -0,0 +1,41 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<TutorialSelectionRenderer
+  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 [],
+    }
+  }
+  loading={true}
+  onSelectTutorial={[Function]}
+  selectedTutorial="manual"
+/>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/__tests__/__snapshots__/TutorialSelectionRenderer-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/__tests__/__snapshots__/TutorialSelectionRenderer-test.tsx.snap
new file mode 100644 (file)
index 0000000..b78bf91
--- /dev/null
@@ -0,0 +1,145 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: jenkins tutorial 1`] = `
+<Fragment>
+  <JenkinsTutorial
+    almBinding={
+      Object {
+        "key": "key",
+        "personalAccessToken": "asdf1234",
+        "url": "http://bbs.enterprise.com",
+      }
+    }
+    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 [],
+      }
+    }
+    projectBinding={
+      Object {
+        "alm": "bitbucket",
+        "key": "foo",
+        "repository": "PROJECT_KEY",
+        "slug": "repo-slug",
+      }
+    }
+  />
+</Fragment>
+`;
+
+exports[`should render correctly: loading 1`] = `
+<i
+  className="spinner"
+/>
+`;
+
+exports[`should render correctly: manual tutorial 1`] = `
+<Fragment>
+  <ManualTutorial
+    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 [],
+      }
+    }
+  />
+</Fragment>
+`;
+
+exports[`should render correctly: selection 1`] = `
+<Fragment>
+  <div
+    className="tutorial-selection"
+  >
+    <header
+      className="spacer-top spacer-bottom padded"
+    >
+      <h1
+        className="text-center big-spacer-bottom"
+      >
+        onboarding.tutorial.choose_method
+      </h1>
+    </header>
+    <div
+      className="display-flex-space-around"
+    >
+      <button
+        className="button button-huge display-flex-column tutorial-mode-jenkins"
+        onClick={[Function]}
+        type="button"
+      >
+        <img
+          alt=""
+          height={80}
+          src="/images/tutorials/jenkins.svg"
+        />
+        <div
+          className="medium big-spacer-top"
+        >
+          onboarding.tutorial.choose_method.jenkins
+        </div>
+      </button>
+      <button
+        className="button button-huge display-flex-column tutorial-mode-manual"
+        onClick={[Function]}
+        type="button"
+      >
+        <img
+          alt=""
+          height={80}
+          src="/images/sonarcloud/analysis/manual.svg"
+        />
+        <div
+          className="medium big-spacer-top"
+        >
+          onboarding.tutorial.choose_method.manual
+        </div>
+      </button>
+    </div>
+  </div>
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/LabelActionPair.tsx b/server/sonar-web/src/main/js/components/tutorials/components/LabelActionPair.tsx
new file mode 100644 (file)
index 0000000..b26c13f
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 'sonar-ui-common/helpers/l10n';
+
+export interface LabelActionPairProps {
+  translationKey: string;
+}
+
+export default function LabelActionPair({ translationKey }: LabelActionPairProps) {
+  return (
+    <>
+      <strong>{translate(translationKey, 'label')}:</strong> {translate(translationKey, 'action')}
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/LabelValuePair.tsx b/server/sonar-web/src/main/js/components/tutorials/components/LabelValuePair.tsx
new file mode 100644 (file)
index 0000000..68ab8eb
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { ClipboardIconButton } from 'sonar-ui-common/components/controls/clipboard';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+
+export interface LabelValuePairProps {
+  translationKey: string;
+  value: string;
+}
+
+export default function LabelValuePair({ translationKey, value }: LabelValuePairProps) {
+  return (
+    <div className="display-flex-center">
+      <strong className="little-spacer-right">{translate(translationKey, 'label')}:</strong> {value}
+      <ClipboardIconButton className="little-spacer-left" copyValue={value} />
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/SentenceWithFilename.tsx b/server/sonar-web/src/main/js/components/tutorials/components/SentenceWithFilename.tsx
new file mode 100644 (file)
index 0000000..0e4c669
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 'sonar-ui-common/helpers/l10n';
+
+export interface SentenceWithFilenameProps {
+  filename: string;
+  translationKey: string;
+}
+
+export default function SentenceWithFilename({
+  filename,
+  translationKey
+}: SentenceWithFilenameProps) {
+  return (
+    <span className="markdown">
+      <FormattedMessage
+        defaultMessage={translate(translationKey, 'sentence')}
+        id={`${translationKey}.sentence`}
+        values={{
+          file: <code>{filename}</code>
+        }}
+      />
+    </span>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/SentenceWithHighlights.tsx b/server/sonar-web/src/main/js/components/tutorials/components/SentenceWithHighlights.tsx
new file mode 100644 (file)
index 0000000..45c6a28
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 'sonar-ui-common/helpers/l10n';
+
+export interface SentenceWithHighlightsProps {
+  highlightKeys: string[];
+  translationKey: string;
+}
+
+export default function SentenceWithHighlights({
+  highlightKeys,
+  translationKey
+}: SentenceWithHighlightsProps) {
+  const values: T.Dict<JSX.Element> = {};
+  highlightKeys.forEach(key => {
+    values[key] = <strong>{translate(translationKey, 'sentence', key)}</strong>;
+  });
+  return (
+    <FormattedMessage
+      defaultMessage={translate(translationKey, 'sentence')}
+      id={`${translationKey}.sentence`}
+      values={values}
+    />
+  );
+}
index ec206ba8afd2c765d1cc6a135bb875da3833c670..5e150f8b666e24c09a7abcaa00720d6b2c973d54 100644 (file)
 .onboarding-step {
   position: relative;
   padding-left: 34px;
+  margin-bottom: var(--gridSize);
+}
+
+.onboarding-step.no-step-number {
+  padding-left: 0;
 }
 
 .onboarding-step:not(.is-open):not(.is-finished) {
   cursor: pointer;
   outline: none;
 }
+
+.onboarding-step ol.list-styled {
+  list-style: none;
+  padding-left: 0;
+  counter-reset: step-counter;
+}
+
+.onboarding-step .markdown {
+  line-height: inherit;
+}
+
+.onboarding-step ul.list-styled li {
+  margin-top: var(--gridSize);
+  margin-bottom: var(--gridSize);
+}
+
+.onboarding-step ol.list-styled > li {
+  position: relative;
+  counter-increment: step-counter;
+  margin-bottom: calc(2 * var(--gridSize));
+  padding-left: calc(4 * var(--gridSize));
+}
+
+.onboarding-step ol.list-styled > li::before {
+  content: counter(step-counter);
+  color: white;
+  background-color: var(--blue);
+  display: inline-flex;
+  border-radius: 50%;
+  width: 16px;
+  position: absolute;
+  left: 0;
+  font-size: 10px;
+  height: 16px;
+  justify-content: center;
+  align-items: center;
+}
index 106d8c3b6a7691e51a40a1b114b4a54e1d71e94c..dba05b1bedcc7a9ac5189888aa9fdf4c255a63c8 100644 (file)
@@ -24,25 +24,29 @@ import './Step.css';
 
 interface Props {
   finished?: boolean;
-  onOpen: VoidFunction;
+  onOpen?: VoidFunction;
   open: boolean;
   renderForm: () => React.ReactNode;
-  renderResult: () => React.ReactNode;
-  stepNumber: number;
+  renderResult?: () => React.ReactNode;
+  stepNumber?: number;
   stepTitle: React.ReactNode;
 }
 
 export default function Step(props: Props) {
+  const { finished, open, stepNumber, stepTitle } = props;
   const className = classNames('boxed-group', 'onboarding-step', {
-    'is-open': props.open,
-    'is-finished': props.finished
+    'is-open': open,
+    'is-finished': finished,
+    'no-step-number': stepNumber === undefined
   });
 
-  const clickable = !props.open && props.finished;
+  const clickable = !open && finished && props.onOpen !== undefined;
 
   const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
     event.preventDefault();
-    props.onOpen();
+    if (props.onOpen !== undefined) {
+      props.onOpen();
+    }
   };
 
   return (
@@ -51,13 +55,13 @@ export default function Step(props: Props) {
       onClick={clickable ? handleClick : undefined}
       role={clickable ? 'button' : undefined}
       tabIndex={clickable ? 0 : undefined}>
-      <div className="onboarding-step-number">{props.stepNumber}</div>
-      {!props.open && props.renderResult()}
+      {stepNumber !== undefined && <div className="onboarding-step-number">{stepNumber}</div>}
+      {!open && props.renderResult && props.renderResult()}
       <div className="boxed-group-header">
-        <h2>{props.stepTitle}</h2>
+        <h2>{stepTitle}</h2>
       </div>
-      {!props.open && <div className="boxed-group-inner" />}
-      <div className={classNames({ hidden: !props.open })}>{props.renderForm()}</div>
+      {!open && <div className="boxed-group-inner" />}
+      <div className={classNames({ hidden: !open })}>{props.renderForm()}</div>
     </div>
   );
 }
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/__tests__/LabelActionPair-test.tsx b/server/sonar-web/src/main/js/components/tutorials/components/__tests__/LabelActionPair-test.tsx
new file mode 100644 (file)
index 0000000..4e38e36
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import LabelActionPair, { LabelActionPairProps } from '../LabelActionPair';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<LabelActionPairProps> = {}) {
+  return shallow<LabelActionPairProps>(<LabelActionPair translationKey="foo" {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/__tests__/LabelValuePair-test.tsx b/server/sonar-web/src/main/js/components/tutorials/components/__tests__/LabelValuePair-test.tsx
new file mode 100644 (file)
index 0000000..3794e83
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import LabelValuePair, { LabelValuePairProps } from '../LabelValuePair';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<LabelValuePairProps> = {}) {
+  return shallow<LabelValuePairProps>(
+    <LabelValuePair translationKey="foo" value="bar" {...props} />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/__tests__/SentenceWithFilename-test.tsx b/server/sonar-web/src/main/js/components/tutorials/components/__tests__/SentenceWithFilename-test.tsx
new file mode 100644 (file)
index 0000000..0271f21
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import SentenceWithFilename, { SentenceWithFilenameProps } from '../SentenceWithFilename';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<SentenceWithFilenameProps> = {}) {
+  return shallow<SentenceWithFilenameProps>(
+    <SentenceWithFilename filename="foo.txt" translationKey="bar" {...props} />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/__tests__/SentenceWithHighlights-test.tsx b/server/sonar-web/src/main/js/components/tutorials/components/__tests__/SentenceWithHighlights-test.tsx
new file mode 100644 (file)
index 0000000..005656b
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import SentenceWithHighlights, { SentenceWithHighlightsProps } from '../SentenceWithHighlights';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<SentenceWithHighlightsProps> = {}) {
+  return shallow<SentenceWithHighlightsProps>(
+    <SentenceWithHighlights highlightKeys={['hello', 'world']} translationKey="foo" {...props} />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/LabelActionPair-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/LabelActionPair-test.tsx.snap
new file mode 100644 (file)
index 0000000..574d189
--- /dev/null
@@ -0,0 +1,12 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <strong>
+    foo.label
+    :
+  </strong>
+   
+  foo.action
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/LabelValuePair-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/LabelValuePair-test.tsx.snap
new file mode 100644 (file)
index 0000000..9930524
--- /dev/null
@@ -0,0 +1,20 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+  className="display-flex-center"
+>
+  <strong
+    className="little-spacer-right"
+  >
+    foo.label
+    :
+  </strong>
+   
+  bar
+  <ClipboardIconButton
+    className="little-spacer-left"
+    copyValue="bar"
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/SentenceWithFilename-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/SentenceWithFilename-test.tsx.snap
new file mode 100644 (file)
index 0000000..54c401e
--- /dev/null
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<span
+  className="markdown"
+>
+  <FormattedMessage
+    defaultMessage="bar.sentence"
+    id="bar.sentence"
+    values={
+      Object {
+        "file": <code>
+          foo.txt
+        </code>,
+      }
+    }
+  />
+</span>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/SentenceWithHighlights-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/SentenceWithHighlights-test.tsx.snap
new file mode 100644 (file)
index 0000000..dc9ab66
--- /dev/null
@@ -0,0 +1,18 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<FormattedMessage
+  defaultMessage="foo.sentence"
+  id="foo.sentence"
+  values={
+    Object {
+      "hello": <strong>
+        foo.sentence.hello
+      </strong>,
+      "world": <strong>
+        foo.sentence.world
+      </strong>,
+    }
+  }
+/>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/BitbucketWebhookStep.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/BitbucketWebhookStep.tsx
new file mode 100644 (file)
index 0000000..5ce0941
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { Button, ButtonLink } from 'sonar-ui-common/components/controls/buttons';
+import { Alert } from 'sonar-ui-common/components/ui/Alert';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+import {
+  BitbucketBindingDefinition,
+  ProjectBitbucketBindingResponse
+} from '../../../types/alm-settings';
+import CodeSnippet from '../../common/CodeSnippet';
+import LabelActionPair from '../components/LabelActionPair';
+import SentenceWithHighlights from '../components/SentenceWithHighlights';
+import Step from '../components/Step';
+
+export interface BitbucketWebhookStepProps {
+  almBinding?: BitbucketBindingDefinition;
+  finished: boolean;
+  onDone: () => void;
+  onOpen: () => void;
+  open: boolean;
+  projectBinding: ProjectBitbucketBindingResponse;
+}
+
+export default function BitbucketWebhookStep(props: BitbucketWebhookStepProps) {
+  const { almBinding, finished, open, projectBinding } = props;
+  return (
+    <Step
+      finished={finished}
+      onOpen={props.onOpen}
+      open={open}
+      renderForm={() => (
+        <div className="boxed-group-inner">
+          <p className="big-spacer-bottom">
+            <FormattedMessage
+              defaultMessage={translate(
+                'onboarding.tutorial.with.jenkins.bitbucket_webhook.intro.sentence'
+              )}
+              id="onboarding.tutorial.with.jenkins.bitbucket_webhook.intro.sentence"
+              values={{
+                link: (
+                  <ButtonLink onClick={props.onDone}>
+                    {translate('onboarding.tutorial.with.jenkins.bitbucket_webhook.intro.link')}
+                  </ButtonLink>
+                )
+              }}
+            />
+          </p>
+          <ol className="list-styled">
+            <li>
+              <FormattedMessage
+                defaultMessage={translate(
+                  'onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.sentence'
+                )}
+                id="onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.sentence"
+                values={{
+                  link:
+                    almBinding !== undefined ? (
+                      <a
+                        href={`${almBinding.url.replace(
+                          /\/$/,
+                          ''
+                        )}/plugins/servlet/webhooks/projects/${projectBinding.repository}/repos/${
+                          projectBinding.slug
+                        }/create`}
+                        rel="noopener noreferrer"
+                        target="_blank">
+                        {translate('onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.link')}
+                      </a>
+                    ) : (
+                      translate('onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.link')
+                    )
+                }}
+              />
+              <ul className="list-styled">
+                <li>
+                  <LabelActionPair translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.name" />
+                </li>
+                <li className="abs-width-600">
+                  <p>
+                    <LabelActionPair translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.url" />
+                  </p>
+                  <CodeSnippet
+                    isOneLine={true}
+                    snippet={`***JENKINS_URL***/bitbucket-scmsource-hook/notify?server_url=${
+                      almBinding !== undefined ? almBinding.url : '***BITBUCKET_URL***'
+                    }`}
+                  />
+                  <Alert variant="info">
+                    {translate(
+                      'onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.url.warning'
+                    )}
+                  </Alert>
+                </li>
+              </ul>
+            </li>
+            <li>
+              <SentenceWithHighlights
+                highlightKeys={['events']}
+                translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step2"
+              />
+              <ul className="list-styled">
+                <li>
+                  <LabelActionPair translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step2.repo" />
+                </li>
+                <li>
+                  <LabelActionPair translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step2.pr" />
+                </li>
+              </ul>
+            </li>
+            <li>
+              <SentenceWithHighlights
+                highlightKeys={['create']}
+                translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step3"
+              />
+            </li>
+          </ol>
+          <Button onClick={props.onDone}>{translate('continue')}</Button>
+        </div>
+      )}
+      stepNumber={2}
+      stepTitle={translate('onboarding.tutorial.with.jenkins.bitbucket_webhook.title')}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/JenkinsTutorial.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/JenkinsTutorial.tsx
new file mode 100644 (file)
index 0000000..47d994c
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { Alert } from 'sonar-ui-common/components/ui/Alert';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+import {
+  isBitbucketBindingDefinition,
+  isProjectBitbucketBindingResponse
+} from '../../../helpers/alm-settings';
+import { AlmBindingDefinition, ProjectAlmBindingResponse } from '../../../types/alm-settings';
+import BitbucketWebhookStep from './BitbucketWebhookStep';
+import JenkinsfileStep from './JenkinsfileStep';
+import MultiBranchPipelineStep from './MultiBranchPipelineStep';
+import PreRequisitesStep from './PreRequisitesStep';
+
+export interface JenkinsTutorialProps {
+  almBinding?: AlmBindingDefinition;
+  component: T.Component;
+  projectBinding: ProjectAlmBindingResponse;
+}
+
+enum Steps {
+  PreRequisites = 0,
+  MultiBranchPipeline = 1,
+  BitbucketWebhook = 2,
+  Jenkinsfile = 3
+}
+
+export default function JenkinsTutorial(props: JenkinsTutorialProps) {
+  const { almBinding, component, projectBinding } = props;
+  const [step, setStep] = React.useState(Steps.PreRequisites);
+
+  // Failsafe; should never happen.
+  if (!isProjectBitbucketBindingResponse(projectBinding)) {
+    return (
+      <Alert variant="error">{translate('onboarding.tutorial.with.jenkins.only_bitbucket')}</Alert>
+    );
+  }
+
+  return (
+    <>
+      <div className="page-header big-spacer-bottom">
+        <h1 className="page-title">{translate('onboarding.tutorial.with.jenkins.title')}</h1>
+      </div>
+
+      <PreRequisitesStep
+        onDone={() => setStep(Steps.MultiBranchPipeline)}
+        onOpen={() => setStep(Steps.PreRequisites)}
+        open={step === Steps.PreRequisites}
+      />
+
+      <MultiBranchPipelineStep
+        finished={step > Steps.MultiBranchPipeline}
+        onDone={() => setStep(Steps.BitbucketWebhook)}
+        onOpen={() => setStep(Steps.MultiBranchPipeline)}
+        open={step === Steps.MultiBranchPipeline}
+        projectBinding={projectBinding}
+      />
+
+      <BitbucketWebhookStep
+        almBinding={almBinding && isBitbucketBindingDefinition(almBinding) ? almBinding : undefined}
+        finished={step > Steps.BitbucketWebhook}
+        onDone={() => setStep(Steps.Jenkinsfile)}
+        onOpen={() => setStep(Steps.BitbucketWebhook)}
+        open={step === Steps.BitbucketWebhook}
+        projectBinding={projectBinding}
+      />
+
+      <JenkinsfileStep component={component} open={step === Steps.Jenkinsfile} />
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/JenkinsfileStep.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/JenkinsfileStep.tsx
new file mode 100644 (file)
index 0000000..7873a2e
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 'sonar-ui-common/helpers/l10n';
+import { getBaseUrl } from 'sonar-ui-common/helpers/urls';
+import RenderOptions from '../components/RenderOptions';
+import SentenceWithHighlights from '../components/SentenceWithHighlights';
+import Step from '../components/Step';
+import Gradle from './buildtool-steps/Gradle';
+import Maven from './buildtool-steps/Maven';
+import MSBuild from './buildtool-steps/MSBuild';
+import Other from './buildtool-steps/Other';
+
+export interface JenkinsfileStepProps {
+  component: T.Component;
+  open: boolean;
+}
+
+export enum BuildTools {
+  Maven = 'maven',
+  Gradle = 'gradle',
+  MSBuild = 'msbuild',
+  Other = 'other'
+}
+
+const BUILDTOOL_COMPONENT_MAP: {
+  [x in BuildTools]: React.ComponentType<{ component: T.Component }>;
+} = {
+  [BuildTools.Maven]: Maven,
+  [BuildTools.Gradle]: Gradle,
+  [BuildTools.MSBuild]: MSBuild,
+  [BuildTools.Other]: Other
+};
+
+export default function JenkinsfileStep(props: JenkinsfileStepProps) {
+  const { component, open } = props;
+  const [buildTool, setBuildTool] = React.useState<BuildTools | undefined>(undefined);
+  return (
+    <Step
+      finished={false}
+      open={open}
+      renderForm={() => (
+        <div className="boxed-group-inner">
+          <ol className="list-styled">
+            <li>
+              {translate('onboarding.build')}
+              <RenderOptions
+                checked={buildTool}
+                name="buildtool"
+                onCheck={value => setBuildTool(value as BuildTools)}
+                optionLabelKey="onboarding.build"
+                options={Object.values(BuildTools)}
+              />
+            </li>
+            {buildTool !== undefined &&
+              React.createElement(BUILDTOOL_COMPONENT_MAP[buildTool], { component })}
+          </ol>
+          {buildTool !== undefined && (
+            <>
+              <hr className="huge-spacer-top huge-spacer-bottom" />
+              <div className="abs-width-600">
+                <p className="big-spacer-bottom">
+                  <SentenceWithHighlights
+                    highlightKeys={['all_set']}
+                    translationKey="onboarding.tutorial.with.jenkins.all_set"
+                  />
+                </p>
+                <div className="display-flex-row big-spacer-bottom">
+                  <div>
+                    <img
+                      alt="" // Should be ignored by screen readers
+                      className="big-spacer-right"
+                      width={30}
+                      src={`${getBaseUrl()}/images/tutorials/commit.svg`}
+                    />
+                  </div>
+                  <div>
+                    <p className="little-spacer-bottom">
+                      <strong>{translate('onboarding.tutorial.with.jenkins.commit')}</strong>
+                    </p>
+                    <p>{translate('onboarding.tutorial.with.jenkins.commit.why')}</p>
+                  </div>
+                </div>
+                <div className="display-flex-row huge-spacer-bottom">
+                  <div>
+                    <img
+                      alt="" // Should be ignored by screen readers
+                      className="big-spacer-right"
+                      width={30}
+                      src={`${getBaseUrl()}/images/tutorials/refresh.svg`}
+                    />
+                  </div>
+                  <div>
+                    <p className="little-spacer-bottom">
+                      <strong>{translate('onboarding.tutorial.with.jenkins.refresh')}</strong>
+                    </p>
+                    <p>{translate('onboarding.tutorial.with.jenkins.refresh.why')}</p>
+                  </div>
+                </div>
+              </div>
+            </>
+          )}
+        </div>
+      )}
+      stepNumber={3}
+      stepTitle={translate('onboarding.tutorial.with.jenkins.jenkinsfile.title')}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/MultiBranchPipelineStep.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/MultiBranchPipelineStep.tsx
new file mode 100644 (file)
index 0000000..6deaaca
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { Button } from 'sonar-ui-common/components/controls/buttons';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+import { ProjectBitbucketBindingResponse } from '../../../types/alm-settings';
+import LabelActionPair from '../components/LabelActionPair';
+import LabelValuePair from '../components/LabelValuePair';
+import SentenceWithHighlights from '../components/SentenceWithHighlights';
+import Step from '../components/Step';
+
+export interface MultiBranchPipelineStepProps {
+  finished: boolean;
+  onDone: () => void;
+  onOpen: () => void;
+  open: boolean;
+  projectBinding: ProjectBitbucketBindingResponse;
+}
+
+export default function MultiBranchPipelineStep(props: MultiBranchPipelineStepProps) {
+  const { finished, open, projectBinding } = props;
+  return (
+    <Step
+      finished={finished}
+      onOpen={props.onOpen}
+      open={open}
+      renderForm={() => (
+        <div className="boxed-group-inner">
+          <p className="big-spacer-bottom">
+            {translate('onboarding.tutorial.with.jenkins.multi_branch_pipeline.intro')}
+          </p>
+          <ol className="list-styled">
+            <li>
+              <SentenceWithHighlights
+                highlightKeys={['new_item', 'type']}
+                translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step1"
+              />
+            </li>
+            <li>
+              <SentenceWithHighlights
+                highlightKeys={['tab']}
+                translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2"
+              />
+              <ul className="list-styled">
+                <li>
+                  <LabelActionPair translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.server" />
+                </li>
+                <li>
+                  <LabelActionPair translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.creds" />
+                </li>
+                <li>
+                  <LabelValuePair
+                    translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.owner"
+                    value={projectBinding.repository}
+                  />
+                </li>
+                <li>
+                  <LabelValuePair
+                    translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.repo"
+                    value={projectBinding.slug}
+                  />
+                </li>
+                <li>
+                  <LabelActionPair translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.behaviour" />
+                </li>
+              </ul>
+            </li>
+            <li>
+              <SentenceWithHighlights
+                highlightKeys={['tab']}
+                translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step3"
+              />
+              <ul className="list-styled">
+                <li>
+                  <LabelActionPair translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step3.mode" />
+                </li>
+                <li>
+                  <LabelActionPair translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step3.script_path" />
+                </li>
+              </ul>
+            </li>
+            <li>
+              <SentenceWithHighlights
+                highlightKeys={['save']}
+                translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step4"
+              />
+            </li>
+          </ol>
+          <Button onClick={props.onDone}>{translate('continue')}</Button>
+        </div>
+      )}
+      stepNumber={1}
+      stepTitle={translate('onboarding.tutorial.with.jenkins.multi_branch_pipeline.title')}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/PreRequisitesStep.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/PreRequisitesStep.tsx
new file mode 100644 (file)
index 0000000..5dbe039
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { Button } from 'sonar-ui-common/components/controls/buttons';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+import SentenceWithHighlights from '../components/SentenceWithHighlights';
+import Step from '../components/Step';
+
+export interface PreRequisitesStepProps {
+  onDone: () => void;
+  onOpen: () => void;
+  open: boolean;
+}
+
+export default function PreRequisitesStep(props: PreRequisitesStepProps) {
+  const { open } = props;
+  return (
+    <Step
+      finished={!open}
+      onOpen={props.onOpen}
+      open={open}
+      renderForm={() => (
+        <div className="boxed-group-inner">
+          <p className="big-spacer-bottom">
+            <SentenceWithHighlights
+              highlightKeys={['must_have']}
+              translationKey="onboarding.tutorial.with.jenkins.prereqs.intro"
+            />
+          </p>
+          <ul className="list-styled big-spacer-bottom">
+            <li>{translate('onboarding.tutorial.with.jenkins.prereqs.plugins.branch_source')}</li>
+            <li>{translate('onboarding.tutorial.with.jenkins.prereqs.plugins.sonar_scanner')}</li>
+          </ul>
+          <p className="big-spacer-bottom">
+            <FormattedMessage
+              defaultMessage={translate(
+                'onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide'
+              )}
+              id="onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide"
+              values={{
+                link: (
+                  <Link target="_blank" to="/documentation/analysis/jenkins/">
+                    {translate('onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide.link')}
+                  </Link>
+                )
+              }}
+            />
+          </p>
+          <p className="big-spacer-bottom">
+            {translate('onboarding.tutorial.with.jenkins.prereqs.following_are_recommendations')}
+          </p>
+          <Button onClick={props.onDone}>
+            {translate('onboarding.tutorial.with.jenkins.prereqs.done')}
+          </Button>
+        </div>
+      )}
+      stepTitle={translate('onboarding.tutorial.with.jenkins.prereqs.title')}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/BitbucketWebhookStep-test.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/BitbucketWebhookStep-test.tsx
new file mode 100644 (file)
index 0000000..6b9ed13
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import {
+  mockBitbucketBindingDefinition,
+  mockProjectBitbucketBindingGet
+} from '../../../../helpers/mocks/alm-settings';
+import BitbucketWebhookStep, { BitbucketWebhookStepProps } from '../BitbucketWebhookStep';
+import { renderStepContent } from '../test-utils';
+
+it('should render correctly', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot('Step wrapper');
+  expect(renderStepContent(wrapper)).toMatchSnapshot('content');
+  expect(renderStepContent(wrapper.setProps({ almBinding: undefined }))).toMatchSnapshot(
+    'no alm binding'
+  );
+});
+
+function shallowRender(props: Partial<BitbucketWebhookStepProps> = {}) {
+  return shallow<BitbucketWebhookStepProps>(
+    <BitbucketWebhookStep
+      almBinding={mockBitbucketBindingDefinition()}
+      finished={false}
+      onDone={jest.fn()}
+      onOpen={jest.fn()}
+      open={true}
+      projectBinding={mockProjectBitbucketBindingGet()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-test.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-test.tsx
new file mode 100644 (file)
index 0000000..495218b
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import {
+  mockProjectAlmBindingResponse,
+  mockProjectBitbucketBindingGet
+} from '../../../../helpers/mocks/alm-settings';
+import { mockComponent } from '../../../../helpers/testMocks';
+import BitbucketWebhookStep from '../BitbucketWebhookStep';
+import JenkinsfileStep from '../JenkinsfileStep';
+import JenkinsTutorial, { JenkinsTutorialProps } from '../JenkinsTutorial';
+import MultiBranchPipelineStep from '../MultiBranchPipelineStep';
+import PreRequisitesStep from '../PreRequisitesStep';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot('default');
+  expect(shallowRender({ projectBinding: mockProjectAlmBindingResponse() })).toMatchSnapshot(
+    'not Bitbucket binding'
+  );
+});
+
+it('should correctly navigate between steps', () => {
+  const wrapper = shallowRender();
+
+  expect(wrapper.find(PreRequisitesStep).prop('open')).toBe(true);
+  expect(wrapper.find(MultiBranchPipelineStep).prop('open')).toBe(false);
+  expect(wrapper.find(BitbucketWebhookStep).prop('open')).toBe(false);
+  expect(wrapper.find(JenkinsfileStep).prop('open')).toBe(false);
+
+  // Pre-reqs done.
+  wrapper.find(PreRequisitesStep).prop('onDone')();
+  expect(wrapper.find(PreRequisitesStep).prop('open')).toBe(false);
+  expect(wrapper.find(MultiBranchPipelineStep).prop('open')).toBe(true);
+  expect(wrapper.find(BitbucketWebhookStep).prop('open')).toBe(false);
+  expect(wrapper.find(JenkinsfileStep).prop('open')).toBe(false);
+
+  // Multibranch done.
+  wrapper.find(MultiBranchPipelineStep).prop('onDone')();
+  expect(wrapper.find(PreRequisitesStep).prop('open')).toBe(false);
+  expect(wrapper.find(MultiBranchPipelineStep).prop('open')).toBe(false);
+  expect(wrapper.find(BitbucketWebhookStep).prop('open')).toBe(true);
+  expect(wrapper.find(JenkinsfileStep).prop('open')).toBe(false);
+
+  // Webhook done.
+  wrapper.find(BitbucketWebhookStep).prop('onDone')();
+  expect(wrapper.find(PreRequisitesStep).prop('open')).toBe(false);
+  expect(wrapper.find(MultiBranchPipelineStep).prop('open')).toBe(false);
+  expect(wrapper.find(BitbucketWebhookStep).prop('open')).toBe(false);
+  expect(wrapper.find(JenkinsfileStep).prop('open')).toBe(true);
+
+  // Open Pre-reqs.
+  wrapper.find(PreRequisitesStep).prop('onOpen')();
+  expect(wrapper.find(PreRequisitesStep).prop('open')).toBe(true);
+
+  // Open Multibranch.
+  wrapper.find(MultiBranchPipelineStep).prop('onOpen')();
+  expect(wrapper.find(MultiBranchPipelineStep).prop('open')).toBe(true);
+
+  // Open Webhook.
+  wrapper.find(BitbucketWebhookStep).prop('onOpen')();
+  expect(wrapper.find(BitbucketWebhookStep).prop('open')).toBe(true);
+});
+
+function shallowRender(props: Partial<JenkinsTutorialProps> = {}) {
+  return shallow<JenkinsTutorialProps>(
+    <JenkinsTutorial
+      component={mockComponent()}
+      projectBinding={mockProjectBitbucketBindingGet()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsfileStep-test.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsfileStep-test.tsx
new file mode 100644 (file)
index 0000000..d3dc2e7
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow, ShallowWrapper } from 'enzyme';
+import * as React from 'react';
+import { mockComponent } from '../../../../helpers/testMocks';
+import RenderOptions from '../../components/RenderOptions';
+import Step from '../../components/Step';
+import JenkinsfileStep, { BuildTools, JenkinsfileStepProps } from '../JenkinsfileStep';
+import { renderStepContent } from '../test-utils';
+
+it('should render correctly', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot('Step wrapper');
+  expect(renderStepContent(wrapper)).toMatchSnapshot('initial content');
+});
+
+it('should render correctly for Maven', () => {
+  const wrapper = shallowRender();
+  selectBuildTool(wrapper, BuildTools.Maven);
+  expect(
+    wrapper
+      .find(Step)
+      .props()
+      .renderForm()
+  ).toMatchSnapshot();
+});
+
+it('should render correctly for Gradle', () => {
+  const wrapper = shallowRender();
+  selectBuildTool(wrapper, BuildTools.Gradle);
+  expect(renderStepContent(wrapper)).toMatchSnapshot();
+});
+
+it('should render correctly for MSBuild', () => {
+  const wrapper = shallowRender();
+  selectBuildTool(wrapper, BuildTools.MSBuild);
+  expect(renderStepContent(wrapper)).toMatchSnapshot();
+});
+
+it('should render correctly for Other', () => {
+  const wrapper = shallowRender();
+  selectBuildTool(wrapper, BuildTools.Other);
+  expect(renderStepContent(wrapper)).toMatchSnapshot();
+});
+
+function selectBuildTool(wrapper: ShallowWrapper<JenkinsfileStepProps>, tool: BuildTools) {
+  const content = new ShallowWrapper(renderStepContent(wrapper) as JSX.Element);
+  content.find(RenderOptions).prop('onCheck')(tool);
+}
+
+function shallowRender(props: Partial<JenkinsfileStepProps> = {}) {
+  return shallow<JenkinsfileStepProps>(
+    <JenkinsfileStep component={mockComponent()} open={true} {...props} />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/MultiBranchPipelineStep-test.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/MultiBranchPipelineStep-test.tsx
new file mode 100644 (file)
index 0000000..85f5e7e
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { mockProjectBitbucketBindingGet } from '../../../../helpers/mocks/alm-settings';
+import MultiBranchPipelineStep, { MultiBranchPipelineStepProps } from '../MultiBranchPipelineStep';
+import { renderStepContent } from '../test-utils';
+
+it('should render correctly', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot('Step wrapper');
+  expect(renderStepContent(wrapper)).toMatchSnapshot('content');
+});
+
+function shallowRender(props: Partial<MultiBranchPipelineStepProps> = {}) {
+  return shallow<MultiBranchPipelineStepProps>(
+    <MultiBranchPipelineStep
+      finished={false}
+      onDone={jest.fn()}
+      onOpen={jest.fn()}
+      open={true}
+      projectBinding={mockProjectBitbucketBindingGet()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/PreRequisitesStep-test.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/PreRequisitesStep-test.tsx
new file mode 100644 (file)
index 0000000..fcfcda7
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import PreRequisitesStep, { PreRequisitesStepProps } from '../PreRequisitesStep';
+import { renderStepContent } from '../test-utils';
+
+it('should render correctly', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot('Step wrapper');
+  expect(renderStepContent(wrapper)).toMatchSnapshot('content');
+});
+
+function shallowRender(props: Partial<PreRequisitesStepProps> = {}) {
+  return shallow<PreRequisitesStepProps>(
+    <PreRequisitesStep onDone={jest.fn()} onOpen={jest.fn()} open={false} {...props} />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/BitbucketWebhookStep-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/BitbucketWebhookStep-test.tsx.snap
new file mode 100644 (file)
index 0000000..c736b26
--- /dev/null
@@ -0,0 +1,228 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: Step wrapper 1`] = `
+<Step
+  finished={false}
+  onOpen={[MockFunction]}
+  open={true}
+  renderForm={[Function]}
+  stepNumber={2}
+  stepTitle="onboarding.tutorial.with.jenkins.bitbucket_webhook.title"
+/>
+`;
+
+exports[`should render correctly: content 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <p
+    className="big-spacer-bottom"
+  >
+    <FormattedMessage
+      defaultMessage="onboarding.tutorial.with.jenkins.bitbucket_webhook.intro.sentence"
+      id="onboarding.tutorial.with.jenkins.bitbucket_webhook.intro.sentence"
+      values={
+        Object {
+          "link": <ButtonLink
+            onClick={[MockFunction]}
+          >
+            onboarding.tutorial.with.jenkins.bitbucket_webhook.intro.link
+          </ButtonLink>,
+        }
+      }
+    />
+  </p>
+  <ol
+    className="list-styled"
+  >
+    <li>
+      <FormattedMessage
+        defaultMessage="onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.sentence"
+        id="onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.sentence"
+        values={
+          Object {
+            "link": <a
+              href="http://bbs.enterprise.com/plugins/servlet/webhooks/projects/PROJECT_KEY/repos/repo-slug/create"
+              rel="noopener noreferrer"
+              target="_blank"
+            >
+              onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.link
+            </a>,
+          }
+        }
+      />
+      <ul
+        className="list-styled"
+      >
+        <li>
+          <LabelActionPair
+            translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.name"
+          />
+        </li>
+        <li
+          className="abs-width-600"
+        >
+          <p>
+            <LabelActionPair
+              translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.url"
+            />
+          </p>
+          <CodeSnippet
+            isOneLine={true}
+            snippet="***JENKINS_URL***/bitbucket-scmsource-hook/notify?server_url=http://bbs.enterprise.com"
+          />
+          <Alert
+            variant="info"
+          >
+            onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.url.warning
+          </Alert>
+        </li>
+      </ul>
+    </li>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "events",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step2"
+      />
+      <ul
+        className="list-styled"
+      >
+        <li>
+          <LabelActionPair
+            translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step2.repo"
+          />
+        </li>
+        <li>
+          <LabelActionPair
+            translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step2.pr"
+          />
+        </li>
+      </ul>
+    </li>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "create",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step3"
+      />
+    </li>
+  </ol>
+  <Button
+    onClick={[MockFunction]}
+  >
+    continue
+  </Button>
+</div>
+`;
+
+exports[`should render correctly: no alm binding 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <p
+    className="big-spacer-bottom"
+  >
+    <FormattedMessage
+      defaultMessage="onboarding.tutorial.with.jenkins.bitbucket_webhook.intro.sentence"
+      id="onboarding.tutorial.with.jenkins.bitbucket_webhook.intro.sentence"
+      values={
+        Object {
+          "link": <ButtonLink
+            onClick={[MockFunction]}
+          >
+            onboarding.tutorial.with.jenkins.bitbucket_webhook.intro.link
+          </ButtonLink>,
+        }
+      }
+    />
+  </p>
+  <ol
+    className="list-styled"
+  >
+    <li>
+      <FormattedMessage
+        defaultMessage="onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.sentence"
+        id="onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.sentence"
+        values={
+          Object {
+            "link": "onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.link",
+          }
+        }
+      />
+      <ul
+        className="list-styled"
+      >
+        <li>
+          <LabelActionPair
+            translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.name"
+          />
+        </li>
+        <li
+          className="abs-width-600"
+        >
+          <p>
+            <LabelActionPair
+              translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.url"
+            />
+          </p>
+          <CodeSnippet
+            isOneLine={true}
+            snippet="***JENKINS_URL***/bitbucket-scmsource-hook/notify?server_url=***BITBUCKET_URL***"
+          />
+          <Alert
+            variant="info"
+          >
+            onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.url.warning
+          </Alert>
+        </li>
+      </ul>
+    </li>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "events",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step2"
+      />
+      <ul
+        className="list-styled"
+      >
+        <li>
+          <LabelActionPair
+            translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step2.repo"
+          />
+        </li>
+        <li>
+          <LabelActionPair
+            translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step2.pr"
+          />
+        </li>
+      </ul>
+    </li>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "create",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.jenkins.bitbucket_webhook.step3"
+      />
+    </li>
+  </ol>
+  <Button
+    onClick={[MockFunction]}
+  >
+    continue
+  </Button>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/JenkinsTutorial-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/JenkinsTutorial-test.tsx.snap
new file mode 100644 (file)
index 0000000..7e1ac0e
--- /dev/null
@@ -0,0 +1,82 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: default 1`] = `
+<Fragment>
+  <div
+    className="page-header big-spacer-bottom"
+  >
+    <h1
+      className="page-title"
+    >
+      onboarding.tutorial.with.jenkins.title
+    </h1>
+  </div>
+  <PreRequisitesStep
+    onDone={[Function]}
+    onOpen={[Function]}
+    open={true}
+  />
+  <MultiBranchPipelineStep
+    finished={false}
+    onDone={[Function]}
+    onOpen={[Function]}
+    open={false}
+    projectBinding={
+      Object {
+        "alm": "bitbucket",
+        "key": "foo",
+        "repository": "PROJECT_KEY",
+        "slug": "repo-slug",
+      }
+    }
+  />
+  <BitbucketWebhookStep
+    finished={false}
+    onDone={[Function]}
+    onOpen={[Function]}
+    open={false}
+    projectBinding={
+      Object {
+        "alm": "bitbucket",
+        "key": "foo",
+        "repository": "PROJECT_KEY",
+        "slug": "repo-slug",
+      }
+    }
+  />
+  <JenkinsfileStep
+    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 [],
+      }
+    }
+    open={false}
+  />
+</Fragment>
+`;
+
+exports[`should render correctly: not Bitbucket binding 1`] = `
+<Alert
+  variant="error"
+>
+  onboarding.tutorial.with.jenkins.only_bitbucket
+</Alert>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/JenkinsfileStep-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/JenkinsfileStep-test.tsx.snap
new file mode 100644 (file)
index 0000000..2ef32e0
--- /dev/null
@@ -0,0 +1,526 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly for Gradle 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <ol
+    className="list-styled"
+  >
+    <li>
+      onboarding.build
+      <RenderOptions
+        checked="gradle"
+        name="buildtool"
+        onCheck={[Function]}
+        optionLabelKey="onboarding.build"
+        options={
+          Array [
+            "maven",
+            "gradle",
+            "msbuild",
+            "other",
+          ]
+        }
+      />
+    </li>
+    <Gradle
+      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 [],
+        }
+      }
+    />
+  </ol>
+  <React.Fragment>
+    <hr
+      className="huge-spacer-top huge-spacer-bottom"
+    />
+    <div
+      className="abs-width-600"
+    >
+      <p
+        className="big-spacer-bottom"
+      >
+        <SentenceWithHighlights
+          highlightKeys={
+            Array [
+              "all_set",
+            ]
+          }
+          translationKey="onboarding.tutorial.with.jenkins.all_set"
+        />
+      </p>
+      <div
+        className="display-flex-row big-spacer-bottom"
+      >
+        <div>
+          <img
+            alt=""
+            className="big-spacer-right"
+            src="/images/tutorials/commit.svg"
+            width={30}
+          />
+        </div>
+        <div>
+          <p
+            className="little-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.jenkins.commit
+            </strong>
+          </p>
+          <p>
+            onboarding.tutorial.with.jenkins.commit.why
+          </p>
+        </div>
+      </div>
+      <div
+        className="display-flex-row huge-spacer-bottom"
+      >
+        <div>
+          <img
+            alt=""
+            className="big-spacer-right"
+            src="/images/tutorials/refresh.svg"
+            width={30}
+          />
+        </div>
+        <div>
+          <p
+            className="little-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.jenkins.refresh
+            </strong>
+          </p>
+          <p>
+            onboarding.tutorial.with.jenkins.refresh.why
+          </p>
+        </div>
+      </div>
+    </div>
+  </React.Fragment>
+</div>
+`;
+
+exports[`should render correctly for MSBuild 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <ol
+    className="list-styled"
+  >
+    <li>
+      onboarding.build
+      <RenderOptions
+        checked="msbuild"
+        name="buildtool"
+        onCheck={[Function]}
+        optionLabelKey="onboarding.build"
+        options={
+          Array [
+            "maven",
+            "gradle",
+            "msbuild",
+            "other",
+          ]
+        }
+      />
+    </li>
+    <MSBuild
+      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 [],
+        }
+      }
+    />
+  </ol>
+  <React.Fragment>
+    <hr
+      className="huge-spacer-top huge-spacer-bottom"
+    />
+    <div
+      className="abs-width-600"
+    >
+      <p
+        className="big-spacer-bottom"
+      >
+        <SentenceWithHighlights
+          highlightKeys={
+            Array [
+              "all_set",
+            ]
+          }
+          translationKey="onboarding.tutorial.with.jenkins.all_set"
+        />
+      </p>
+      <div
+        className="display-flex-row big-spacer-bottom"
+      >
+        <div>
+          <img
+            alt=""
+            className="big-spacer-right"
+            src="/images/tutorials/commit.svg"
+            width={30}
+          />
+        </div>
+        <div>
+          <p
+            className="little-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.jenkins.commit
+            </strong>
+          </p>
+          <p>
+            onboarding.tutorial.with.jenkins.commit.why
+          </p>
+        </div>
+      </div>
+      <div
+        className="display-flex-row huge-spacer-bottom"
+      >
+        <div>
+          <img
+            alt=""
+            className="big-spacer-right"
+            src="/images/tutorials/refresh.svg"
+            width={30}
+          />
+        </div>
+        <div>
+          <p
+            className="little-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.jenkins.refresh
+            </strong>
+          </p>
+          <p>
+            onboarding.tutorial.with.jenkins.refresh.why
+          </p>
+        </div>
+      </div>
+    </div>
+  </React.Fragment>
+</div>
+`;
+
+exports[`should render correctly for Maven 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <ol
+    className="list-styled"
+  >
+    <li>
+      onboarding.build
+      <RenderOptions
+        checked="maven"
+        name="buildtool"
+        onCheck={[Function]}
+        optionLabelKey="onboarding.build"
+        options={
+          Array [
+            "maven",
+            "gradle",
+            "msbuild",
+            "other",
+          ]
+        }
+      />
+    </li>
+    <Maven
+      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 [],
+        }
+      }
+    />
+  </ol>
+  <React.Fragment>
+    <hr
+      className="huge-spacer-top huge-spacer-bottom"
+    />
+    <div
+      className="abs-width-600"
+    >
+      <p
+        className="big-spacer-bottom"
+      >
+        <SentenceWithHighlights
+          highlightKeys={
+            Array [
+              "all_set",
+            ]
+          }
+          translationKey="onboarding.tutorial.with.jenkins.all_set"
+        />
+      </p>
+      <div
+        className="display-flex-row big-spacer-bottom"
+      >
+        <div>
+          <img
+            alt=""
+            className="big-spacer-right"
+            src="/images/tutorials/commit.svg"
+            width={30}
+          />
+        </div>
+        <div>
+          <p
+            className="little-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.jenkins.commit
+            </strong>
+          </p>
+          <p>
+            onboarding.tutorial.with.jenkins.commit.why
+          </p>
+        </div>
+      </div>
+      <div
+        className="display-flex-row huge-spacer-bottom"
+      >
+        <div>
+          <img
+            alt=""
+            className="big-spacer-right"
+            src="/images/tutorials/refresh.svg"
+            width={30}
+          />
+        </div>
+        <div>
+          <p
+            className="little-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.jenkins.refresh
+            </strong>
+          </p>
+          <p>
+            onboarding.tutorial.with.jenkins.refresh.why
+          </p>
+        </div>
+      </div>
+    </div>
+  </React.Fragment>
+</div>
+`;
+
+exports[`should render correctly for Other 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <ol
+    className="list-styled"
+  >
+    <li>
+      onboarding.build
+      <RenderOptions
+        checked="other"
+        name="buildtool"
+        onCheck={[Function]}
+        optionLabelKey="onboarding.build"
+        options={
+          Array [
+            "maven",
+            "gradle",
+            "msbuild",
+            "other",
+          ]
+        }
+      />
+    </li>
+    <Other
+      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 [],
+        }
+      }
+    />
+  </ol>
+  <React.Fragment>
+    <hr
+      className="huge-spacer-top huge-spacer-bottom"
+    />
+    <div
+      className="abs-width-600"
+    >
+      <p
+        className="big-spacer-bottom"
+      >
+        <SentenceWithHighlights
+          highlightKeys={
+            Array [
+              "all_set",
+            ]
+          }
+          translationKey="onboarding.tutorial.with.jenkins.all_set"
+        />
+      </p>
+      <div
+        className="display-flex-row big-spacer-bottom"
+      >
+        <div>
+          <img
+            alt=""
+            className="big-spacer-right"
+            src="/images/tutorials/commit.svg"
+            width={30}
+          />
+        </div>
+        <div>
+          <p
+            className="little-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.jenkins.commit
+            </strong>
+          </p>
+          <p>
+            onboarding.tutorial.with.jenkins.commit.why
+          </p>
+        </div>
+      </div>
+      <div
+        className="display-flex-row huge-spacer-bottom"
+      >
+        <div>
+          <img
+            alt=""
+            className="big-spacer-right"
+            src="/images/tutorials/refresh.svg"
+            width={30}
+          />
+        </div>
+        <div>
+          <p
+            className="little-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.jenkins.refresh
+            </strong>
+          </p>
+          <p>
+            onboarding.tutorial.with.jenkins.refresh.why
+          </p>
+        </div>
+      </div>
+    </div>
+  </React.Fragment>
+</div>
+`;
+
+exports[`should render correctly: Step wrapper 1`] = `
+<Step
+  finished={false}
+  open={true}
+  renderForm={[Function]}
+  stepNumber={3}
+  stepTitle="onboarding.tutorial.with.jenkins.jenkinsfile.title"
+/>
+`;
+
+exports[`should render correctly: initial content 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <ol
+    className="list-styled"
+  >
+    <li>
+      onboarding.build
+      <RenderOptions
+        name="buildtool"
+        onCheck={[Function]}
+        optionLabelKey="onboarding.build"
+        options={
+          Array [
+            "maven",
+            "gradle",
+            "msbuild",
+            "other",
+          ]
+        }
+      />
+    </li>
+  </ol>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/MultiBranchPipelineStep-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/MultiBranchPipelineStep-test.tsx.snap
new file mode 100644 (file)
index 0000000..ba91752
--- /dev/null
@@ -0,0 +1,119 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: Step wrapper 1`] = `
+<Step
+  finished={false}
+  onOpen={[MockFunction]}
+  open={true}
+  renderForm={[Function]}
+  stepNumber={1}
+  stepTitle="onboarding.tutorial.with.jenkins.multi_branch_pipeline.title"
+/>
+`;
+
+exports[`should render correctly: content 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <p
+    className="big-spacer-bottom"
+  >
+    onboarding.tutorial.with.jenkins.multi_branch_pipeline.intro
+  </p>
+  <ol
+    className="list-styled"
+  >
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "new_item",
+            "type",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step1"
+      />
+    </li>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "tab",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2"
+      />
+      <ul
+        className="list-styled"
+      >
+        <li>
+          <LabelActionPair
+            translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.server"
+          />
+        </li>
+        <li>
+          <LabelActionPair
+            translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.creds"
+          />
+        </li>
+        <li>
+          <LabelValuePair
+            translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.owner"
+            value="PROJECT_KEY"
+          />
+        </li>
+        <li>
+          <LabelValuePair
+            translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.repo"
+            value="repo-slug"
+          />
+        </li>
+        <li>
+          <LabelActionPair
+            translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.behaviour"
+          />
+        </li>
+      </ul>
+    </li>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "tab",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step3"
+      />
+      <ul
+        className="list-styled"
+      >
+        <li>
+          <LabelActionPair
+            translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step3.mode"
+          />
+        </li>
+        <li>
+          <LabelActionPair
+            translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step3.script_path"
+          />
+        </li>
+      </ul>
+    </li>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "save",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.jenkins.multi_branch_pipeline.step4"
+      />
+    </li>
+  </ol>
+  <Button
+    onClick={[MockFunction]}
+  >
+    continue
+  </Button>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/PreRequisitesStep-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/PreRequisitesStep-test.tsx.snap
new file mode 100644 (file)
index 0000000..01eb4af
--- /dev/null
@@ -0,0 +1,70 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: Step wrapper 1`] = `
+<Step
+  finished={true}
+  onOpen={[MockFunction]}
+  open={false}
+  renderForm={[Function]}
+  stepTitle="onboarding.tutorial.with.jenkins.prereqs.title"
+/>
+`;
+
+exports[`should render correctly: content 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <p
+    className="big-spacer-bottom"
+  >
+    <SentenceWithHighlights
+      highlightKeys={
+        Array [
+          "must_have",
+        ]
+      }
+      translationKey="onboarding.tutorial.with.jenkins.prereqs.intro"
+    />
+  </p>
+  <ul
+    className="list-styled big-spacer-bottom"
+  >
+    <li>
+      onboarding.tutorial.with.jenkins.prereqs.plugins.branch_source
+    </li>
+    <li>
+      onboarding.tutorial.with.jenkins.prereqs.plugins.sonar_scanner
+    </li>
+  </ul>
+  <p
+    className="big-spacer-bottom"
+  >
+    <FormattedMessage
+      defaultMessage="onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide"
+      id="onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide"
+      values={
+        Object {
+          "link": <Link
+            onlyActiveOnIndex={false}
+            style={Object {}}
+            target="_blank"
+            to="/documentation/analysis/jenkins/"
+          >
+            onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide.link
+          </Link>,
+        }
+      }
+    />
+  </p>
+  <p
+    className="big-spacer-bottom"
+  >
+    onboarding.tutorial.with.jenkins.prereqs.following_are_recommendations
+  </p>
+  <Button
+    onClick={[MockFunction]}
+  >
+    onboarding.tutorial.with.jenkins.prereqs.done
+  </Button>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/CreateJenkinsfileBulletPoint.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/CreateJenkinsfileBulletPoint.tsx
new file mode 100644 (file)
index 0000000..0afb84c
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
+import { Alert } from 'sonar-ui-common/components/ui/Alert';
+import CodeSnippet from '../../../common/CodeSnippet';
+import SentenceWithFilename from '../../components/SentenceWithFilename';
+import SentenceWithHighlights from '../../components/SentenceWithHighlights';
+
+export interface CreateJenkinsfileBulletPointProps {
+  snippet: string;
+  alertTranslationKeyPart?: string;
+}
+
+export default function CreateJenkinsfileBulletPoint(props: CreateJenkinsfileBulletPointProps) {
+  const { snippet, alertTranslationKeyPart } = props;
+
+  return (
+    <li className="abs-width-600">
+      <SentenceWithFilename
+        filename="Jenkinsfile"
+        translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.jenkinsfile_step"
+      />
+      {alertTranslationKeyPart !== undefined && (
+        <Alert className="spacer-top" variant="info">
+          <p className="text-middle">
+            <SentenceWithHighlights
+              highlightKeys={['default', 'in_jenkins']}
+              translationKey={`${alertTranslationKeyPart}.replace`}
+            />
+            <HelpTooltip
+              className="little-spacer-left"
+              overlay={
+                <>
+                  <p className="spacer-bottom">
+                    <SentenceWithHighlights
+                      highlightKeys={['path']}
+                      translationKey={`${alertTranslationKeyPart}.help1`}
+                    />
+                  </p>
+                  <p>
+                    <SentenceWithHighlights
+                      highlightKeys={['path', 'name']}
+                      translationKey={`${alertTranslationKeyPart}.help2`}
+                    />
+                  </p>
+                </>
+              }
+            />
+          </p>
+        </Alert>
+      )}
+      <CodeSnippet snippet={snippet} />
+    </li>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/Gradle.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/Gradle.tsx
new file mode 100644 (file)
index 0000000..acf803a
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import CodeSnippet from '../../../common/CodeSnippet';
+import SentenceWithFilename from '../../components/SentenceWithFilename';
+import CreateJenkinsfileBulletPoint from './CreateJenkinsfileBulletPoint';
+
+export interface GradleProps {
+  component: T.Component;
+}
+
+const buildGradleSnippet = (key: string) => `plugins {
+  id "org.sonarqube" version "2.7"
+}
+
+sonarqube {
+  properties {
+    property "sonar.projectKey", "${key}"
+  }
+}`;
+
+const JENKINSFILE_SNIPPET = `node {
+  stage('SCM') {
+    checkout scm
+  }
+  stage('SonarQube Analysis') {
+    withSonarQubeEnv() {
+      sh "./gradlew sonarqube"
+    }
+  }
+}`;
+
+export default function Gradle({ component }: GradleProps) {
+  return (
+    <>
+      <li className="abs-width-600">
+        <SentenceWithFilename
+          filename="build.gradle"
+          translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.gradle.step2"
+        />
+        <CodeSnippet snippet={buildGradleSnippet(component.key)} />
+      </li>
+      <CreateJenkinsfileBulletPoint snippet={JENKINSFILE_SNIPPET} />
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/MSBuild.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/MSBuild.tsx
new file mode 100644 (file)
index 0000000..3200d3e
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
+import { Alert } from 'sonar-ui-common/components/ui/Alert';
+import CodeSnippet from '../../../common/CodeSnippet';
+import SentenceWithFilename from '../../components/SentenceWithFilename';
+import SentenceWithHighlights from '../../components/SentenceWithHighlights';
+
+export interface MSBuildProps {
+  component: T.Component;
+}
+
+const jenkinsfileSnippet = (key: string) => `node {
+  stage('SCM') {
+    checkout scm
+  }
+  stage('SonarQube Analysis') {
+    def msbuildHome = tool 'Default MSBuild'
+    def scannerHome = tool 'SonarScanner for MSBuild'
+    withSonarQubeEnv() {
+      bat "\\"\${scannerHome}\\\\SonarScanner.MSBuild.exe\\" begin /k:\\"${key}\\""
+      bat "\\"\${msbuildHome}\\\\MSBuild.exe\\" /t:Rebuild"
+      bat "\\"\${scannerHome}\\\\SonarScanner.MSBuild.exe\\" end"
+    }
+  }
+}
+`;
+
+export default function MSBuild({ component }: MSBuildProps) {
+  return (
+    <li className="abs-width-600">
+      <SentenceWithFilename
+        filename="Jenkinsfile"
+        translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.jenkinsfile_step"
+      />
+      <Alert className="spacer-top" variant="info">
+        <p className="text-middle">
+          <SentenceWithHighlights
+            highlightKeys={['default_msbuild', 'default_scanner', 'in_jenkins']}
+            translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.replace"
+          />
+          <HelpTooltip
+            className="little-spacer-left"
+            overlay={
+              <>
+                <p className="spacer-bottom">
+                  <SentenceWithHighlights
+                    highlightKeys={['path']}
+                    translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.help1"
+                  />
+                </p>
+                <p className="spacer-bottom">
+                  <SentenceWithHighlights
+                    highlightKeys={['path', 'name']}
+                    translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.help2"
+                  />
+                </p>
+                <p>
+                  <SentenceWithHighlights
+                    highlightKeys={['path', 'name']}
+                    translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.help3"
+                  />
+                </p>
+              </>
+            }
+          />
+        </p>
+      </Alert>
+      <CodeSnippet snippet={jenkinsfileSnippet(component.key)} />
+    </li>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/Maven.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/Maven.tsx
new file mode 100644 (file)
index 0000000..98a018d
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import CodeSnippet from '../../../common/CodeSnippet';
+import SentenceWithFilename from '../../components/SentenceWithFilename';
+import CreateJenkinsfileBulletPoint from './CreateJenkinsfileBulletPoint';
+
+export interface MavenProps {
+  component: T.Component;
+}
+
+const pomSnippet = (key: string) => `<properties>
+  <sonar.projectKey>${key}</sonar.projectKey>
+</properties>`;
+
+const JENKINSFILE_SNIPPET = `node {
+  stage('SCM') {
+    checkout scm
+  }
+  stage('SonarQube Analysis') {
+    def mvn = tool 'Default Maven';
+    withSonarQubeEnv() {
+      sh "\${mvn}/bin/mvn sonar:sonar"
+    }
+  }
+}`;
+
+export default function Maven({ component }: MavenProps) {
+  return (
+    <>
+      <li className="abs-width-600">
+        <SentenceWithFilename
+          filename="pom.xml"
+          translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.maven.step2"
+        />
+        <CodeSnippet snippet={pomSnippet(component.key)} />
+      </li>
+      <CreateJenkinsfileBulletPoint
+        alertTranslationKeyPart="onboarding.tutorial.with.jenkins.jenkinsfile.maven.step3"
+        snippet={JENKINSFILE_SNIPPET}
+      />
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/Other.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/Other.tsx
new file mode 100644 (file)
index 0000000..aa4f207
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import CodeSnippet from '../../../common/CodeSnippet';
+import SentenceWithFilename from '../../components/SentenceWithFilename';
+import CreateJenkinsfileBulletPoint from './CreateJenkinsfileBulletPoint';
+
+export interface OtherProps {
+  component: T.Component;
+}
+
+const sonarProjectSnippet = (key: string) => `sonar.projectKey=${key}`;
+
+const JENKINSFILE_SNIPPET = `node {
+  stage('SCM') {
+    checkout scm
+  }
+  stage('SonarQube Analysis') {
+    def scannerHome = tool 'SonarScanner';
+    withSonarQubeEnv() {
+      sh "\${scannerHome}/bin/sonar-scanner"
+    }
+  }
+}`;
+
+export default function Other({ component }: OtherProps) {
+  return (
+    <>
+      <li className="abs-width-600">
+        <SentenceWithFilename
+          filename="sonar-project.properties"
+          translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.other.step2"
+        />
+        <CodeSnippet snippet={sonarProjectSnippet(component.key)} />
+      </li>
+      <CreateJenkinsfileBulletPoint
+        alertTranslationKeyPart="onboarding.tutorial.with.jenkins.jenkinsfile.other.step3"
+        snippet={JENKINSFILE_SNIPPET}
+      />
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/CreateJenkinsfileBulletPoint-test.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/CreateJenkinsfileBulletPoint-test.tsx
new file mode 100644 (file)
index 0000000..69fc5ba
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import CreateJenkinsfileBulletPoint, {
+  CreateJenkinsfileBulletPointProps
+} from '../CreateJenkinsfileBulletPoint';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot('default');
+  expect(shallowRender({ alertTranslationKeyPart: 'baz' })).toMatchSnapshot('with alert');
+});
+
+function shallowRender(props: Partial<CreateJenkinsfileBulletPointProps> = {}) {
+  return shallow<CreateJenkinsfileBulletPointProps>(
+    <CreateJenkinsfileBulletPoint snippet="foo { bar() }" {...props} />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/Gradle-test.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/Gradle-test.tsx
new file mode 100644 (file)
index 0000000..5bc3adf
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { mockComponent } from '../../../../../helpers/testMocks';
+import Gradle, { GradleProps } from '../Gradle';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<GradleProps> = {}) {
+  return shallow<GradleProps>(<Gradle component={mockComponent()} {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/MSBuild-test.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/MSBuild-test.tsx
new file mode 100644 (file)
index 0000000..a391815
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { mockComponent } from '../../../../../helpers/testMocks';
+import MSBuild, { MSBuildProps } from '../MSBuild';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<MSBuildProps> = {}) {
+  return shallow<MSBuildProps>(<MSBuild component={mockComponent()} {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/Maven-test.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/Maven-test.tsx
new file mode 100644 (file)
index 0000000..13632ee
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { mockComponent } from '../../../../../helpers/testMocks';
+import Maven, { MavenProps } from '../Maven';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<MavenProps> = {}) {
+  return shallow<MavenProps>(<Maven component={mockComponent()} {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/Other-test.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/Other-test.tsx
new file mode 100644 (file)
index 0000000..c9c5b30
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { mockComponent } from '../../../../../helpers/testMocks';
+import Other, { OtherProps } from '../Other';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<OtherProps> = {}) {
+  return shallow<OtherProps>(<Other component={mockComponent()} {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/CreateJenkinsfileBulletPoint-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/CreateJenkinsfileBulletPoint-test.tsx.snap
new file mode 100644 (file)
index 0000000..b56d3e4
--- /dev/null
@@ -0,0 +1,77 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: default 1`] = `
+<li
+  className="abs-width-600"
+>
+  <SentenceWithFilename
+    filename="Jenkinsfile"
+    translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.jenkinsfile_step"
+  />
+  <CodeSnippet
+    snippet="foo { bar() }"
+  />
+</li>
+`;
+
+exports[`should render correctly: with alert 1`] = `
+<li
+  className="abs-width-600"
+>
+  <SentenceWithFilename
+    filename="Jenkinsfile"
+    translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.jenkinsfile_step"
+  />
+  <Alert
+    className="spacer-top"
+    variant="info"
+  >
+    <p
+      className="text-middle"
+    >
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "default",
+            "in_jenkins",
+          ]
+        }
+        translationKey="baz.replace"
+      />
+      <HelpTooltip
+        className="little-spacer-left"
+        overlay={
+          <React.Fragment>
+            <p
+              className="spacer-bottom"
+            >
+              <SentenceWithHighlights
+                highlightKeys={
+                  Array [
+                    "path",
+                  ]
+                }
+                translationKey="baz.help1"
+              />
+            </p>
+            <p>
+              <SentenceWithHighlights
+                highlightKeys={
+                  Array [
+                    "path",
+                    "name",
+                  ]
+                }
+                translationKey="baz.help2"
+              />
+            </p>
+          </React.Fragment>
+        }
+      />
+    </p>
+  </Alert>
+  <CodeSnippet
+    snippet="foo { bar() }"
+  />
+</li>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/Gradle-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/Gradle-test.tsx.snap
new file mode 100644 (file)
index 0000000..db1fcec
--- /dev/null
@@ -0,0 +1,37 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <li
+    className="abs-width-600"
+  >
+    <SentenceWithFilename
+      filename="build.gradle"
+      translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.gradle.step2"
+    />
+    <CodeSnippet
+      snippet="plugins {
+  id \\"org.sonarqube\\" version \\"2.7\\"
+}
+
+sonarqube {
+  properties {
+    property \\"sonar.projectKey\\", \\"my-project\\"
+  }
+}"
+    />
+  </li>
+  <CreateJenkinsfileBulletPoint
+    snippet="node {
+  stage('SCM') {
+    checkout scm
+  }
+  stage('SonarQube Analysis') {
+    withSonarQubeEnv() {
+      sh \\"./gradlew sonarqube\\"
+    }
+  }
+}"
+  />
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/MSBuild-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/MSBuild-test.tsx.snap
new file mode 100644 (file)
index 0000000..822c34c
--- /dev/null
@@ -0,0 +1,91 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<li
+  className="abs-width-600"
+>
+  <SentenceWithFilename
+    filename="Jenkinsfile"
+    translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.jenkinsfile_step"
+  />
+  <Alert
+    className="spacer-top"
+    variant="info"
+  >
+    <p
+      className="text-middle"
+    >
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "default_msbuild",
+            "default_scanner",
+            "in_jenkins",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.replace"
+      />
+      <HelpTooltip
+        className="little-spacer-left"
+        overlay={
+          <React.Fragment>
+            <p
+              className="spacer-bottom"
+            >
+              <SentenceWithHighlights
+                highlightKeys={
+                  Array [
+                    "path",
+                  ]
+                }
+                translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.help1"
+              />
+            </p>
+            <p
+              className="spacer-bottom"
+            >
+              <SentenceWithHighlights
+                highlightKeys={
+                  Array [
+                    "path",
+                    "name",
+                  ]
+                }
+                translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.help2"
+              />
+            </p>
+            <p>
+              <SentenceWithHighlights
+                highlightKeys={
+                  Array [
+                    "path",
+                    "name",
+                  ]
+                }
+                translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.help3"
+              />
+            </p>
+          </React.Fragment>
+        }
+      />
+    </p>
+  </Alert>
+  <CodeSnippet
+    snippet="node {
+  stage('SCM') {
+    checkout scm
+  }
+  stage('SonarQube Analysis') {
+    def msbuildHome = tool 'Default MSBuild'
+    def scannerHome = tool 'SonarScanner for MSBuild'
+    withSonarQubeEnv() {
+      bat \\"\\\\\\"\${scannerHome}\\\\\\\\SonarScanner.MSBuild.exe\\\\\\" begin /k:\\\\\\"my-project\\\\\\"\\"
+      bat \\"\\\\\\"\${msbuildHome}\\\\\\\\MSBuild.exe\\\\\\" /t:Rebuild\\"
+      bat \\"\\\\\\"\${scannerHome}\\\\\\\\SonarScanner.MSBuild.exe\\\\\\" end\\"
+    }
+  }
+}
+"
+  />
+</li>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/Maven-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/Maven-test.tsx.snap
new file mode 100644 (file)
index 0000000..a619e3b
--- /dev/null
@@ -0,0 +1,33 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <li
+    className="abs-width-600"
+  >
+    <SentenceWithFilename
+      filename="pom.xml"
+      translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.maven.step2"
+    />
+    <CodeSnippet
+      snippet="<properties>
+  <sonar.projectKey>my-project</sonar.projectKey>
+</properties>"
+    />
+  </li>
+  <CreateJenkinsfileBulletPoint
+    alertTranslationKeyPart="onboarding.tutorial.with.jenkins.jenkinsfile.maven.step3"
+    snippet="node {
+  stage('SCM') {
+    checkout scm
+  }
+  stage('SonarQube Analysis') {
+    def mvn = tool 'Default Maven';
+    withSonarQubeEnv() {
+      sh \\"\${mvn}/bin/mvn sonar:sonar\\"
+    }
+  }
+}"
+  />
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/Other-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/__tests__/__snapshots__/Other-test.tsx.snap
new file mode 100644 (file)
index 0000000..c5a5493
--- /dev/null
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <li
+    className="abs-width-600"
+  >
+    <SentenceWithFilename
+      filename="sonar-project.properties"
+      translationKey="onboarding.tutorial.with.jenkins.jenkinsfile.other.step2"
+    />
+    <CodeSnippet
+      snippet="sonar.projectKey=my-project"
+    />
+  </li>
+  <CreateJenkinsfileBulletPoint
+    alertTranslationKeyPart="onboarding.tutorial.with.jenkins.jenkinsfile.other.step3"
+    snippet="node {
+  stage('SCM') {
+    checkout scm
+  }
+  stage('SonarQube Analysis') {
+    def scannerHome = tool 'SonarScanner';
+    withSonarQubeEnv() {
+      sh \\"\${scannerHome}/bin/sonar-scanner\\"
+    }
+  }
+}"
+  />
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/test-utils.ts b/server/sonar-web/src/main/js/components/tutorials/jenkins/test-utils.ts
new file mode 100644 (file)
index 0000000..572caf9
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { ShallowWrapper } from 'enzyme';
+import Step from '../components/Step';
+
+export function renderStepContent(wrapper: ShallowWrapper<React.ReactNode>) {
+  return wrapper
+    .find(Step)
+    .props()
+    .renderForm();
+}
index f1b4c1dcee3cacbd0439c8d113ad9291658c137f..1714244e00e7ef229eaa5046970067e04d64ef39 100644 (file)
@@ -18,8 +18,6 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import Tooltip from 'sonar-ui-common/components/controls/Tooltip';
-import BackIcon from 'sonar-ui-common/components/icons/BackIcon';
 import { translate } from 'sonar-ui-common/helpers/l10n';
 import { isVSTS } from '../../../helpers/almIntegrations';
 import InstanceMessage from '../../common/InstanceMessage';
@@ -34,7 +32,6 @@ export enum Steps {
 interface Props {
   component: T.Component;
   currentUser: T.LoggedInUser;
-  onBack?: () => void;
 }
 
 interface State {
@@ -61,19 +58,7 @@ export default class ManualTutorial extends React.PureComponent<Props, State> {
     return (
       <>
         <div className="page-header big-spacer-bottom">
-          <h1 className="page-title">
-            {this.props.onBack !== undefined && (
-              <Tooltip overlay={translate('onboarding.tutorial.return_to_list')}>
-                <a
-                  aria-label={translate('onboarding.tutorial.return_to_list')}
-                  className="link-no-underline big-spacer-right"
-                  onClick={this.props.onBack}>
-                  <BackIcon />
-                </a>
-              </Tooltip>
-            )}
-            {translate('onboarding.project_analysis.header')}
-          </h1>
+          <h1 className="page-title">{translate('onboarding.project_analysis.header')}</h1>
           <p className="page-description">
             <InstanceMessage message={translate('onboarding.project_analysis.description')} />
           </p>
index 79a269a35c484d127ada234f20a2a5a67038fea6..188227b8db952a3c327a75fccc6e5b97b1729f7e 100644 (file)
@@ -26,7 +26,6 @@ import TokenStep from '../TokenStep';
 
 it('renders correctly', () => {
   expect(shallowRender()).toMatchSnapshot('default');
-  expect(shallowRender({ onBack: jest.fn() })).toMatchSnapshot('with back button');
 });
 
 it('allows to navigate between steps', () => {
index cc1cf2fc27ae0ad7f5a95a1b34009f452b26d7cd..77e51dfb0646c952b4e58befd83109135dfdae22 100644 (file)
@@ -65,80 +65,3 @@ exports[`renders correctly: default 1`] = `
   />
 </Fragment>
 `;
-
-exports[`renders correctly: with back button 1`] = `
-<Fragment>
-  <div
-    className="page-header big-spacer-bottom"
-  >
-    <h1
-      className="page-title"
-    >
-      <Tooltip
-        overlay="onboarding.tutorial.return_to_list"
-      >
-        <a
-          aria-label="onboarding.tutorial.return_to_list"
-          className="link-no-underline big-spacer-right"
-          onClick={[MockFunction]}
-        >
-          <BackIcon />
-        </a>
-      </Tooltip>
-      onboarding.project_analysis.header
-    </h1>
-    <p
-      className="page-description"
-    >
-      <InstanceMessage
-        message="onboarding.project_analysis.description"
-      />
-    </p>
-  </div>
-  <TokenStep
-    currentUser={
-      Object {
-        "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 {
-        "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}
-  />
-</Fragment>
-`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/styles.css b/server/sonar-web/src/main/js/components/tutorials/styles.css
new file mode 100644 (file)
index 0000000..295fb42
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+.tutorial-selection {
+  margin: 0 auto;
+  max-width: 500px;
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/types.ts b/server/sonar-web/src/main/js/components/tutorials/types.ts
new file mode 100644 (file)
index 0000000..2ad9390
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 enum TutorialModes {
+  Manual = 'manual',
+  Jenkins = 'jenkins'
+}
+
+export interface LanguageConfig {
+  language?: string;
+  javaBuild?: string;
+  cFamilyCompiler?: string;
+  os?: string;
+  projectKey?: string;
+}
index a1a28edc7120980498f6d5ad291819148c69439e..89394ecb80b0b4a45456e5b9a69527062d148394 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-export interface LanguageConfig {
-  language?: string;
-  javaBuild?: string;
-  cFamilyCompiler?: string;
-  os?: string;
-  projectKey?: string;
-}
+import { LanguageConfig } from './types';
 
 export function isLanguageConfigured(config?: LanguageConfig) {
   if (!config) {
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/alm-settings.ts b/server/sonar-web/src/main/js/helpers/__tests__/alm-settings.ts
new file mode 100644 (file)
index 0000000..f6be966
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 { isBitbucketBindingDefinition, isProjectBitbucketBindingResponse } from '../alm-settings';
+import {
+  mockBitbucketBindingDefinition,
+  mockGithubBindingDefinition,
+  mockProjectAlmBindingResponse,
+  mockProjectBitbucketBindingGet
+} from '../mocks/alm-settings';
+
+describe('isProjectBitbucketBindingResponse', () => {
+  it('works as expected', () => {
+    expect(isProjectBitbucketBindingResponse(mockProjectAlmBindingResponse())).toBe(false);
+    expect(isProjectBitbucketBindingResponse(mockProjectBitbucketBindingGet())).toBe(true);
+  });
+});
+
+describe('isBitbucketBindingDefinition', () => {
+  it('works as expected', () => {
+    expect(isBitbucketBindingDefinition(mockGithubBindingDefinition())).toBe(false);
+    expect(isBitbucketBindingDefinition(mockBitbucketBindingDefinition())).toBe(true);
+  });
+});
diff --git a/server/sonar-web/src/main/js/helpers/alm-settings.ts b/server/sonar-web/src/main/js/helpers/alm-settings.ts
new file mode 100644 (file)
index 0000000..0bb5082
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 {
+  AlmBindingDefinition,
+  AlmKeys,
+  BitbucketBindingDefinition,
+  ProjectAlmBindingResponse,
+  ProjectBitbucketBindingResponse
+} from '../types/alm-settings';
+
+export function isProjectBitbucketBindingResponse(
+  binding: ProjectAlmBindingResponse
+): binding is ProjectBitbucketBindingResponse {
+  return binding.alm === AlmKeys.Bitbucket;
+}
+
+export function isBitbucketBindingDefinition(
+  binding: AlmBindingDefinition & { url?: string; personalAccessToken?: string }
+): binding is BitbucketBindingDefinition {
+  return binding.url !== undefined && binding.personalAccessToken !== undefined;
+}
index 2eab0248c9d729af0f35eda95c1e460504256323..2a495addc9d2ab1553d242feba4e5f19b8f7fd36 100644 (file)
@@ -23,7 +23,9 @@ import {
   AzureBindingDefinition,
   BitbucketBindingDefinition,
   GithubBindingDefinition,
-  GitlabBindingDefinition
+  GitlabBindingDefinition,
+  ProjectAlmBindingResponse,
+  ProjectBitbucketBindingResponse
 } from '../../types/alm-settings';
 
 export function mockAlmSettingsInstance(
@@ -78,3 +80,25 @@ export function mockGitlabBindingDefinition(
     ...overrides
   };
 }
+
+export function mockProjectAlmBindingResponse(
+  overrides: Partial<ProjectAlmBindingResponse> = {}
+): ProjectAlmBindingResponse {
+  return {
+    alm: AlmKeys.GitHub,
+    key: 'foo',
+    ...overrides
+  };
+}
+
+export function mockProjectBitbucketBindingGet(
+  overrides: Partial<ProjectBitbucketBindingResponse> = {}
+): ProjectBitbucketBindingResponse {
+  return {
+    alm: AlmKeys.Bitbucket,
+    key: 'foo',
+    repository: 'PROJECT_KEY',
+    slug: 'repo-slug',
+    ...overrides
+  };
+}
index 1fbff623de29b3dd7b6e389ddcd232e5b96c02f4..534471f4741cdc9a2ee3fcb37dddbbe0848f5357 100644 (file)
@@ -56,6 +56,12 @@ export interface ProjectAlmBindingResponse {
   summaryCommentEnabled?: boolean;
 }
 
+export interface ProjectBitbucketBindingResponse extends ProjectAlmBindingResponse {
+  alm: AlmKeys.Bitbucket;
+  repository: string;
+  slug: string;
+}
+
 export interface ProjectAlmBindingParams {
   almSetting: string;
   project: string;
index d111d22a497147c5dce2113e671c1b98431bdd96..d13c038a540097b5713824441b4adf155d9bc60e 100644 (file)
@@ -3180,6 +3180,7 @@ onboarding.build=What is your build technology?
 onboarding.build.maven=Maven
 onboarding.build.gradle=Gradle
 onboarding.build.make=Make
+onboarding.build.msbuild=MSBuild
 onboarding.build.other=Other (for JS, TS, Go, Python, PHP, ...)
 
 onboarding.language=What is your project's main language?
@@ -3252,6 +3253,104 @@ onboarding.analysis.sqscanner.docs.gradle.example_project.title=live Gradle-base
 
 onboarding.tutorial.return_to_list=Choose another option
 
+onboarding.tutorial.choose_method=How do you want to analyze your repository?
+onboarding.tutorial.choose_method.manual=Manually
+onboarding.tutorial.choose_method.jenkins=With Jenkins
+
+onboarding.tutorial.with.jenkins.title=Analyze your project with Jenkins
+onboarding.tutorial.with.jenkins.only_bitbucket=This tutorial is only available for projects bound to Bitbucket Server.
+onboarding.tutorial.with.jenkins.prereqs.title=Prerequisites
+onboarding.tutorial.with.jenkins.prereqs.intro.sentence=To run your project analyses with Jenkins, the following plugins {must_have}
+onboarding.tutorial.with.jenkins.prereqs.intro.sentence.must_have=must be installed and configured:
+onboarding.tutorial.with.jenkins.prereqs.plugins.branch_source=Bitbucket Branch Source plugin for Jenkins - version 2.7 or later
+onboarding.tutorial.with.jenkins.prereqs.plugins.sonar_scanner=SonarQube Scanner plugin for Jenkins - version 2.11 or later
+onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide=For a step by step guide on installing and configuring those plugins in Jenkins, visit the {link} documentation page.
+onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide.link=Analysis Prerequisites
+onboarding.tutorial.with.jenkins.prereqs.following_are_recommendations=We recommend using the configuration in the following steps for the best results, but you can customize it as needed.
+onboarding.tutorial.with.jenkins.prereqs.done=Configure Analysis
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.title=Create a Multibranch Pipeline Job
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.intro=Create a Multibranch Pipeline in order to automatically analyze all your branches and pull requests.
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step1.sentence=From Jenkins' dashboard, click {new_item} and create a {type}.
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step1.sentence.new_item=New Item
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step1.sentence.type=Multibranch Pipeline Job
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.sentence=Under {tab}, add a Bitbucket source and enter the following information:
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.sentence.tab=Branch Sources
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.server.label=Server
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.server.action=select the instance hosting the repository you want to analyze.
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.creds.label=Credentials
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.creds.action=select the Bitbucket Server credentials.
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.owner.label=Owner
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.owner.action=enter your project key.
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.repo.label=Repository
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.repo.action=select the repository you want to analyze.
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.behaviour.label=Behavior
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step2.behaviour.action=select Exclude branches that are also filed as PRs.
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step3.sentence=Jump to the {tab} section and set the following parameters:
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step3.sentence.tab=Build Configuration
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step3.mode.label=Mode
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step3.mode.action=by Jenkinsfile
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step3.script_path.label=Script Path
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step3.script_path.action=Jenkinsfile
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step4.sentence=Click {save}.
+onboarding.tutorial.with.jenkins.multi_branch_pipeline.step4.sentence.save=Save
+onboarding.tutorial.with.jenkins.bitbucket_webhook.title=Create a Bitbucket Server Webhook
+onboarding.tutorial.with.jenkins.bitbucket_webhook.intro.sentence=Create a Webhook in your repository to trigger the Jenkins job on push. Already have a Webhook configured? {link}
+onboarding.tutorial.with.jenkins.bitbucket_webhook.intro.link=Skip this step.
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.sentence=Go to the {link} and enter the following information:
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.link=Bitbucket Server Webhook creation page for your repository
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.name.label=Name
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.name.action=give a unique name.
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.url.label=URL
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.url.action=Enter the following URL, replacing the tokens as needed:
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step1.url.warning=The Bitbucket Server URL must be identical to the one in your Jenkins configuration. Watch out for any missing or extra "/" at the end.
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step2.sentence=Under {events}, make sure the following options are checked:
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step2.sentence.events=Events
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step2.repo.label=Repository
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step2.repo.action=Push
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step2.pr.label=Pull Request
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step2.pr.action=Opened
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step3.sentence=Click {create}.
+onboarding.tutorial.with.jenkins.bitbucket_webhook.step3.sentence.create=Create
+onboarding.tutorial.with.jenkins.jenkinsfile.title=Create a Jenkinsfile
+onboarding.tutorial.with.jenkins.jenkinsfile.jenkinsfile_step.sentence=Create a {file} file in your repository and paste the following code:
+onboarding.tutorial.with.jenkins.jenkinsfile.maven.step2.sentence=Add the following to your {file} file:
+onboarding.tutorial.with.jenkins.jenkinsfile.maven.step3.replace.sentence=Make sure to replace {default} with the name you gave to your Maven tool {in_jenkins}.
+onboarding.tutorial.with.jenkins.jenkinsfile.maven.step3.replace.sentence.default=Default Maven
+onboarding.tutorial.with.jenkins.jenkinsfile.maven.step3.replace.sentence.in_jenkins=in Jenkins
+onboarding.tutorial.with.jenkins.jenkinsfile.maven.step3.help1.sentence=To get the name of your Maven tool in Jenkins, navigate to {path}.
+onboarding.tutorial.with.jenkins.jenkinsfile.maven.step3.help1.sentence.path=Manage Jenkins > Global Tool Configuration
+onboarding.tutorial.with.jenkins.jenkinsfile.maven.step3.help2.sentence=The name is located under the {path} section, in the {name} field.
+onboarding.tutorial.with.jenkins.jenkinsfile.maven.step3.help2.sentence.path=Maven > Maven installations
+onboarding.tutorial.with.jenkins.jenkinsfile.maven.step3.help2.sentence.name=Name
+onboarding.tutorial.with.jenkins.jenkinsfile.gradle.step2.sentence=Add the following to your {file} file:
+onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.replace.sentence=Make sure to replace {default_msbuild} and {default_scanner} with the names you gave to your MSBuild and SonarScanner for MSBuild tools {in_jenkins}.
+onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.replace.sentence.default_msbuild=Default MSBuild
+onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.replace.sentence.default_scanner=SonarScanner for MSBuild
+onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.replace.sentence.in_jenkins=in Jenkins
+onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.help1.sentence=To get the name of these tools in Jenkins, navigate to {path}.
+onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.help1.sentence.path=Manage Jenkins > Global Tool Configuration
+onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.help2.sentence=For your MSBuild tool, the name is located under the {path} section, in the {name} field.
+onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.help2.sentence.path=MSBuild > MSBuild installations
+onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.help2.sentence.name=Name
+onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.help3.sentence=For your SonarScanner for MSBuild tool, the name is located under the {path} section, in the {name} field.
+onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.help3.sentence.path=SonarScanner for MSBuild > SonarScanner for MSBuild installations
+onboarding.tutorial.with.jenkins.jenkinsfile.msbuild.step2.help3.sentence.name=Name
+onboarding.tutorial.with.jenkins.jenkinsfile.other.step2.sentence=Create a {file} file in your repository and paste the following code:
+onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.replace.sentence=Make sure to replace {default} with the name you gave to your SonarQube Scanner tool {in_jenkins}.
+onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.replace.sentence.default=SonarScanner
+onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.replace.sentence.in_jenkins=in Jenkins
+onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.help1.sentence=To get the name of your SonarQube Scanner tool in Jenkins, navigate to {path}.
+onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.help1.sentence.path=Manage Jenkins > Global Tool Configuration
+onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.help2.sentence=The name is located under the {path} section, in the {name} field.
+onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.help2.sentence.path=SonarQube Scanner > SonarQube Scanner installations
+onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.help2.sentence.name=Name
+onboarding.tutorial.with.jenkins.all_set.sentence={all_set} and ready to improve the quality and security of your code!
+onboarding.tutorial.with.jenkins.all_set.sentence.all_set=You're all set
+onboarding.tutorial.with.jenkins.commit=Commit and push your code to start the analysis.
+onboarding.tutorial.with.jenkins.commit.why=Each new push you make on your branches or pull requests will trigger a new analysis in SonarQube.
+onboarding.tutorial.with.jenkins.refresh=This page will then refresh with your analysis results.
+onboarding.tutorial.with.jenkins.refresh.why=If the page doesn't refresh after a while, please double-check the analysis configuration.
+
 #------------------------------------------------------------------------------
 #
 # BRANCHES