]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-14079 Add Azure Pipelines tutorial for Azure DevOps Server
authorPhilippe Perrin <philippe.perrin@sonarsource.com>
Wed, 18 Nov 2020 12:23:52 +0000 (13:23 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 2 Dec 2020 20:06:58 +0000 (20:06 +0000)
30 files changed:
server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx
server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx
server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx
server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelectionRenderer-test.tsx
server/sonar-web/src/main/js/components/tutorials/__tests__/__snapshots__/TutorialSelectionRenderer-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/AzurePipelinesTutorial.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/BranchAnalysisStepContent.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/ExtensionInstallationStepContent.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/SaveAndRunStepContent.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/ServiceEndpointStepContent.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/BranchAnalysisStepContent-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/ExtensionInstallationStepContent-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/SaveAndRunStepContent-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/ServiceEndpointStepContent-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/AzurePipelinesTutorial-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/BranchAnalysisStepContent-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/ExtensionInstallationStepContent-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/SaveAndRunStepContent-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/ServiceEndpointStepContent-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/GitLabCITutorial.tsx
server/sonar-web/src/main/js/components/tutorials/jenkins/JenkinsTutorial.tsx
server/sonar-web/src/main/js/components/tutorials/jenkins/MultiBranchPipelineStep.tsx
server/sonar-web/src/main/js/components/tutorials/jenkins/WebhookStep.tsx
server/sonar-web/src/main/js/components/tutorials/types.ts
server/sonar-web/src/main/js/helpers/__tests__/alm-settings-test.ts [deleted file]
server/sonar-web/src/main/js/helpers/alm-settings.ts [deleted file]
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

index a077d399d1a5d545dd43926a069dce3f48b244f0..24095a0d0081a98c330dc5353966676147eb142b 100644 (file)
@@ -20,7 +20,7 @@
 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 { AlmBindingDefinition, ProjectAlmBindingResponse } from '../../types/alm-settings';
 import { withRouter } from '../hoc/withRouter';
 import TutorialSelectionRenderer from './TutorialSelectionRenderer';
 import { TutorialModes } from './types';
@@ -58,11 +58,7 @@ export class TutorialSelection extends React.PureComponent<Props, State> {
     ]);
 
     if (this.mounted) {
-      // We only support Bitbucket, GitHub & Gitlab for now.
-      if (
-        projectBinding === undefined ||
-        ![AlmKeys.Bitbucket, AlmKeys.GitHub, AlmKeys.GitLab].includes(projectBinding.alm)
-      ) {
+      if (projectBinding === undefined) {
         this.setState({ loading: false, forceManual: true });
       } else {
         let almBinding;
index 1fc339d8dcf8edf0c712823e93e7f8c6109b8ccd..1cf814c264673a0d06f6e478ee3104ac58e85608 100644 (file)
@@ -22,6 +22,7 @@ import * as React from 'react';
 import { translate } from 'sonar-ui-common/helpers/l10n';
 import { getBaseUrl } from 'sonar-ui-common/helpers/urls';
 import { AlmBindingDefinition, AlmKeys, ProjectAlmBindingResponse } from '../../types/alm-settings';
+import AzurePipelinesTutorial from './azure-pipelines/AzurePipelinesTutorial';
 import GitLabCITutorial from './gitlabci/GitLabCITutorial';
 import JenkinsTutorial from './jenkins/JenkinsTutorial';
 import ManualTutorial from './manual/ManualTutorial';
@@ -74,6 +75,22 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
               </button>
             )}
 
+            {projectBinding?.alm === AlmKeys.Azure && (
+              <button
+                className="button button-huge display-flex-column spacer-left spacer-right azure-pipelines"
+                onClick={() => props.onSelectTutorial(TutorialModes.AzurePipelines)}
+                type="button">
+                <img
+                  alt="" // Should be ignored by screen readers
+                  height={80}
+                  src={`${getBaseUrl()}/images/alm/azure.svg`}
+                />
+                <div className="medium big-spacer-top">
+                  {translate('onboarding.tutorial.choose_method.azure_pipelines')}
+                </div>
+              </button>
+            )}
+
             {jenkinsAvailable && (
               <button
                 className="button button-huge display-flex-column spacer-left spacer-right tutorial-mode-jenkins"
@@ -126,6 +143,14 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
           projectBinding={projectBinding}
         />
       )}
+
+      {selectedTutorial === TutorialModes.AzurePipelines && projectBinding !== undefined && (
+        <AzurePipelinesTutorial
+          component={component}
+          currentUser={currentUser}
+          projectBinding={projectBinding}
+        />
+      )}
     </>
   );
 }
index 06ba00709789467aec087f59e1c41bf499e10793..9585564dcc51ca6b3ec2785e8fc69f84df3ad697 100644 (file)
@@ -50,20 +50,13 @@ it('should select manual if project is not bound', async () => {
   expect(wrapper.state().forceManual).toBe(true);
 });
 
-it('should not select anything if project is bound to Bitbucket', async () => {
+it('should not select anything if project is bound', 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 unsupported ALM', async () => {
-  (getProjectAlmBinding as jest.Mock).mockResolvedValueOnce({ alm: AlmKeys.Azure });
-  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 });
index bd45cbac9c186e161b31eaaa9acfabe0b5e2baf9..9231d66da02cf7687b1f0e8ab650a15693c91e84 100644 (file)
@@ -23,6 +23,7 @@ import * as React from 'react';
 import { click } from 'sonar-ui-common/helpers/testUtils';
 import {
   mockBitbucketBindingDefinition,
+  mockProjectAzureBindingResponse,
   mockProjectBitbucketBindingResponse,
   mockProjectGitLabBindingResponse
 } from '../../../helpers/mocks/alm-settings';
@@ -53,6 +54,12 @@ it('should render correctly', () => {
       projectBinding: mockProjectGitLabBindingResponse()
     })
   ).toMatchSnapshot('gitlab tutorial');
+  expect(
+    shallowRender({
+      selectedTutorial: TutorialModes.AzurePipelines,
+      projectBinding: mockProjectAzureBindingResponse()
+    })
+  ).toMatchSnapshot('azure pipelines tutorial');
 });
 
 it('should allow mode selection', () => {
@@ -80,6 +87,17 @@ it('should allow gitlab selection', () => {
   expect(onSelectTutorial).toHaveBeenLastCalledWith(TutorialModes.GitLabCI);
 });
 
+it('should allow azure pipelines selection', () => {
+  const onSelectTutorial = jest.fn();
+  const wrapper = shallowRender({
+    onSelectTutorial,
+    projectBinding: mockProjectAzureBindingResponse()
+  });
+
+  click(wrapper.find('button.azure-pipelines'));
+  expect(onSelectTutorial).toHaveBeenLastCalledWith(TutorialModes.AzurePipelines);
+});
+
 function shallowRender(props: Partial<TutorialSelectionRendererProps> = {}) {
   return shallow<TutorialSelectionRendererProps>(
     <TutorialSelectionRenderer
index ef26e8102911e9208ac49d7c2b4b24eb4b3c9d8e..10796cbddc1a2bbf57fe57301236494b45dea42c 100644 (file)
@@ -1,5 +1,53 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`should render correctly: azure pipelines tutorial 1`] = `
+<Fragment>
+  <AzurePipelinesTutorial
+    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 [],
+      }
+    }
+    projectBinding={
+      Object {
+        "alm": "azure",
+        "key": "foo",
+        "repository": "REPOSITORY_NAME",
+        "slug": "PROJECT_NAME",
+        "url": "https://ado.my_company.com/mycollection",
+      }
+    }
+  />
+</Fragment>
+`;
+
 exports[`should render correctly: gitlab tutorial 1`] = `
 <Fragment>
   <GitLabCITutorial
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/AzurePipelinesTutorial.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/AzurePipelinesTutorial.tsx
new file mode 100644 (file)
index 0000000..8cde683
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * 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 { Alert } from 'sonar-ui-common/components/ui/Alert';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+import {
+  isProjectAzureBindingResponse,
+  ProjectAlmBindingResponse
+} from '../../../types/alm-settings';
+import Step from '../components/Step';
+import BranchAnalysisStepContent from './BranchAnalysisStepContent';
+import ExtensionInstallationStepContent from './ExtensionInstallationStepContent';
+import SaveAndRunStepContent from './SaveAndRunStepContent';
+import ServiceEndpointStepContent from './ServiceEndpointStepContent';
+
+export interface AzurePipelinesTutorialProps {
+  component: T.Component;
+  currentUser: T.LoggedInUser;
+  projectBinding: ProjectAlmBindingResponse;
+}
+
+export enum Steps {
+  ExtensionInstallation,
+  ServiceEndpoint,
+  BranchAnalysis,
+  SaveAndRun
+}
+
+interface Step {
+  step: Steps;
+  content: JSX.Element;
+  checkValidity?: boolean;
+}
+
+export default function AzurePipelinesTutorial(props: AzurePipelinesTutorialProps) {
+  const { component, currentUser, projectBinding } = props;
+
+  const [currentStep, setCurrentStep] = React.useState(Steps.ExtensionInstallation);
+  const [isCurrentStepValid, setIsCurrentStepValid] = React.useState(false);
+
+  // Failsafe; should never happen.
+  if (!isProjectAzureBindingResponse(projectBinding)) {
+    return (
+      <Alert variant="error">
+        {translate('onboarding.tutorial.with.azure_pipelines.unsupported')}
+      </Alert>
+    );
+  }
+
+  const steps: Array<Step> = [
+    { step: Steps.ExtensionInstallation, content: <ExtensionInstallationStepContent /> },
+    {
+      step: Steps.ServiceEndpoint,
+      content: <ServiceEndpointStepContent component={component} currentUser={currentUser} />
+    },
+    {
+      step: Steps.BranchAnalysis,
+      content: (
+        <BranchAnalysisStepContent
+          component={component}
+          onStepValidationChange={isValid => setIsCurrentStepValid(isValid)}
+        />
+      ),
+      checkValidity: true
+    },
+    { step: Steps.SaveAndRun, content: <SaveAndRunStepContent /> }
+  ];
+
+  const switchCurrentStep = (step: Steps) => {
+    setCurrentStep(step);
+    setIsCurrentStepValid(false);
+  };
+
+  const canContinue = (step: Step, i: number) =>
+    i < steps.length - 1 && (!step.checkValidity || isCurrentStepValid);
+
+  return (
+    <>
+      <div className="page-header big-spacer-bottom">
+        <h1 className="page-title">
+          {translate('onboarding.tutorial.with.azure_pipelines.title')}
+        </h1>
+      </div>
+
+      {steps.map((step, i) => (
+        <Step
+          key={step.step}
+          stepNumber={i + 1}
+          stepTitle={translate(
+            `onboarding.tutorial.with.azure_pipelines.${Steps[step.step]}.title`
+          )}
+          open={step.step === currentStep}
+          finished={step.step < currentStep}
+          onOpen={() => switchCurrentStep(step.step)}
+          renderForm={() => (
+            <div className="boxed-group-inner">
+              <div>{step.content}</div>
+              {canContinue(step, i) && (
+                <Button
+                  className="big-spacer-top spacer-bottom"
+                  onClick={() => switchCurrentStep(step.step + 1)}>
+                  {translate('continue')}
+                </Button>
+              )}
+            </div>
+          )}
+        />
+      ))}
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/BranchAnalysisStepContent.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/BranchAnalysisStepContent.tsx
new file mode 100644 (file)
index 0000000..f091e22
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * 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 { ClipboardIconButton } from 'sonar-ui-common/components/controls/clipboard';
+import { Alert } from 'sonar-ui-common/components/ui/Alert';
+import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
+import CodeSnippet from '../../common/CodeSnippet';
+import RenderOptions from '../components/RenderOptions';
+import SentenceWithHighlights from '../components/SentenceWithHighlights';
+
+export interface BranchesAnalysisStepProps {
+  component: T.Component;
+  onStepValidationChange: (isValid: boolean) => void;
+}
+
+export enum BuildTechnology {
+  DotNet = 'dotnet',
+  Maven = 'maven',
+  Gradle = 'gradle',
+  Other = 'other'
+}
+
+export default function BranchAnalysisStepContent(props: BranchesAnalysisStepProps) {
+  const { component, onStepValidationChange } = props;
+
+  const [buildTechnology, setBuildTechnology] = React.useState<BuildTechnology | undefined>();
+
+  React.useEffect(() => {
+    if (buildTechnology) {
+      onStepValidationChange(true);
+    }
+  }, [buildTechnology, onStepValidationChange]);
+
+  const MAVEN_GRADLE_PROPS_SNIPPET = `# Additional properties that will be passed to the scanner,
+# Put one key=value per line, example:
+# sonar.exclusions=**/*.bin
+sonar.projectKey=${component.key}`;
+
+  return (
+    <>
+      <span>{translate('onboarding.build')}</span>
+      <RenderOptions
+        checked={buildTechnology}
+        name="buildTechnology"
+        onCheck={value => setBuildTechnology(value as BuildTechnology)}
+        optionLabelKey="onboarding.build"
+        options={Object.values(BuildTechnology)}
+      />
+      <ol className="list-styled big-spacer-top">
+        {buildTechnology && (
+          <>
+            <li>
+              <SentenceWithHighlights
+                translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare"
+                highlightKeys={['pipeline', 'task', 'before']}
+              />
+            </li>
+            <ul className="list-styled">
+              <li>
+                <SentenceWithHighlights
+                  translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.endpoint"
+                  highlightKeys={['endpoint']}
+                />
+              </li>
+              <li>
+                <FormattedMessage
+                  id="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis"
+                  defaultMessage={translate(
+                    'onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis'
+                  )}
+                  values={{
+                    section: (
+                      <strong>
+                        {translate(
+                          'onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.section'
+                        )}
+                      </strong>
+                    ),
+                    run_analysis_value: (
+                      <strong>
+                        {translate(
+                          'onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.values',
+                          buildTechnology
+                        )}
+                      </strong>
+                    )
+                  }}
+                />
+              </li>
+              {buildTechnology === BuildTechnology.Other && (
+                <li>
+                  <SentenceWithHighlights
+                    translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.manual"
+                    highlightKeys={['mode']}
+                  />
+                </li>
+              )}
+              {[BuildTechnology.DotNet, BuildTechnology.Other].includes(buildTechnology) && (
+                <li>
+                  <FormattedMessage
+                    id="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run.key.sentence"
+                    defaultMessage={translate(
+                      'onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run.key.sentence'
+                    )}
+                    values={{
+                      key: <code className="rule">{component.key}</code>,
+                      button: <ClipboardIconButton copyValue={component.key} />
+                    }}
+                  />
+                </li>
+              )}
+              {[BuildTechnology.Maven, BuildTechnology.Gradle].includes(buildTechnology) && (
+                <li>
+                  <SentenceWithHighlights
+                    translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.advanced_properties"
+                    highlightKeys={['section', 'properties']}
+                  />
+                  :
+                  <CodeSnippet snippet={MAVEN_GRADLE_PROPS_SNIPPET} />
+                </li>
+              )}
+            </ul>
+            {[BuildTechnology.DotNet, BuildTechnology.Other].includes(buildTechnology) && (
+              <li>
+                <SentenceWithHighlights
+                  translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run"
+                  highlightKeys={['task', 'after']}
+                />
+              </li>
+            )}
+            {[BuildTechnology.Maven, BuildTechnology.Gradle].includes(buildTechnology) && (
+              <>
+                <li>
+                  {translateWithParameters(
+                    'onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java',
+                    translate('onboarding.build', buildTechnology)
+                  )}
+                </li>
+                <ul className="list-styled big-spacer-bottom">
+                  <li>
+                    <SentenceWithHighlights
+                      translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java.settings"
+                      highlightKeys={['section', 'option']}
+                    />
+                  </li>
+                </ul>
+              </>
+            )}
+
+            <li>
+              <SentenceWithHighlights
+                translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg"
+                highlightKeys={['task']}
+              />
+              <Alert variant="info" className="spacer-top">
+                {translate(
+                  'onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg.info.sentence1'
+                )}
+              </Alert>
+            </li>
+            <li>
+              <SentenceWithHighlights
+                translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration"
+                highlightKeys={['tab', 'continuous_integration']}
+              />
+            </li>
+            <hr />
+            <FormattedMessage
+              id="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection"
+              defaultMessage={translate(
+                'onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection'
+              )}
+              values={{
+                link: (
+                  <Link to="/documentation/analysis/azuredevops-integration/" target="_blank">
+                    {translate(
+                      'onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection.link'
+                    )}
+                  </Link>
+                )
+              }}
+            />
+          </>
+        )}
+      </ol>
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/ExtensionInstallationStepContent.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/ExtensionInstallationStepContent.tsx
new file mode 100644 (file)
index 0000000..4e00f96
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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 default function ExtensionInstallationStepContent() {
+  return (
+    <span>
+      <FormattedMessage
+        defaultMessage={translate(
+          'onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence'
+        )}
+        id="onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence"
+        values={{
+          link: (
+            <a
+              href="https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarqube"
+              rel="noopener noreferrer"
+              target="_blank">
+              {translate(
+                'onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence.link'
+              )}
+            </a>
+          ),
+          button: (
+            <strong>
+              {translate(
+                'onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence.button'
+              )}
+            </strong>
+          )
+        }}
+      />
+    </span>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/SaveAndRunStepContent.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/SaveAndRunStepContent.tsx
new file mode 100644 (file)
index 0000000..ed70ae4
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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 '../../../helpers/system';
+
+export default function SaveAndRunStepContent() {
+  return (
+    <>
+      <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.azure_pipelines.SaveAndRun.commit')}
+            </strong>
+          </p>
+          <p>{translate('onboarding.tutorial.with.azure_pipelines.SaveAndRun.commit.why')}</p>
+        </div>
+      </div>
+      <div className="display-flex-row">
+        <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.azure_pipelines.SaveAndRun.refresh')}
+            </strong>
+          </p>
+          <p>{translate('onboarding.tutorial.with.azure_pipelines.SaveAndRun.refresh.why')}</p>
+        </div>
+      </div>
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/ServiceEndpointStepContent.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/ServiceEndpointStepContent.tsx
new file mode 100644 (file)
index 0000000..69a907a
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * 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 } from 'sonar-ui-common/components/controls/buttons';
+import { ClipboardIconButton } from 'sonar-ui-common/components/controls/clipboard';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+import { getHostUrl } from 'sonar-ui-common/helpers/urls';
+import EditTokenModal from '../components/EditTokenModal';
+import SentenceWithHighlights from '../components/SentenceWithHighlights';
+
+export interface ServiceEndpointStepProps {
+  component: T.Component;
+  currentUser: T.LoggedInUser;
+}
+
+export default function ServiceEndpointStepContent(props: ServiceEndpointStepProps) {
+  const { component, currentUser } = props;
+
+  const [isModalVisible, toggleModal] = React.useState(false);
+
+  return (
+    <>
+      <ol className="list-styled">
+        <li>
+          <SentenceWithHighlights
+            translationKey="onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step1"
+            highlightKeys={['menu']}
+          />
+        </li>
+        <li>
+          <SentenceWithHighlights
+            translationKey="onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step2"
+            highlightKeys={['type']}
+          />
+        </li>
+        <li>
+          {translate('onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step3.sentence')}
+        </li>
+        <li>
+          <FormattedMessage
+            defaultMessage={translate(
+              'onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step4.sentence'
+            )}
+            id="onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step4.sentence"
+            values={{
+              url: <code className="rule">{getHostUrl()}</code>,
+              button: <ClipboardIconButton copyValue={getHostUrl()} />
+            }}
+          />
+        </li>
+        <li>
+          <span>
+            {translate('onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step5.sentence')}:
+          </span>
+          <Button className="spacer-left" onClick={() => toggleModal(true)}>
+            {translate('onboarding.token.generate_token')}
+          </Button>
+        </li>
+        <li>
+          {translate('onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step6.sentence')}
+        </li>
+      </ol>
+
+      {isModalVisible && (
+        <EditTokenModal
+          component={component}
+          currentUser={currentUser}
+          onClose={() => toggleModal(false)}
+        />
+      )}
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-test.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-test.tsx
new file mode 100644 (file)
index 0000000..d7ad84a
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * 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 { Button } from 'sonar-ui-common/components/controls/buttons';
+import { click } from 'sonar-ui-common/helpers/testUtils';
+import {
+  mockProjectAzureBindingResponse,
+  mockProjectGithubBindingResponse
+} from '../../../../helpers/mocks/alm-settings';
+import { mockComponent, mockLoggedInUser } from '../../../../helpers/testMocks';
+import Step from '../../components/Step';
+import AzurePipelinesTutorial, { AzurePipelinesTutorialProps } from '../AzurePipelinesTutorial';
+
+it('should render correctly', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot();
+  expect(
+    wrapper
+      .find(Step)
+      .first()
+      .dive()
+  ).toMatchSnapshot('first-step-wrapper');
+  expect(
+    wrapper
+      .find(Step)
+      .last()
+      .dive()
+  ).toMatchSnapshot('last-step-wrapper');
+  expect(shallowRender({ projectBinding: mockProjectGithubBindingResponse() })).toMatchSnapshot(
+    'wrong alm'
+  );
+});
+
+it('should display the next step when one is finished', () => {
+  const wrapper = shallowRender();
+  expect(
+    wrapper
+      .find(Step)
+      .filterWhere(elt => elt.props().open === true)
+      .props().stepNumber
+  ).toBe(1);
+
+  click(
+    wrapper
+      .find(Step)
+      .first()
+      .dive()
+      .find(Button)
+  );
+
+  expect(
+    wrapper
+      .find(Step)
+      .filterWhere(elt => elt.props().open === true)
+      .props().stepNumber
+  ).toBe(2);
+});
+
+it('should open a step when user click on it', () => {
+  const wrapper = shallowRender();
+  expect(
+    wrapper
+      .find(Step)
+      .filterWhere(elt => elt.props().open === true)
+      .props().stepNumber
+  ).toBe(1);
+
+  (
+    wrapper
+      .find(Step)
+      .filterWhere(elt => elt.props().stepNumber === 3)
+      .props().onOpen ?? (() => {})
+  )();
+
+  expect(
+    wrapper
+      .find(Step)
+      .filterWhere(elt => elt.props().open === true)
+      .props().stepNumber
+  ).toBe(3);
+});
+
+function shallowRender(props: Partial<AzurePipelinesTutorialProps> = {}) {
+  return shallow<AzurePipelinesTutorialProps>(
+    <AzurePipelinesTutorial
+      component={mockComponent()}
+      currentUser={mockLoggedInUser()}
+      projectBinding={mockProjectAzureBindingResponse()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/BranchAnalysisStepContent-test.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/BranchAnalysisStepContent-test.tsx
new file mode 100644 (file)
index 0000000..f6f45ae
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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 RenderOptions from '../../components/RenderOptions';
+import BranchAnalysisStepContent, {
+  BranchesAnalysisStepProps,
+  BuildTechnology
+} from '../BranchAnalysisStepContent';
+
+it('should render correctly', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot();
+});
+
+it.each([
+  BuildTechnology.DotNet,
+  BuildTechnology.Gradle,
+  BuildTechnology.Maven,
+  BuildTechnology.Other
+])('should render correctly', (buildTech: BuildTechnology) => {
+  const wrapper = shallowRender();
+  wrapper
+    .find(RenderOptions)
+    .props()
+    .onCheck(buildTech);
+
+  expect(wrapper).toMatchSnapshot(buildTech);
+});
+
+function shallowRender(props: Partial<BranchesAnalysisStepProps> = {}) {
+  return shallow(
+    <BranchAnalysisStepContent
+      component={mockComponent()}
+      onStepValidationChange={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/ExtensionInstallationStepContent-test.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/ExtensionInstallationStepContent-test.tsx
new file mode 100644 (file)
index 0000000..da36100
--- /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 ExtensionInstallationStepContent from '../ExtensionInstallationStepContent';
+
+it('should render correctly', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender() {
+  return shallow(<ExtensionInstallationStepContent />);
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/SaveAndRunStepContent-test.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/SaveAndRunStepContent-test.tsx
new file mode 100644 (file)
index 0000000..0e0af02
--- /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 SaveAndRunStepContent from '../SaveAndRunStepContent';
+
+it('should render correctly', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender() {
+  return shallow(<SaveAndRunStepContent />);
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/ServiceEndpointStepContent-test.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/ServiceEndpointStepContent-test.tsx
new file mode 100644 (file)
index 0000000..683e932
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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 { Button } from 'sonar-ui-common/components/controls/buttons';
+import { click } from 'sonar-ui-common/helpers/testUtils';
+import { mockComponent, mockLoggedInUser } from '../../../../helpers/testMocks';
+import EditTokenModal from '../../components/EditTokenModal';
+import ServiceEndpointStepContent from '../ServiceEndpointStepContent';
+
+it('should render correctly', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('should render open/close the token modal when asked to', () => {
+  const wrapper = shallowRender();
+  expect(wrapper.exists(EditTokenModal)).toBe(false);
+
+  click(wrapper.find(Button));
+  expect(wrapper.exists(EditTokenModal)).toBe(true);
+
+  wrapper
+    .find(EditTokenModal)
+    .props()
+    .onClose();
+  expect(wrapper.exists(EditTokenModal)).toBe(false);
+});
+
+function shallowRender() {
+  return shallow(
+    <ServiceEndpointStepContent component={mockComponent()} currentUser={mockLoggedInUser()} />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/AzurePipelinesTutorial-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/AzurePipelinesTutorial-test.tsx.snap
new file mode 100644 (file)
index 0000000..84e3287
--- /dev/null
@@ -0,0 +1,128 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <div
+    className="page-header big-spacer-bottom"
+  >
+    <h1
+      className="page-title"
+    >
+      onboarding.tutorial.with.azure_pipelines.title
+    </h1>
+  </div>
+  <Step
+    finished={false}
+    key="0"
+    onOpen={[Function]}
+    open={true}
+    renderForm={[Function]}
+    stepNumber={1}
+    stepTitle="onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title"
+  />
+  <Step
+    finished={false}
+    key="1"
+    onOpen={[Function]}
+    open={false}
+    renderForm={[Function]}
+    stepNumber={2}
+    stepTitle="onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.title"
+  />
+  <Step
+    finished={false}
+    key="2"
+    onOpen={[Function]}
+    open={false}
+    renderForm={[Function]}
+    stepNumber={3}
+    stepTitle="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.title"
+  />
+  <Step
+    finished={false}
+    key="3"
+    onOpen={[Function]}
+    open={false}
+    renderForm={[Function]}
+    stepNumber={4}
+    stepTitle="onboarding.tutorial.with.azure_pipelines.SaveAndRun.title"
+  />
+</Fragment>
+`;
+
+exports[`should render correctly: first-step-wrapper 1`] = `
+<div
+  className="boxed-group onboarding-step is-open"
+>
+  <div
+    className="onboarding-step-number"
+  >
+    1
+  </div>
+  <div
+    className="boxed-group-header"
+  >
+    <h2>
+      onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title
+    </h2>
+  </div>
+  <div
+    className=""
+  >
+    <div
+      className="boxed-group-inner"
+    >
+      <div>
+        <ExtensionInstallationStepContent />
+      </div>
+      <Button
+        className="big-spacer-top spacer-bottom"
+        onClick={[Function]}
+      >
+        continue
+      </Button>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`should render correctly: last-step-wrapper 1`] = `
+<div
+  className="boxed-group onboarding-step"
+>
+  <div
+    className="onboarding-step-number"
+  >
+    4
+  </div>
+  <div
+    className="boxed-group-header"
+  >
+    <h2>
+      onboarding.tutorial.with.azure_pipelines.SaveAndRun.title
+    </h2>
+  </div>
+  <div
+    className="boxed-group-inner"
+  />
+  <div
+    className="hidden"
+  >
+    <div
+      className="boxed-group-inner"
+    >
+      <div>
+        <SaveAndRunStepContent />
+      </div>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`should render correctly: wrong alm 1`] = `
+<Alert
+  variant="error"
+>
+  onboarding.tutorial.with.azure_pipelines.unsupported
+</Alert>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/BranchAnalysisStepContent-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/BranchAnalysisStepContent-test.tsx.snap
new file mode 100644 (file)
index 0000000..0760231
--- /dev/null
@@ -0,0 +1,613 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <span>
+    onboarding.build
+  </span>
+  <RenderOptions
+    name="buildTechnology"
+    onCheck={[Function]}
+    optionLabelKey="onboarding.build"
+    options={
+      Array [
+        "dotnet",
+        "maven",
+        "gradle",
+        "other",
+      ]
+    }
+  />
+  <ol
+    className="list-styled big-spacer-top"
+  />
+</Fragment>
+`;
+
+exports[`should render correctly: dotnet 1`] = `
+<Fragment>
+  <span>
+    onboarding.build
+  </span>
+  <RenderOptions
+    checked="dotnet"
+    name="buildTechnology"
+    onCheck={[Function]}
+    optionLabelKey="onboarding.build"
+    options={
+      Array [
+        "dotnet",
+        "maven",
+        "gradle",
+        "other",
+      ]
+    }
+  />
+  <ol
+    className="list-styled big-spacer-top"
+  >
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "pipeline",
+            "task",
+            "before",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare"
+      />
+    </li>
+    <ul
+      className="list-styled"
+    >
+      <li>
+        <SentenceWithHighlights
+          highlightKeys={
+            Array [
+              "endpoint",
+            ]
+          }
+          translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.endpoint"
+        />
+      </li>
+      <li>
+        <FormattedMessage
+          defaultMessage="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis"
+          id="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis"
+          values={
+            Object {
+              "run_analysis_value": <strong>
+                onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.values.dotnet
+              </strong>,
+              "section": <strong>
+                onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.section
+              </strong>,
+            }
+          }
+        />
+      </li>
+      <li>
+        <FormattedMessage
+          defaultMessage="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run.key.sentence"
+          id="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run.key.sentence"
+          values={
+            Object {
+              "button": <ClipboardIconButton
+                copyValue="my-project"
+              />,
+              "key": <code
+                className="rule"
+              >
+                my-project
+              </code>,
+            }
+          }
+        />
+      </li>
+    </ul>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "task",
+            "after",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run"
+      />
+    </li>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "task",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg"
+      />
+      <Alert
+        className="spacer-top"
+        variant="info"
+      >
+        onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg.info.sentence1
+      </Alert>
+    </li>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "tab",
+            "continuous_integration",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration"
+      />
+    </li>
+    <hr />
+    <FormattedMessage
+      defaultMessage="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection"
+      id="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection"
+      values={
+        Object {
+          "link": <Link
+            onlyActiveOnIndex={false}
+            style={Object {}}
+            target="_blank"
+            to="/documentation/analysis/azuredevops-integration/"
+          >
+            onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection.link
+          </Link>,
+        }
+      }
+    />
+  </ol>
+</Fragment>
+`;
+
+exports[`should render correctly: gradle 1`] = `
+<Fragment>
+  <span>
+    onboarding.build
+  </span>
+  <RenderOptions
+    checked="gradle"
+    name="buildTechnology"
+    onCheck={[Function]}
+    optionLabelKey="onboarding.build"
+    options={
+      Array [
+        "dotnet",
+        "maven",
+        "gradle",
+        "other",
+      ]
+    }
+  />
+  <ol
+    className="list-styled big-spacer-top"
+  >
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "pipeline",
+            "task",
+            "before",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare"
+      />
+    </li>
+    <ul
+      className="list-styled"
+    >
+      <li>
+        <SentenceWithHighlights
+          highlightKeys={
+            Array [
+              "endpoint",
+            ]
+          }
+          translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.endpoint"
+        />
+      </li>
+      <li>
+        <FormattedMessage
+          defaultMessage="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis"
+          id="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis"
+          values={
+            Object {
+              "run_analysis_value": <strong>
+                onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.values.gradle
+              </strong>,
+              "section": <strong>
+                onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.section
+              </strong>,
+            }
+          }
+        />
+      </li>
+      <li>
+        <SentenceWithHighlights
+          highlightKeys={
+            Array [
+              "section",
+              "properties",
+            ]
+          }
+          translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.advanced_properties"
+        />
+        :
+        <CodeSnippet
+          snippet="# Additional properties that will be passed to the scanner,
+# Put one key=value per line, example:
+# sonar.exclusions=**/*.bin
+sonar.projectKey=my-project"
+        />
+      </li>
+    </ul>
+    <li>
+      onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java.onboarding.build.gradle
+    </li>
+    <ul
+      className="list-styled big-spacer-bottom"
+    >
+      <li>
+        <SentenceWithHighlights
+          highlightKeys={
+            Array [
+              "section",
+              "option",
+            ]
+          }
+          translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java.settings"
+        />
+      </li>
+    </ul>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "task",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg"
+      />
+      <Alert
+        className="spacer-top"
+        variant="info"
+      >
+        onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg.info.sentence1
+      </Alert>
+    </li>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "tab",
+            "continuous_integration",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration"
+      />
+    </li>
+    <hr />
+    <FormattedMessage
+      defaultMessage="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection"
+      id="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection"
+      values={
+        Object {
+          "link": <Link
+            onlyActiveOnIndex={false}
+            style={Object {}}
+            target="_blank"
+            to="/documentation/analysis/azuredevops-integration/"
+          >
+            onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection.link
+          </Link>,
+        }
+      }
+    />
+  </ol>
+</Fragment>
+`;
+
+exports[`should render correctly: maven 1`] = `
+<Fragment>
+  <span>
+    onboarding.build
+  </span>
+  <RenderOptions
+    checked="maven"
+    name="buildTechnology"
+    onCheck={[Function]}
+    optionLabelKey="onboarding.build"
+    options={
+      Array [
+        "dotnet",
+        "maven",
+        "gradle",
+        "other",
+      ]
+    }
+  />
+  <ol
+    className="list-styled big-spacer-top"
+  >
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "pipeline",
+            "task",
+            "before",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare"
+      />
+    </li>
+    <ul
+      className="list-styled"
+    >
+      <li>
+        <SentenceWithHighlights
+          highlightKeys={
+            Array [
+              "endpoint",
+            ]
+          }
+          translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.endpoint"
+        />
+      </li>
+      <li>
+        <FormattedMessage
+          defaultMessage="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis"
+          id="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis"
+          values={
+            Object {
+              "run_analysis_value": <strong>
+                onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.values.maven
+              </strong>,
+              "section": <strong>
+                onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.section
+              </strong>,
+            }
+          }
+        />
+      </li>
+      <li>
+        <SentenceWithHighlights
+          highlightKeys={
+            Array [
+              "section",
+              "properties",
+            ]
+          }
+          translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.advanced_properties"
+        />
+        :
+        <CodeSnippet
+          snippet="# Additional properties that will be passed to the scanner,
+# Put one key=value per line, example:
+# sonar.exclusions=**/*.bin
+sonar.projectKey=my-project"
+        />
+      </li>
+    </ul>
+    <li>
+      onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java.onboarding.build.maven
+    </li>
+    <ul
+      className="list-styled big-spacer-bottom"
+    >
+      <li>
+        <SentenceWithHighlights
+          highlightKeys={
+            Array [
+              "section",
+              "option",
+            ]
+          }
+          translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java.settings"
+        />
+      </li>
+    </ul>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "task",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg"
+      />
+      <Alert
+        className="spacer-top"
+        variant="info"
+      >
+        onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg.info.sentence1
+      </Alert>
+    </li>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "tab",
+            "continuous_integration",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration"
+      />
+    </li>
+    <hr />
+    <FormattedMessage
+      defaultMessage="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection"
+      id="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection"
+      values={
+        Object {
+          "link": <Link
+            onlyActiveOnIndex={false}
+            style={Object {}}
+            target="_blank"
+            to="/documentation/analysis/azuredevops-integration/"
+          >
+            onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection.link
+          </Link>,
+        }
+      }
+    />
+  </ol>
+</Fragment>
+`;
+
+exports[`should render correctly: other 1`] = `
+<Fragment>
+  <span>
+    onboarding.build
+  </span>
+  <RenderOptions
+    checked="other"
+    name="buildTechnology"
+    onCheck={[Function]}
+    optionLabelKey="onboarding.build"
+    options={
+      Array [
+        "dotnet",
+        "maven",
+        "gradle",
+        "other",
+      ]
+    }
+  />
+  <ol
+    className="list-styled big-spacer-top"
+  >
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "pipeline",
+            "task",
+            "before",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare"
+      />
+    </li>
+    <ul
+      className="list-styled"
+    >
+      <li>
+        <SentenceWithHighlights
+          highlightKeys={
+            Array [
+              "endpoint",
+            ]
+          }
+          translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.endpoint"
+        />
+      </li>
+      <li>
+        <FormattedMessage
+          defaultMessage="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis"
+          id="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis"
+          values={
+            Object {
+              "run_analysis_value": <strong>
+                onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.values.other
+              </strong>,
+              "section": <strong>
+                onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.section
+              </strong>,
+            }
+          }
+        />
+      </li>
+      <li>
+        <SentenceWithHighlights
+          highlightKeys={
+            Array [
+              "mode",
+            ]
+          }
+          translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.manual"
+        />
+      </li>
+      <li>
+        <FormattedMessage
+          defaultMessage="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run.key.sentence"
+          id="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run.key.sentence"
+          values={
+            Object {
+              "button": <ClipboardIconButton
+                copyValue="my-project"
+              />,
+              "key": <code
+                className="rule"
+              >
+                my-project
+              </code>,
+            }
+          }
+        />
+      </li>
+    </ul>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "task",
+            "after",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run"
+      />
+    </li>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "task",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg"
+      />
+      <Alert
+        className="spacer-top"
+        variant="info"
+      >
+        onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg.info.sentence1
+      </Alert>
+    </li>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "tab",
+            "continuous_integration",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration"
+      />
+    </li>
+    <hr />
+    <FormattedMessage
+      defaultMessage="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection"
+      id="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection"
+      values={
+        Object {
+          "link": <Link
+            onlyActiveOnIndex={false}
+            style={Object {}}
+            target="_blank"
+            to="/documentation/analysis/azuredevops-integration/"
+          >
+            onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection.link
+          </Link>,
+        }
+      }
+    />
+  </ol>
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/ExtensionInstallationStepContent-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/ExtensionInstallationStepContent-test.tsx.snap
new file mode 100644 (file)
index 0000000..9a8c93a
--- /dev/null
@@ -0,0 +1,24 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<span>
+  <FormattedMessage
+    defaultMessage="onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence"
+    id="onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence"
+    values={
+      Object {
+        "button": <strong>
+          onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence.button
+        </strong>,
+        "link": <a
+          href="https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarqube"
+          rel="noopener noreferrer"
+          target="_blank"
+        >
+          onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence.link
+        </a>,
+      }
+    }
+  />
+</span>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/SaveAndRunStepContent-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/SaveAndRunStepContent-test.tsx.snap
new file mode 100644 (file)
index 0000000..9f4dd34
--- /dev/null
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <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.azure_pipelines.SaveAndRun.commit
+        </strong>
+      </p>
+      <p>
+        onboarding.tutorial.with.azure_pipelines.SaveAndRun.commit.why
+      </p>
+    </div>
+  </div>
+  <div
+    className="display-flex-row"
+  >
+    <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.azure_pipelines.SaveAndRun.refresh
+        </strong>
+      </p>
+      <p>
+        onboarding.tutorial.with.azure_pipelines.SaveAndRun.refresh.why
+      </p>
+    </div>
+  </div>
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/ServiceEndpointStepContent-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/ServiceEndpointStepContent-test.tsx.snap
new file mode 100644 (file)
index 0000000..1596e44
--- /dev/null
@@ -0,0 +1,66 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <ol
+    className="list-styled"
+  >
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "menu",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step1"
+      />
+    </li>
+    <li>
+      <SentenceWithHighlights
+        highlightKeys={
+          Array [
+            "type",
+          ]
+        }
+        translationKey="onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step2"
+      />
+    </li>
+    <li>
+      onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step3.sentence
+    </li>
+    <li>
+      <FormattedMessage
+        defaultMessage="onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step4.sentence"
+        id="onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step4.sentence"
+        values={
+          Object {
+            "button": <ClipboardIconButton
+              copyValue="http://localhost"
+            />,
+            "url": <code
+              className="rule"
+            >
+              http://localhost
+            </code>,
+          }
+        }
+      />
+    </li>
+    <li>
+      <span>
+        onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step5.sentence
+        :
+      </span>
+      <Button
+        className="spacer-left"
+        onClick={[Function]}
+      >
+        onboarding.token.generate_token
+      </Button>
+    </li>
+    <li>
+      onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step6.sentence
+    </li>
+  </ol>
+</Fragment>
+`;
index 8d4dee3d6bc0f068298870115473243ab6144263..d66ac7b3771ec1f1a7889d2a10b452bee533e5ed 100644 (file)
 import * as React from 'react';
 import { Alert } from 'sonar-ui-common/components/ui/Alert';
 import { translate } from 'sonar-ui-common/helpers/l10n';
-import { isProjectGitLabBindingResponse } from '../../../helpers/alm-settings';
-import { ProjectAlmBindingResponse } from '../../../types/alm-settings';
+import {
+  isProjectGitLabBindingResponse,
+  ProjectAlmBindingResponse
+} from '../../../types/alm-settings';
 import EnvironmentVariablesStep from './EnvironmentVariablesStep';
 import ProjectKeyStep from './ProjectKeyStep';
 import { BuildTools } from './types';
index a516260891f7d1e25646f8052435fc11a0569054..cdc00fbf235fde950f2d193ab2c515745a21959e 100644 (file)
@@ -21,13 +21,14 @@ import * as React from 'react';
 import { connect } from 'react-redux';
 import { Alert } from 'sonar-ui-common/components/ui/Alert';
 import { translate } from 'sonar-ui-common/helpers/l10n';
-import {
-  isProjectBitbucketBindingResponse,
-  isProjectGitHubBindingResponse
-} from '../../../helpers/alm-settings';
 import { getCurrentUserSetting, Store } from '../../../store/rootReducer';
 import { setCurrentUserSetting } from '../../../store/users';
-import { AlmBindingDefinition, ProjectAlmBindingResponse } from '../../../types/alm-settings';
+import {
+  AlmBindingDefinition,
+  isProjectBitbucketBindingResponse,
+  isProjectGitHubBindingResponse,
+  ProjectAlmBindingResponse
+} from '../../../types/alm-settings';
 import JenkinsfileStep from './JenkinsfileStep';
 import MultiBranchPipelineStep from './MultiBranchPipelineStep';
 import PreRequisitesStep from './PreRequisitesStep';
index d0feb3133bcdb7d2e731f52b1409cbc6c009f1cb..26279e2c0c7ec5be16041f7e63a44ea73628ff2c 100644 (file)
@@ -21,12 +21,10 @@ import * as React from 'react';
 import { Button } from 'sonar-ui-common/components/controls/buttons';
 import { translate } from 'sonar-ui-common/helpers/l10n';
 import {
+  AlmBindingDefinition,
   isGithubBindingDefinition,
   isProjectBitbucketBindingResponse,
-  isProjectGitHubBindingResponse
-} from '../../../helpers/alm-settings';
-import {
-  AlmBindingDefinition,
+  isProjectGitHubBindingResponse,
   ProjectBitbucketBindingResponse,
   ProjectGitHubBindingResponse
 } from '../../../types/alm-settings';
index 60dfc61831427c1cd463624ec98079d259b1f30c..4243c27cc2ecc29666ebd6980d4d1d6e1ea972e8 100644 (file)
@@ -21,13 +21,11 @@ import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { Button, ButtonLink } from 'sonar-ui-common/components/controls/buttons';
 import { translate } from 'sonar-ui-common/helpers/l10n';
-import {
-  isBitbucketBindingDefinition,
-  isGithubBindingDefinition
-} from '../../../helpers/alm-settings';
 import {
   AlmBindingDefinition,
   AlmKeys,
+  isBitbucketBindingDefinition,
+  isGithubBindingDefinition,
   ProjectAlmBindingResponse
 } from '../../../types/alm-settings';
 import Step from '../components/Step';
index 80e22751eda4475e2b524d8c9a016061776ed959..46edfc4f552b5609f904e2d7471a60b9d98f1b97 100644 (file)
@@ -20,7 +20,8 @@
 export enum TutorialModes {
   Manual = 'manual',
   Jenkins = 'jenkins',
-  GitLabCI = 'gitlab-ci'
+  GitLabCI = 'gitlab-ci',
+  AzurePipelines = 'azure-pipelines'
 }
 
 export enum BuildTools {
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/alm-settings-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/alm-settings-test.ts
deleted file mode 100644 (file)
index 2f8c1c4..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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 { AlmKeys } from '../../types/alm-settings';
-import {
-  isBitbucketBindingDefinition,
-  isGithubBindingDefinition,
-  isProjectBitbucketBindingResponse,
-  isProjectGitHubBindingResponse
-} from '../alm-settings';
-import {
-  mockBitbucketBindingDefinition,
-  mockGithubBindingDefinition,
-  mockProjectAlmBindingResponse,
-  mockProjectBitbucketBindingResponse,
-  mockProjectGithubBindingResponse
-} from '../mocks/alm-settings';
-
-/* eslint-disable sonarjs/no-duplicate-string */
-
-describe('isProjectBitbucketBindingResponse', () => {
-  it('works as expected', () => {
-    expect(isProjectBitbucketBindingResponse(mockProjectAlmBindingResponse())).toBe(false);
-    expect(isProjectBitbucketBindingResponse(mockProjectBitbucketBindingResponse())).toBe(true);
-  });
-});
-
-describe('isBitbucketBindingDefinition', () => {
-  it('works as expected', () => {
-    expect(isBitbucketBindingDefinition(undefined)).toBe(false);
-    expect(isBitbucketBindingDefinition(mockGithubBindingDefinition())).toBe(false);
-    expect(isBitbucketBindingDefinition(mockBitbucketBindingDefinition())).toBe(true);
-  });
-});
-
-describe('isProjectGithubBindingResponse', () => {
-  it('works as expected', () => {
-    expect(
-      isProjectGitHubBindingResponse(mockProjectAlmBindingResponse({ alm: AlmKeys.Azure }))
-    ).toBe(false);
-    expect(isProjectGitHubBindingResponse(mockProjectGithubBindingResponse())).toBe(true);
-  });
-});
-
-describe('isGithubBindingDefinition', () => {
-  it('works as expected', () => {
-    expect(isGithubBindingDefinition(undefined)).toBe(false);
-    expect(isGithubBindingDefinition(mockBitbucketBindingDefinition())).toBe(false);
-    expect(isGithubBindingDefinition(mockGithubBindingDefinition())).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
deleted file mode 100644 (file)
index bd00137..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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,
-  GithubBindingDefinition,
-  ProjectAlmBindingResponse,
-  ProjectBitbucketBindingResponse,
-  ProjectGitHubBindingResponse,
-  ProjectGitLabBindingResponse
-} from '../types/alm-settings';
-
-export function isProjectBitbucketBindingResponse(
-  binding: ProjectAlmBindingResponse
-): binding is ProjectBitbucketBindingResponse {
-  return binding.alm === AlmKeys.Bitbucket;
-}
-
-export function isProjectGitHubBindingResponse(
-  binding: ProjectAlmBindingResponse
-): binding is ProjectGitHubBindingResponse {
-  return binding.alm === AlmKeys.GitHub;
-}
-
-export function isProjectGitLabBindingResponse(
-  binding: ProjectAlmBindingResponse
-): binding is ProjectGitLabBindingResponse {
-  return binding.alm === AlmKeys.GitLab;
-}
-
-export function isBitbucketBindingDefinition(
-  binding?: AlmBindingDefinition & { url?: string; personalAccessToken?: string }
-): binding is BitbucketBindingDefinition {
-  return (
-    binding !== undefined && binding.url !== undefined && binding.personalAccessToken !== undefined
-  );
-}
-
-export function isGithubBindingDefinition(
-  binding?: AlmBindingDefinition & { appId?: string; privateKey?: string; url?: string }
-): binding is GithubBindingDefinition {
-  return (
-    binding !== undefined &&
-    binding.appId !== undefined &&
-    binding.privateKey !== undefined &&
-    binding.url !== undefined
-  );
-}
index b4a67f72610ac9576afcd60d52525911e04068f4..b8de0c0aa214c2862283fe2928cfaccc7f3cb4c7 100644 (file)
@@ -27,6 +27,7 @@ import {
   GithubBindingDefinition,
   GitlabBindingDefinition,
   ProjectAlmBindingResponse,
+  ProjectAzureBindingResponse,
   ProjectBitbucketBindingResponse,
   ProjectGitHubBindingResponse,
   ProjectGitLabBindingResponse
@@ -132,6 +133,19 @@ export function mockProjectGitLabBindingResponse(
   };
 }
 
+export function mockProjectAzureBindingResponse(
+  overrides: Partial<ProjectAzureBindingResponse> = {}
+): ProjectAzureBindingResponse {
+  return {
+    alm: AlmKeys.Azure,
+    key: 'foo',
+    slug: 'PROJECT_NAME',
+    repository: 'REPOSITORY_NAME',
+    url: 'https://ado.my_company.com/mycollection',
+    ...overrides
+  };
+}
+
 export function mockAlmSettingsBindingStatus(
   overrides: Partial<AlmSettingsBindingStatus>
 ): AlmSettingsBindingStatus {
index c27018afb2518d55d6c71177e428baafd7a93187..c70a371b8784204471f515ddcc363fa24c2ea288 100644 (file)
@@ -60,6 +60,13 @@ export interface ProjectAlmBindingResponse {
   summaryCommentEnabled?: boolean;
 }
 
+export interface ProjectAzureBindingResponse extends ProjectAlmBindingResponse {
+  alm: AlmKeys.Azure;
+  repository: string;
+  slug: string;
+  url: string;
+}
+
 export interface ProjectBitbucketBindingResponse extends ProjectAlmBindingResponse {
   alm: AlmKeys.Bitbucket;
   repository: string;
@@ -126,3 +133,46 @@ export enum AlmSettingsBindingStatusType {
   Failure,
   Warning
 }
+
+export function isProjectBitbucketBindingResponse(
+  binding: ProjectAlmBindingResponse
+): binding is ProjectBitbucketBindingResponse {
+  return binding.alm === AlmKeys.Bitbucket;
+}
+
+export function isProjectGitHubBindingResponse(
+  binding: ProjectAlmBindingResponse
+): binding is ProjectGitHubBindingResponse {
+  return binding.alm === AlmKeys.GitHub;
+}
+
+export function isProjectGitLabBindingResponse(
+  binding: ProjectAlmBindingResponse
+): binding is ProjectGitLabBindingResponse {
+  return binding.alm === AlmKeys.GitLab;
+}
+
+export function isProjectAzureBindingResponse(
+  binding: ProjectAlmBindingResponse
+): binding is ProjectAzureBindingResponse {
+  return binding.alm === AlmKeys.Azure;
+}
+
+export function isBitbucketBindingDefinition(
+  binding?: AlmBindingDefinition & { url?: string; personalAccessToken?: string }
+): binding is BitbucketBindingDefinition {
+  return (
+    binding !== undefined && binding.url !== undefined && binding.personalAccessToken !== undefined
+  );
+}
+
+export function isGithubBindingDefinition(
+  binding?: AlmBindingDefinition & { appId?: string; privateKey?: string; url?: string }
+): binding is GithubBindingDefinition {
+  return (
+    binding !== undefined &&
+    binding.appId !== undefined &&
+    binding.privateKey !== undefined &&
+    binding.url !== undefined
+  );
+}
index 38cefcb7b2241a04382a9cb0e43cc64ef4fd709f..68cb4d8e9b48fe6ffe4b9e6b18a84a2866f26185 100644 (file)
@@ -3447,6 +3447,7 @@ 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.choose_method.gitlab_ci=With GitLab CI
+onboarding.tutorial.choose_method.azure_pipelines=With Azure Pipelines
 
 onboarding.tutorial.with.gitlab_ci.title=Analyze your project with GitLab CI
 onboarding.tutorial.with.gitlab_ci.unsupported=This tutorial is only available for projects bound to GitLab.
@@ -3598,6 +3599,63 @@ onboarding.tutorial.with.jenkins.commit.why=Each new push you make on your branc
 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.
 
+onboarding.tutorial.with.azure_pipelines.title=Analyze your project with Azure DevOps Pipelines
+onboarding.tutorial.with.azure_pipelines.unsupported=This tutorial is only available for projects bound to Azure DevOps Server.
+onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title=Install SonarQube extension for Azure DevOps Server
+onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence=From your Azure DevOps Server instance, navigate to the Visual Studio Marketplace and install the {link} by clicking the {button} button.
+onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence.link=SonarQube extension
+onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence.button=Get it free
+onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.title=Add a new SonarQube Service Endpoint
+onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step1.sentence=In Azure DevOps Server, go to {menu}
+onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step1.sentence.menu=Project settings > Service connections
+onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step2.sentence=Add a new service connection of type {type}
+onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step2.sentence.type=SonarQube
+onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step3.sentence=Enter a memorable connection name
+onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step4.sentence=Enter your SonarQube server url: {url} {button}
+onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step5.sentence=Enter an existing token, or a newly generated one
+onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step6.sentence=Create the service connection
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.title=Configure Branch analysis
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.sentence=In Azure DevOps Server, create or edit a {pipeline} and add a new {task} task {before} your build task
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.sentence.pipeline=Build Pipeline
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.sentence.task=Prepare Analysis Configuration
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.sentence.before=before
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.endpoint.sentence=Select the {endpoint} you created in Step 2
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.endpoint.sentence.endpoint=SonarQube server endpoint
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis=Under {section}, select {run_analysis_value}
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.section=Choose the way to run the analysis
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.values.dotnet=Integrate with MSBuild
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.values.maven=Integrate with Maven or Gradle
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.values.gradle=Integrate with Maven or Gradle
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.values.other=Use standalone scanner
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.manual.sentence=Select the {mode} mode
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.manual.sentence.mode=Manually provide configuration
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run.sentence=Add a new {task} task {after} your build task
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run.sentence.task=Run Code Analysis
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run.sentence.after=after
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run.key.sentence=In the project key field, enter {key} {button}
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.advanced_properties.sentence=Expand the {section} and replace the {properties} with the following snippet
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.advanced_properties.sentence.section=Advanced section
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.advanced_properties.sentence.properties=Additional Properties
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java=Edit or add a new {0} task
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java.settings.sentence=Under {section}, check {option}
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java.settings.sentence.section=Code Analysis
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java.settings.sentence.option=Run SonarQube or SonarCloud Analysis
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg.sentence=Add a new {task} task to publish SonarQube's Quality Gate results on your build pipeline summary
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg.info.sentence1=This task may increase your build time as your pipeline will have to wait for SonarQube to process the analysis report. It is highly recommended but optional.
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg.sentence.task=Publish Quality Gate Result
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration.sentence=Under the {tab} tab of your pipeline, check {continuous_integration} and select all the branches for which you want the SonarQube analysis to run automatically
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration.sentence.tab=Triggers
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration.sentence.continuous_integration=Enable continuous integration
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection=To make sure your Pull Requests are analyzed automatically and aren't merged when they're failing their quality gate, check out the {link}.
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection.link=documentation
+onboarding.tutorial.with.azure_pipelines.SaveAndRun.title=Save and run your pipeline
+onboarding.tutorial.with.azure_pipelines.SaveAndRun.commit=Save your pipeline and push your code to start the analysis
+onboarding.tutorial.with.azure_pipelines.SaveAndRun.commit.why=Each new push you make on your branches will trigger a new analysis in SonarQube
+onboarding.tutorial.with.azure_pipelines.SaveAndRun.refresh=This page will then refresh with your analysis results
+onboarding.tutorial.with.azure_pipelines.SaveAndRun.refresh.why=If this page doesn't refresh after a while, please double-check your pipeline configuration
+onboarding.tutorial.with.azure_pipelines.SaveAndRun.then={what} Checkout our {link} to make sure SonarQube analyze and decorate all of your Pull Requests
+onboarding.tutorial.with.azure_pipelines.SaveAndRun.then.what=Then what?
+onboarding.tutorial.with.azure_pipelines.SaveAndRun.then.link=Azure integration documentation
 #------------------------------------------------------------------------------
 #
 # BRANCHES