]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-13754 Add GitLab CI tutorial
authorJeremy Davis <jeremy.davis@sonarsource.com>
Wed, 26 Aug 2020 15:57:26 +0000 (17:57 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 3 Sep 2020 20:07:19 +0000 (20:07 +0000)
39 files changed:
server/sonar-docs/src/tooltips/tutorials/use-existing-token.md [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx
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/components/EditTokenModal.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/__tests__/EditTokenModal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/EditTokenModal-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/EnvironmentVariablesStep.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/GitLabCITutorial.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/ProjectKeyStep.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/YmlFileStep.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/EnvironmentVariablesStep-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/GitLabCITutorial-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/ProjectKeyStep-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/YmlFileStep-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/EnvironmentVariablesStep-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/GitLabCITutorial-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/ProjectKeyStep-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/YmlFileStep-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandGradle.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandMaven.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandOther.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandGradle-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandMaven-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandOther-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandGradle-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandMaven-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandOther-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/gitlabci/types.ts [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/styles.css [deleted file]
server/sonar-web/src/main/js/components/tutorials/types.ts
server/sonar-web/src/main/js/helpers/alm-settings.ts
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-docs/src/tooltips/tutorials/use-existing-token.md b/server/sonar-docs/src/tooltips/tutorials/use-existing-token.md
new file mode 100644 (file)
index 0000000..5c7c31f
--- /dev/null
@@ -0,0 +1,5 @@
+Paste an existing token value into the input field.
+
+---
+
+See also: [User Token](/user-guide/user-token/)
index ec09e6c5e31ad72346f0c1298425ce88d0888b84..6e639e557bfbe90974c943925c18b08a94199efe 100644 (file)
@@ -22,7 +22,6 @@ import { Button } from 'sonar-ui-common/components/controls/buttons';
 import OnboardingAddMembersIcon from 'sonar-ui-common/components/icons/OnboardingAddMembersIcon';
 import { translate } from 'sonar-ui-common/helpers/l10n';
 import { Router, withRouter } from '../../../components/hoc/withRouter';
-import '../../../components/tutorials/styles.css';
 import './OrganizationEmpty.css';
 
 interface Props {
index 865d08766b90ccd2ce8b96bffd6910e99b1168a2..a077d399d1a5d545dd43926a069dce3f48b244f0 100644 (file)
@@ -22,7 +22,6 @@ 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';
 
@@ -59,10 +58,10 @@ export class TutorialSelection extends React.PureComponent<Props, State> {
     ]);
 
     if (this.mounted) {
-      // We only support Bitbucket & GitHub for now.
+      // We only support Bitbucket, GitHub & Gitlab for now.
       if (
         projectBinding === undefined ||
-        (projectBinding.alm !== AlmKeys.Bitbucket && projectBinding.alm !== AlmKeys.GitHub)
+        ![AlmKeys.Bitbucket, AlmKeys.GitHub, AlmKeys.GitLab].includes(projectBinding.alm)
       ) {
         this.setState({ loading: false, forceManual: true });
       } else {
index ca68491326ee3e4626cc8e271bd81abfa61474a1..1fc339d8dcf8edf0c712823e93e7f8c6109b8ccd 100644 (file)
@@ -21,7 +21,8 @@
 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 { AlmBindingDefinition, AlmKeys, ProjectAlmBindingResponse } from '../../types/alm-settings';
+import GitLabCITutorial from './gitlabci/GitLabCITutorial';
 import JenkinsTutorial from './jenkins/JenkinsTutorial';
 import ManualTutorial from './manual/ManualTutorial';
 import { TutorialModes } from './types';
@@ -43,6 +44,9 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
     return <i className="spinner" />;
   }
 
+  const jenkinsAvailable =
+    projectBinding && [AlmKeys.Bitbucket, AlmKeys.GitHub].includes(projectBinding.alm);
+
   return (
     <>
       {selectedTutorial === undefined && (
@@ -53,23 +57,41 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
             </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>
+          <div className="display-flex-justify-center">
+            {projectBinding?.alm === AlmKeys.GitLab && (
+              <button
+                className="button button-huge display-flex-column spacer-left spacer-right tutorial-mode-gitlab"
+                onClick={() => props.onSelectTutorial(TutorialModes.GitLabCI)}
+                type="button">
+                <img
+                  alt="" // Should be ignored by screen readers
+                  height={80}
+                  src={`${getBaseUrl()}/images/alm/gitlab.svg`}
+                />
+                <div className="medium big-spacer-top">
+                  {translate('onboarding.tutorial.choose_method.gitlab_ci')}
+                </div>
+              </button>
+            )}
+
+            {jenkinsAvailable && (
+              <button
+                className="button button-huge display-flex-column spacer-left spacer-right 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"
+              className="button button-huge display-flex-column spacer-left spacer-right tutorial-mode-manual"
               onClick={() => props.onSelectTutorial(TutorialModes.Manual)}
               type="button">
               <img
@@ -96,6 +118,14 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
           projectBinding={projectBinding}
         />
       )}
+
+      {selectedTutorial === TutorialModes.GitLabCI && projectBinding !== undefined && (
+        <GitLabCITutorial
+          component={component}
+          currentUser={currentUser}
+          projectBinding={projectBinding}
+        />
+      )}
     </>
   );
 }
index 772907017366dd93316f0eaabaccbb42e4aedd05..06ba00709789467aec087f59e1c41bf499e10793 100644 (file)
@@ -57,8 +57,8 @@ it('should not select anything if project is bound to Bitbucket', async () => {
   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 });
+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);
index f393bcb45c6110150c394fdd67ac40bb875e8ecb..bd45cbac9c186e161b31eaaa9acfabe0b5e2baf9 100644 (file)
@@ -23,7 +23,8 @@ import * as React from 'react';
 import { click } from 'sonar-ui-common/helpers/testUtils';
 import {
   mockBitbucketBindingDefinition,
-  mockProjectBitbucketBindingResponse
+  mockProjectBitbucketBindingResponse,
+  mockProjectGitLabBindingResponse
 } from '../../../helpers/mocks/alm-settings';
 import { mockComponent, mockLoggedInUser } from '../../../helpers/testMocks';
 import TutorialSelectionRenderer, {
@@ -33,6 +34,9 @@ import { TutorialModes } from '../types';
 
 it('should render correctly', () => {
   expect(shallowRender()).toMatchSnapshot('selection');
+  expect(shallowRender({ projectBinding: mockProjectBitbucketBindingResponse() })).toMatchSnapshot(
+    'selection with jenkins available'
+  );
   expect(shallowRender({ loading: true })).toMatchSnapshot('loading');
   expect(shallowRender({ selectedTutorial: TutorialModes.Manual })).toMatchSnapshot(
     'manual tutorial'
@@ -43,11 +47,20 @@ it('should render correctly', () => {
       projectBinding: mockProjectBitbucketBindingResponse()
     })
   ).toMatchSnapshot('jenkins tutorial');
+  expect(
+    shallowRender({
+      selectedTutorial: TutorialModes.GitLabCI,
+      projectBinding: mockProjectGitLabBindingResponse()
+    })
+  ).toMatchSnapshot('gitlab tutorial');
 });
 
 it('should allow mode selection', () => {
   const onSelectTutorial = jest.fn();
-  const wrapper = shallowRender({ onSelectTutorial });
+  const wrapper = shallowRender({
+    onSelectTutorial,
+    projectBinding: mockProjectBitbucketBindingResponse()
+  });
 
   click(wrapper.find('button.tutorial-mode-jenkins'));
   expect(onSelectTutorial).toHaveBeenLastCalledWith(TutorialModes.Jenkins);
@@ -56,6 +69,17 @@ it('should allow mode selection', () => {
   expect(onSelectTutorial).toHaveBeenLastCalledWith(TutorialModes.Manual);
 });
 
+it('should allow gitlab selection', () => {
+  const onSelectTutorial = jest.fn();
+  const wrapper = shallowRender({
+    onSelectTutorial,
+    projectBinding: mockProjectGitLabBindingResponse()
+  });
+
+  click(wrapper.find('button.tutorial-mode-gitlab'));
+  expect(onSelectTutorial).toHaveBeenLastCalledWith(TutorialModes.GitLabCI);
+});
+
 function shallowRender(props: Partial<TutorialSelectionRendererProps> = {}) {
   return shallow<TutorialSelectionRendererProps>(
     <TutorialSelectionRenderer
index c73bf30606cbc3f78fd7315201ec713786574734..ef26e8102911e9208ac49d7c2b4b24eb4b3c9d8e 100644 (file)
@@ -1,5 +1,52 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`should render correctly: gitlab tutorial 1`] = `
+<Fragment>
+  <GitLabCITutorial
+    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": "gitlab",
+        "key": "foo",
+        "repository": "PROJECT_KEY",
+        "url": "https://gitlab.com/api/v4",
+      }
+    }
+  />
+</Fragment>
+`;
+
 exports[`should render correctly: jenkins tutorial 1`] = `
 <Fragment>
   <Connect(JenkinsTutorial)
@@ -105,10 +152,48 @@ exports[`should render correctly: selection 1`] = `
       </h1>
     </header>
     <div
-      className="display-flex-space-around"
+      className="display-flex-justify-center"
+    >
+      <button
+        className="button button-huge display-flex-column spacer-left spacer-right 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>
+`;
+
+exports[`should render correctly: selection with jenkins available 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-justify-center"
     >
       <button
-        className="button button-huge display-flex-column tutorial-mode-jenkins"
+        className="button button-huge display-flex-column spacer-left spacer-right tutorial-mode-jenkins"
         onClick={[Function]}
         type="button"
       >
@@ -124,7 +209,7 @@ exports[`should render correctly: selection 1`] = `
         </div>
       </button>
       <button
-        className="button button-huge display-flex-column tutorial-mode-manual"
+        className="button button-huge display-flex-column spacer-left spacer-right tutorial-mode-manual"
         onClick={[Function]}
         type="button"
       >
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/EditTokenModal.tsx b/server/sonar-web/src/main/js/components/tutorials/components/EditTokenModal.tsx
new file mode 100644 (file)
index 0000000..713a5f0
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * 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, DeleteButton } from 'sonar-ui-common/components/controls/buttons';
+import { ClipboardIconButton } from 'sonar-ui-common/components/controls/clipboard';
+import SimpleModal from 'sonar-ui-common/components/controls/SimpleModal';
+import { Alert } from 'sonar-ui-common/components/ui/Alert';
+import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
+import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
+import { generateToken, getTokens, revokeToken } from '../../../api/user-tokens';
+import { getUniqueTokenName } from '../utils';
+
+interface State {
+  loading: boolean;
+  token?: string;
+  tokenName: string;
+}
+
+interface Props {
+  component: T.Component;
+  currentUser: T.LoggedInUser;
+  onClose: (token?: string) => void;
+}
+
+export default class EditTokenModal extends React.PureComponent<Props, State> {
+  mounted = false;
+  state: State = {
+    loading: true,
+    tokenName: ''
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.getTokensAndName();
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  getTokensAndName = async () => {
+    const { component, currentUser } = this.props;
+
+    const tokens = await getTokens(currentUser.login);
+
+    if (this.mounted) {
+      this.setState({
+        loading: false,
+        tokenName: getUniqueTokenName(tokens, `Analyze "${component.name}"`)
+      });
+    }
+  };
+
+  getNewToken = async () => {
+    const { tokenName } = this.state;
+
+    const { token } = await generateToken({ name: tokenName });
+
+    if (this.mounted) {
+      this.setState({
+        token,
+        tokenName
+      });
+    }
+  };
+
+  handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    this.setState({
+      tokenName: event.target.value
+    });
+  };
+
+  handleTokenRevoke = async () => {
+    const { tokenName } = this.state;
+
+    if (tokenName) {
+      await revokeToken({ name: tokenName });
+
+      if (this.mounted) {
+        this.setState({
+          token: '',
+          tokenName: ''
+        });
+      }
+    }
+  };
+
+  render() {
+    const { loading, token, tokenName } = this.state;
+
+    const header = translate('onboarding.token.generate_token');
+
+    return (
+      <SimpleModal header={header} onClose={this.props.onClose} onSubmit={this.props.onClose}>
+        {({ onCloseClick }) => (
+          <>
+            <div className="modal-head">
+              <h2>{header}</h2>
+            </div>
+
+            <div className="modal-body">
+              <p className="spacer-bottom">
+                <FormattedMessage
+                  defaultMessage={translate('onboarding.token.text')}
+                  id="onboarding.token.text"
+                  values={{
+                    link: (
+                      <Link target="_blank" to="/account/security">
+                        {translate('onboarding.token.text.user_account')}
+                      </Link>
+                    )
+                  }}
+                />
+              </p>
+
+              {token ? (
+                <>
+                  <span className="text-middle">
+                    {tokenName}
+                    {': '}
+                  </span>
+                  <div className="display-float-center">
+                    <code className="rule spacer-right">{token}</code>
+
+                    <ClipboardIconButton copyValue={token} />
+
+                    <DeleteButton onClick={this.handleTokenRevoke} />
+                  </div>
+
+                  <Alert className="big-spacer-top" variant="warning">
+                    {translateWithParameters('users.tokens.new_token_created', token)}
+                  </Alert>
+                </>
+              ) : (
+                <div className="big-spacer-top">
+                  {loading ? (
+                    <DeferredSpinner />
+                  ) : (
+                    <>
+                      <input
+                        className="input-super-large spacer-right text-middle"
+                        onChange={this.handleChange}
+                        placeholder={translate('onboarding.token.generate_token.placeholder')}
+                        required={true}
+                        type="text"
+                        value={tokenName}
+                      />
+                      <Button
+                        className="text-middle"
+                        disabled={!tokenName}
+                        onClick={this.getNewToken}>
+                        {translate('onboarding.token.generate')}
+                      </Button>
+                    </>
+                  )}
+                </div>
+              )}
+            </div>
+            <div className="modal-foot">
+              <Button onClick={onCloseClick}>{translate('continue')}</Button>
+            </div>
+          </>
+        )}
+      </SimpleModal>
+    );
+  }
+}
index 5e150f8b666e24c09a7abcaa00720d6b2c973d54..a89e4fcded8f6519d5c5fb324f3e888b97f4ea75 100644 (file)
 
 .onboarding-step ol.list-styled > li {
   position: relative;
-  counter-increment: step-counter;
+  counter-increment: li;
   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%;
+  display: inline-block;
   width: 16px;
-  position: absolute;
-  left: 0;
-  font-size: 10px;
   height: 16px;
-  justify-content: center;
-  align-items: center;
+  margin-right: 8px;
+  color: #fff;
+  font-size: 12px;
+  line-height: 16px;
+  direction: rtl;
+  text-align: center;
+  background-color: #4b9fd5;
+  border-radius: 50%;
+  content: counter(li);
 }
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/__tests__/EditTokenModal-test.tsx b/server/sonar-web/src/main/js/components/tutorials/components/__tests__/EditTokenModal-test.tsx
new file mode 100644 (file)
index 0000000..0146b98
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * 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 { generateToken, getTokens, revokeToken } from '../../../../api/user-tokens';
+import { mockComponent, mockEvent, mockLoggedInUser } from '../../../../helpers/testMocks';
+import { getUniqueTokenName } from '../../utils';
+import EditTokenModal from '../EditTokenModal';
+
+jest.mock('../../../../api/user-tokens', () => ({
+  generateToken: jest.fn().mockResolvedValue({
+    name: 'baz',
+    createdAt: '2019-01-21T08:06:00+0100',
+    login: 'luke',
+    token: 'token_value'
+  }),
+  getTokens: jest.fn().mockResolvedValue([
+    {
+      name: 'foo',
+      createdAt: '2019-01-15T15:06:33+0100',
+      lastConnectionDate: '2019-01-18T15:06:33+0100'
+    },
+    { name: 'bar', createdAt: '2019-01-18T15:06:33+0100' }
+  ]),
+  revokeToken: jest.fn().mockResolvedValue(Promise.resolve())
+}));
+
+jest.mock('../../utils', () => ({
+  getUniqueTokenName: jest.fn().mockReturnValue('lightsaber-9000')
+}));
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should get tokens and unique name', async () => {
+  const wrapper = shallowRender();
+  const { getTokensAndName } = wrapper.instance();
+
+  getTokensAndName();
+  await waitAndUpdate(wrapper);
+
+  expect(getTokens).toHaveBeenCalled();
+  expect(getUniqueTokenName).toHaveBeenCalled();
+  expect(wrapper.state('tokenName')).toBe('lightsaber-9000');
+});
+
+it('should get a new token', async () => {
+  const wrapper = shallowRender();
+  const { getNewToken } = wrapper.instance();
+
+  getNewToken();
+  await waitAndUpdate(wrapper);
+
+  expect(generateToken).toHaveBeenCalled();
+  expect(wrapper.state('token')).toBe('token_value');
+});
+
+it('should handle token revocation', async () => {
+  const wrapper = shallowRender();
+  const { getTokensAndName, handleTokenRevoke } = wrapper.instance();
+
+  getTokensAndName();
+  await waitAndUpdate(wrapper);
+  handleTokenRevoke();
+  await waitAndUpdate(wrapper);
+
+  expect(revokeToken).toHaveBeenCalled();
+  expect(wrapper.state('token')).toBe('');
+  expect(wrapper.state('tokenName')).toBe('');
+});
+
+it('should handle change on user input', () => {
+  const wrapper = shallowRender();
+  const instance = wrapper.instance();
+
+  instance.handleChange(mockEvent({ target: { value: 'my-token' } }));
+  expect(wrapper.state('tokenName')).toBe('my-token');
+});
+
+function shallowRender(props: Partial<EditTokenModal['props']> = {}) {
+  return shallow<EditTokenModal>(
+    <EditTokenModal
+      component={mockComponent()}
+      currentUser={mockLoggedInUser()}
+      onClose={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/EditTokenModal-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/EditTokenModal-test.tsx.snap
new file mode 100644 (file)
index 0000000..6be1acf
--- /dev/null
@@ -0,0 +1,11 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<SimpleModal
+  header="onboarding.token.generate_token"
+  onClose={[MockFunction]}
+  onSubmit={[MockFunction]}
+>
+  <Component />
+</SimpleModal>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/EnvironmentVariablesStep.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/EnvironmentVariablesStep.tsx
new file mode 100644 (file)
index 0000000..884899d
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * 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 Step from '../components/Step';
+
+export interface EnvironmentVariablesStepProps {
+  component: T.Component;
+  currentUser: T.LoggedInUser;
+  finished: boolean;
+  onDone: () => void;
+  onOpen: () => void;
+  open: boolean;
+}
+
+const pipelineDescriptionLinkLabel = translate(
+  'onboarding.tutorial.with.gitlab_ci.env_variables.description.link'
+);
+
+export default function EnvironmentVariablesStep(props: EnvironmentVariablesStepProps) {
+  const { component, currentUser, finished, open } = props;
+
+  const [isModalVisible, toggleModal] = React.useState(false);
+
+  const toggleTokenModal = () => toggleModal(!isModalVisible);
+  const closeTokenModal = () => toggleModal(false);
+
+  const fieldValueTranslation = translate(
+    'onboarding.tutorial.with.gitlab_ci.env_variables.enter_field_value'
+  );
+
+  const renderForm = () => (
+    <div className="boxed-group-inner">
+      <h2 className="spacer-bottom spacer-top">
+        {translate('onboarding.tutorial.with.gitlab_ci.env_variables.section.title')}
+      </h2>
+
+      <FormattedMessage
+        defaultMessage={translate(
+          'onboarding.tutorial.with.gitlab_ci.env_variables.section.description'
+        )}
+        id="onboarding.tutorial.with.gitlab_ci.env_variables.section.description"
+        values={{
+          /* This link will be added when the backend provides the project URL */
+          link: <strong>{pipelineDescriptionLinkLabel}</strong>
+        }}
+      />
+
+      <ol className="list-styled big-spacer-top">
+        <li className="big-spacer-bottom">
+          <FormattedMessage
+            defaultMessage={fieldValueTranslation}
+            id="onboarding.tutorial.with.gitlab_ci.env_variables.step1"
+            values={{
+              extra: <ClipboardIconButton copyValue="SONAR_TOKEN" />,
+              field: translate('onboarding.tutorial.with.gitlab_ci.env_variables.step1'),
+              value: <code className="rule">SONAR_TOKEN</code>
+            }}
+          />
+        </li>
+        <li className="big-spacer-bottom">
+          <FormattedMessage
+            defaultMessage={fieldValueTranslation}
+            id="onboarding.tutorial.with.gitlab_ci.env_variables.step2"
+            values={{
+              extra: (
+                <Button className="spacer-left" onClick={toggleTokenModal}>
+                  {translate('onboarding.token.generate_token')}
+                </Button>
+              ),
+              field: translate('onboarding.tutorial.with.gitlab_ci.env_variables.step2'),
+              value: translate(
+                'onboarding.tutorial.with.gitlab_ci.env_variables.section.step2.value'
+              )
+            }}
+          />
+        </li>
+        <li className="big-spacer-bottom">
+          {translate('onboarding.tutorial.with.gitlab_ci.env_variables.step3')}
+        </li>
+        <li className="big-spacer-bottom">
+          {translate('onboarding.tutorial.with.gitlab_ci.env_variables.section.step4')}
+        </li>
+      </ol>
+
+      <hr className="no-horizontal-margins" />
+
+      <h2 className="spacer-bottom big-spacer-top">
+        {translate('onboarding.tutorial.with.gitlab_ci.env_variables.section2.title')}
+      </h2>
+
+      <FormattedMessage
+        defaultMessage={translate(
+          'onboarding.tutorial.with.gitlab_ci.env_variables.section2.description'
+        )}
+        id="onboarding.tutorial.with.gitlab_ci.env_variables.section2.description"
+        values={{
+          /* This link will be added when the backend provides the project URL */
+          link: <strong>{pipelineDescriptionLinkLabel}</strong>
+        }}
+      />
+
+      <ol className="list-styled big-spacer-top big-spacer-bottom">
+        <li className="big-spacer-bottom">
+          <FormattedMessage
+            defaultMessage={fieldValueTranslation}
+            id="onboarding.tutorial.with.gitlab_ci.env_variables.step1"
+            values={{
+              extra: <ClipboardIconButton copyValue="SONAR_HOST_URL" />,
+              field: translate('onboarding.tutorial.with.gitlab_ci.env_variables.step1'),
+              value: <code className="rule">SONAR_HOST_URL</code>
+            }}
+          />
+        </li>
+        <li className="big-spacer-bottom">
+          <FormattedMessage
+            defaultMessage={fieldValueTranslation}
+            id="onboarding.tutorial.with.gitlab_ci.env_variables.step2"
+            values={{
+              extra: <ClipboardIconButton copyValue={getHostUrl()} />,
+              field: translate('onboarding.tutorial.with.gitlab_ci.env_variables.step2'),
+              value: <code className="rule">{getHostUrl()}</code>
+            }}
+          />
+        </li>
+        <li className="big-spacer-bottom">
+          {translate('onboarding.tutorial.with.gitlab_ci.env_variables.step3')}
+        </li>
+        <li className="big-spacer-bottom">
+          {translate('onboarding.tutorial.with.gitlab_ci.env_variables.section2.step4')}
+        </li>
+      </ol>
+
+      <Button className="big-spacer-bottom" onClick={props.onDone}>
+        {translate('continue')}
+      </Button>
+    </div>
+  );
+
+  return (
+    <>
+      {isModalVisible && (
+        <EditTokenModal component={component} currentUser={currentUser} onClose={closeTokenModal} />
+      )}
+
+      <Step
+        finished={finished}
+        onOpen={props.onOpen}
+        open={open}
+        renderForm={renderForm}
+        stepNumber={2}
+        stepTitle={translate('onboarding.tutorial.with.gitlab_ci.env_variables.title')}
+      />
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/GitLabCITutorial.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/GitLabCITutorial.tsx
new file mode 100644 (file)
index 0000000..8d4dee3
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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 { isProjectGitLabBindingResponse } from '../../../helpers/alm-settings';
+import { ProjectAlmBindingResponse } from '../../../types/alm-settings';
+import EnvironmentVariablesStep from './EnvironmentVariablesStep';
+import ProjectKeyStep from './ProjectKeyStep';
+import { BuildTools } from './types';
+import YmlFileStep from './YmlFileStep';
+
+export enum Steps {
+  PROJECT_KEY,
+  ENV_VARIABLES,
+  YML
+}
+
+export interface GitLabCITutorialProps {
+  component: T.Component;
+  currentUser: T.LoggedInUser;
+  projectBinding: ProjectAlmBindingResponse;
+}
+
+export default function GitLabCITutorial(props: GitLabCITutorialProps) {
+  const { component, currentUser, projectBinding } = props;
+
+  const [step, setStep] = React.useState(Steps.PROJECT_KEY);
+  const [buildTool, setBuildTool] = React.useState<BuildTools | undefined>();
+
+  // Failsafe; should never happen.
+  if (!isProjectGitLabBindingResponse(projectBinding)) {
+    return (
+      <Alert variant="error">{translate('onboarding.tutorial.with.gitlab_ci.unsupported')}</Alert>
+    );
+  }
+
+  return (
+    <>
+      <div className="page-header big-spacer-bottom">
+        <h1 className="page-title">{translate('onboarding.tutorial.with.gitlab_ci.title')}</h1>
+      </div>
+
+      <ProjectKeyStep
+        buildTool={buildTool}
+        component={component}
+        finished={step > Steps.PROJECT_KEY}
+        onDone={() => setStep(Steps.ENV_VARIABLES)}
+        onOpen={() => setStep(Steps.PROJECT_KEY)}
+        open={step === Steps.PROJECT_KEY}
+        setBuildTool={setBuildTool}
+      />
+
+      <EnvironmentVariablesStep
+        component={component}
+        currentUser={currentUser}
+        finished={step > Steps.ENV_VARIABLES}
+        onDone={() => setStep(Steps.YML)}
+        onOpen={() => setStep(Steps.ENV_VARIABLES)}
+        open={step === Steps.ENV_VARIABLES}
+      />
+
+      <YmlFileStep buildTool={buildTool} open={step === Steps.YML} />
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/ProjectKeyStep.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/ProjectKeyStep.tsx
new file mode 100644 (file)
index 0000000..6583a3d
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * 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 CodeSnippet from '../../common/CodeSnippet';
+import RenderOptions from '../components/RenderOptions';
+import Step from '../components/Step';
+import { BuildTools } from './types';
+
+export interface ProjectKeyStepProps {
+  buildTool?: BuildTools;
+  component: T.Component;
+  finished: boolean;
+  onDone: () => void;
+  onOpen: () => void;
+  open: boolean;
+  setBuildTool: (tool: BuildTools) => void;
+}
+
+const mavenSnippet = (key: string) => `<properties>
+  <sonar.projectKey>${key}</sonar.projectKey>
+  <sonar.qualitygate.wait>true</sonar.qualitygate.wait>
+</properties>`;
+
+const gradleSnippet = (key: string) => `plugins {
+  id "org.sonarqube" version "3.0"
+}
+
+sonarqube {
+  properties {
+    property "sonar.projectKey", "${key}"
+    property "sonar.qualitygate.wait", true 
+  }
+}`;
+
+const otherSnippet = (key: string) => `sonar.projectKey=${key}
+sonar.qualitygate.wait=true
+`;
+
+const snippetForBuildTool = {
+  [BuildTools.Maven]: mavenSnippet,
+  [BuildTools.Gradle]: gradleSnippet,
+  [BuildTools.Other]: otherSnippet
+};
+
+const filenameForBuildTool = {
+  [BuildTools.Maven]: 'pom.xml',
+  [BuildTools.Gradle]: 'build.gradle',
+  [BuildTools.Other]: 'sonar-project.properties'
+};
+
+export default function ProjectKeyStep(props: ProjectKeyStepProps) {
+  const { buildTool, component, finished, open } = props;
+
+  const renderForm = () => (
+    <div className="boxed-group-inner">
+      <ol className="list-styled">
+        <li>
+          {translate('onboarding.build')}
+          <RenderOptions
+            checked={buildTool}
+            name="buildtool"
+            onCheck={value => props.setBuildTool(value as BuildTools)}
+            optionLabelKey="onboarding.build"
+            options={Object.values(BuildTools)}
+          />
+        </li>
+        {buildTool !== undefined && (
+          <li className="abs-width-600">
+            <FormattedMessage
+              defaultMessage={translate(
+                `onboarding.tutorial.with.gitlab_ci.project_key.${buildTool}.step2`
+              )}
+              id={`onboarding.tutorial.with.gitlab_ci.project_key.${buildTool}.step2`}
+              values={{
+                file: (
+                  <>
+                    <code className="rule">{filenameForBuildTool[buildTool]}</code>
+                    <ClipboardIconButton
+                      className="little-spacer-left"
+                      copyValue={filenameForBuildTool[buildTool]}
+                    />
+                  </>
+                )
+              }}
+            />
+            <CodeSnippet snippet={snippetForBuildTool[buildTool](component.key)} />
+          </li>
+        )}
+      </ol>
+      {buildTool !== undefined && <Button onClick={props.onDone}>{translate('continue')}</Button>}
+    </div>
+  );
+
+  return (
+    <Step
+      finished={finished}
+      onOpen={props.onOpen}
+      open={open}
+      renderForm={renderForm}
+      stepNumber={1}
+      stepTitle={translate('onboarding.tutorial.with.gitlab_ci.project_key.title')}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/YmlFileStep.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/YmlFileStep.tsx
new file mode 100644 (file)
index 0000000..7985b7d
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * 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 { translate } from 'sonar-ui-common/helpers/l10n';
+import Step from '../components/Step';
+import PipeCommandGradle from './commands/PipeCommandGradle';
+import PipeCommandMaven from './commands/PipeCommandMaven';
+import PipeCommandOther from './commands/PipeCommandOther';
+import { BuildTools } from './types';
+
+export interface YmlFileStepProps {
+  buildTool?: BuildTools;
+  open: boolean;
+}
+
+export default function YmlFileStep({ buildTool, open }: YmlFileStepProps) {
+  const renderForm = () => (
+    <div className="boxed-group-inner">
+      <div className="flex-columns">
+        <div className="flex-column-full">
+          {buildTool && (
+            <>
+              <div className="big-spacer-bottom">
+                <FormattedMessage
+                  defaultMessage={translate('onboarding.tutorial.with.gitlab_ci.yml.description')}
+                  id="onboarding.tutorial.with.gitlab_ci.yml.description"
+                  values={{
+                    filename: (
+                      <>
+                        <code className="rule">
+                          {translate('onboarding.tutorial.with.gitlab_ci.yml.filename')}
+                        </code>
+                        <ClipboardIconButton
+                          className="little-spacer-left"
+                          copyValue={translate('onboarding.tutorial.with.gitlab_ci.yml.filename')}
+                        />
+                      </>
+                    )
+                  }}
+                />
+              </div>
+
+              <div className="big-spacer-bottom">
+                {buildTool === BuildTools.Maven && <PipeCommandMaven />}
+                {buildTool === BuildTools.Gradle && <PipeCommandGradle />}
+                {buildTool === BuildTools.Other && <PipeCommandOther />}
+              </div>
+
+              <p className="little-spacer-bottom">
+                {translate('onboarding.tutorial.with.gitlab_ci.yml.baseconfig')}
+              </p>
+
+              <p className="huge-spacer-bottom">
+                {translate('onboarding.tutorial.with.gitlab_ci.yml.existing')}
+              </p>
+
+              <hr className="no-horizontal-margins" />
+              <div>
+                <p className="big-spacer-bottom">
+                  <strong>{translate('onboarding.tutorial.with.gitlab_ci.yml.done')} </strong>{' '}
+                  <FormattedMessage
+                    defaultMessage={translate(
+                      'onboarding.tutorial.with.gitlab_ci.yml.done.description'
+                    )}
+                    id="onboarding.tutorial.with.gitlab_ci.yml.done.description"
+                    values={{
+                      /* This link will be added when the backend provides the project URL */
+                      link: translate(
+                        'onboarding.tutorial.with.gitlab_ci.yml.done.description.link'
+                      )
+                    }}
+                  />
+                </p>
+                <p className="big-spacer-bottom">
+                  <strong>
+                    {translate('onboarding.tutorial.with.gitlab_ci.yml.done.then-what')}
+                  </strong>{' '}
+                  {translate('onboarding.tutorial.with.gitlab_ci.yml.done.then-what.description')}
+                </p>
+
+                <p className="big-spacer-bottom">
+                  <FormattedMessage
+                    defaultMessage={translate(
+                      'onboarding.tutorial.with.gitlab_ci.yml.done.links.title'
+                    )}
+                    id="onboarding.tutorial.with.gitlab_ci.yml.done.links.title"
+                    values={{
+                      links: (
+                        <Link
+                          rel="noopener noreferrer"
+                          target="_blank"
+                          to="/documentation/user-guide/quality-gates/">
+                          {translate('onboarding.tutorial.with.gitlab_ci.yml.done.links.QG')}
+                        </Link>
+                      )
+                    }}
+                  />
+                </p>
+              </div>
+            </>
+          )}
+        </div>
+      </div>
+    </div>
+  );
+
+  return (
+    <Step
+      finished={false}
+      open={open}
+      renderForm={renderForm}
+      stepNumber={3}
+      stepTitle={translate('onboarding.tutorial.with.gitlab_ci.yml.title')}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/EnvironmentVariablesStep-test.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/EnvironmentVariablesStep-test.tsx
new file mode 100644 (file)
index 0000000..9ca6c15
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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, mockLoggedInUser } from '../../../../helpers/testMocks';
+import { renderStepContent } from '../../jenkins/test-utils';
+import EnvironmentVariablesStep, {
+  EnvironmentVariablesStepProps
+} from '../EnvironmentVariablesStep';
+
+it('should render correctly', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot('Step wrapper');
+  expect(renderStepContent(wrapper)).toMatchSnapshot('initial content');
+});
+
+function shallowRender(props: Partial<EnvironmentVariablesStepProps> = {}) {
+  return shallow<EnvironmentVariablesStepProps>(
+    <EnvironmentVariablesStep
+      currentUser={mockLoggedInUser()}
+      component={mockComponent()}
+      finished={false}
+      onDone={jest.fn()}
+      onOpen={jest.fn()}
+      open={true}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/GitLabCITutorial-test.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/GitLabCITutorial-test.tsx
new file mode 100644 (file)
index 0000000..613224d
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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 {
+  mockProjectGithubBindingResponse,
+  mockProjectGitLabBindingResponse
+} from '../../../../helpers/mocks/alm-settings';
+import { mockComponent, mockLoggedInUser } from '../../../../helpers/testMocks';
+import GitLabCITutorial, { GitLabCITutorialProps } from '../GitLabCITutorial';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+  expect(shallowRender({ projectBinding: mockProjectGithubBindingResponse() })).toMatchSnapshot(
+    'wrong alm'
+  );
+});
+
+function shallowRender(props: Partial<GitLabCITutorialProps> = {}) {
+  return shallow<GitLabCITutorialProps>(
+    <GitLabCITutorial
+      component={mockComponent()}
+      currentUser={mockLoggedInUser()}
+      projectBinding={mockProjectGitLabBindingResponse()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/ProjectKeyStep-test.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/ProjectKeyStep-test.tsx
new file mode 100644 (file)
index 0000000..ea419b7
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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 { renderStepContent } from '../../jenkins/test-utils';
+import ProjectKeyStep, { ProjectKeyStepProps } from '../ProjectKeyStep';
+import { BuildTools } from '../types';
+
+it('should render correctly', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot('Step wrapper');
+  expect(renderStepContent(wrapper)).toMatchSnapshot('initial content');
+});
+
+it.each([[BuildTools.Maven], [BuildTools.Gradle], [BuildTools.Other]])(
+  'should render correctly for build tool %s',
+  buildTool => {
+    expect(renderStepContent(shallowRender({ buildTool }))).toMatchSnapshot();
+  }
+);
+
+it('should correctly callback with selected build tool', () => {
+  const setBuildTool = jest.fn();
+  const wrapper = shallowRender({ setBuildTool });
+  selectBuildTool(wrapper, BuildTools.Maven);
+
+  expect(setBuildTool).toBeCalledWith(BuildTools.Maven);
+});
+
+function selectBuildTool(wrapper: ShallowWrapper<ProjectKeyStepProps>, tool: BuildTools) {
+  const content = new ShallowWrapper(renderStepContent(wrapper) as JSX.Element);
+  content
+    .find(RenderOptions)
+    .props()
+    .onCheck(tool);
+}
+
+function shallowRender(props: Partial<ProjectKeyStepProps> = {}) {
+  return shallow<ProjectKeyStepProps>(
+    <ProjectKeyStep
+      component={mockComponent()}
+      finished={false}
+      onDone={jest.fn()}
+      onOpen={jest.fn()}
+      open={true}
+      setBuildTool={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/YmlFileStep-test.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/YmlFileStep-test.tsx
new file mode 100644 (file)
index 0000000..2e7e099
--- /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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { renderStepContent } from '../../jenkins/test-utils';
+import { BuildTools } from '../types';
+import YmlFileStep, { YmlFileStepProps } from '../YmlFileStep';
+
+it('should render correctly', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot('Step wrapper');
+  expect(renderStepContent(wrapper)).toMatchSnapshot('initial content');
+});
+
+it.each([[BuildTools.Maven], [BuildTools.Gradle], [BuildTools.Other]])(
+  'should render correctly for build tool %s',
+  buildTool => {
+    expect(renderStepContent(shallowRender({ buildTool }))).toMatchSnapshot();
+  }
+);
+
+function shallowRender(props: Partial<YmlFileStepProps> = {}) {
+  return shallow<YmlFileStepProps>(<YmlFileStep open={true} {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/EnvironmentVariablesStep-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/EnvironmentVariablesStep-test.tsx.snap
new file mode 100644 (file)
index 0000000..d37c402
--- /dev/null
@@ -0,0 +1,173 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: Step wrapper 1`] = `
+<Fragment>
+  <Step
+    finished={false}
+    onOpen={[MockFunction]}
+    open={true}
+    renderForm={[Function]}
+    stepNumber={2}
+    stepTitle="onboarding.tutorial.with.gitlab_ci.env_variables.title"
+  />
+</Fragment>
+`;
+
+exports[`should render correctly: initial content 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <h2
+    className="spacer-bottom spacer-top"
+  >
+    onboarding.tutorial.with.gitlab_ci.env_variables.section.title
+  </h2>
+  <FormattedMessage
+    defaultMessage="onboarding.tutorial.with.gitlab_ci.env_variables.section.description"
+    id="onboarding.tutorial.with.gitlab_ci.env_variables.section.description"
+    values={
+      Object {
+        "link": <strong>
+          onboarding.tutorial.with.gitlab_ci.env_variables.description.link
+        </strong>,
+      }
+    }
+  />
+  <ol
+    className="list-styled big-spacer-top"
+  >
+    <li
+      className="big-spacer-bottom"
+    >
+      <FormattedMessage
+        defaultMessage="onboarding.tutorial.with.gitlab_ci.env_variables.enter_field_value"
+        id="onboarding.tutorial.with.gitlab_ci.env_variables.step1"
+        values={
+          Object {
+            "extra": <ClipboardIconButton
+              copyValue="SONAR_TOKEN"
+            />,
+            "field": "onboarding.tutorial.with.gitlab_ci.env_variables.step1",
+            "value": <code
+              className="rule"
+            >
+              SONAR_TOKEN
+            </code>,
+          }
+        }
+      />
+    </li>
+    <li
+      className="big-spacer-bottom"
+    >
+      <FormattedMessage
+        defaultMessage="onboarding.tutorial.with.gitlab_ci.env_variables.enter_field_value"
+        id="onboarding.tutorial.with.gitlab_ci.env_variables.step2"
+        values={
+          Object {
+            "extra": <Button
+              className="spacer-left"
+              onClick={[Function]}
+            >
+              onboarding.token.generate_token
+            </Button>,
+            "field": "onboarding.tutorial.with.gitlab_ci.env_variables.step2",
+            "value": "onboarding.tutorial.with.gitlab_ci.env_variables.section.step2.value",
+          }
+        }
+      />
+    </li>
+    <li
+      className="big-spacer-bottom"
+    >
+      onboarding.tutorial.with.gitlab_ci.env_variables.step3
+    </li>
+    <li
+      className="big-spacer-bottom"
+    >
+      onboarding.tutorial.with.gitlab_ci.env_variables.section.step4
+    </li>
+  </ol>
+  <hr
+    className="no-horizontal-margins"
+  />
+  <h2
+    className="spacer-bottom big-spacer-top"
+  >
+    onboarding.tutorial.with.gitlab_ci.env_variables.section2.title
+  </h2>
+  <FormattedMessage
+    defaultMessage="onboarding.tutorial.with.gitlab_ci.env_variables.section2.description"
+    id="onboarding.tutorial.with.gitlab_ci.env_variables.section2.description"
+    values={
+      Object {
+        "link": <strong>
+          onboarding.tutorial.with.gitlab_ci.env_variables.description.link
+        </strong>,
+      }
+    }
+  />
+  <ol
+    className="list-styled big-spacer-top big-spacer-bottom"
+  >
+    <li
+      className="big-spacer-bottom"
+    >
+      <FormattedMessage
+        defaultMessage="onboarding.tutorial.with.gitlab_ci.env_variables.enter_field_value"
+        id="onboarding.tutorial.with.gitlab_ci.env_variables.step1"
+        values={
+          Object {
+            "extra": <ClipboardIconButton
+              copyValue="SONAR_HOST_URL"
+            />,
+            "field": "onboarding.tutorial.with.gitlab_ci.env_variables.step1",
+            "value": <code
+              className="rule"
+            >
+              SONAR_HOST_URL
+            </code>,
+          }
+        }
+      />
+    </li>
+    <li
+      className="big-spacer-bottom"
+    >
+      <FormattedMessage
+        defaultMessage="onboarding.tutorial.with.gitlab_ci.env_variables.enter_field_value"
+        id="onboarding.tutorial.with.gitlab_ci.env_variables.step2"
+        values={
+          Object {
+            "extra": <ClipboardIconButton
+              copyValue="http://localhost"
+            />,
+            "field": "onboarding.tutorial.with.gitlab_ci.env_variables.step2",
+            "value": <code
+              className="rule"
+            >
+              http://localhost
+            </code>,
+          }
+        }
+      />
+    </li>
+    <li
+      className="big-spacer-bottom"
+    >
+      onboarding.tutorial.with.gitlab_ci.env_variables.step3
+    </li>
+    <li
+      className="big-spacer-bottom"
+    >
+      onboarding.tutorial.with.gitlab_ci.env_variables.section2.step4
+    </li>
+  </ol>
+  <Button
+    className="big-spacer-bottom"
+    onClick={[MockFunction]}
+  >
+    continue
+  </Button>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/GitLabCITutorial-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/GitLabCITutorial-test.tsx.snap
new file mode 100644 (file)
index 0000000..9943639
--- /dev/null
@@ -0,0 +1,94 @@
+// 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.gitlab_ci.title
+    </h1>
+  </div>
+  <ProjectKeyStep
+    component={
+      Object {
+        "breadcrumbs": Array [],
+        "key": "my-project",
+        "name": "MyProject",
+        "organization": "foo",
+        "qualifier": "TRK",
+        "qualityGate": Object {
+          "isDefault": true,
+          "key": "30",
+          "name": "Sonar way",
+        },
+        "qualityProfiles": Array [
+          Object {
+            "deleted": false,
+            "key": "my-qp",
+            "language": "ts",
+            "name": "Sonar way",
+          },
+        ],
+        "tags": Array [],
+      }
+    }
+    finished={false}
+    onDone={[Function]}
+    onOpen={[Function]}
+    open={true}
+    setBuildTool={[Function]}
+  />
+  <EnvironmentVariablesStep
+    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 [],
+      }
+    }
+    finished={false}
+    onDone={[Function]}
+    onOpen={[Function]}
+    open={false}
+  />
+  <YmlFileStep
+    open={false}
+  />
+</Fragment>
+`;
+
+exports[`should render correctly: wrong alm 1`] = `
+<Alert
+  variant="error"
+>
+  onboarding.tutorial.with.gitlab_ci.unsupported
+</Alert>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/ProjectKeyStep-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/ProjectKeyStep-test.tsx.snap
new file mode 100644 (file)
index 0000000..b1e3b8e
--- /dev/null
@@ -0,0 +1,226 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly for build tool 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",
+            "other",
+          ]
+        }
+      />
+    </li>
+    <li
+      className="abs-width-600"
+    >
+      <FormattedMessage
+        defaultMessage="onboarding.tutorial.with.gitlab_ci.project_key.gradle.step2"
+        id="onboarding.tutorial.with.gitlab_ci.project_key.gradle.step2"
+        values={
+          Object {
+            "file": <React.Fragment>
+              <code
+                className="rule"
+              >
+                build.gradle
+              </code>
+              <ClipboardIconButton
+                className="little-spacer-left"
+                copyValue="build.gradle"
+              />
+            </React.Fragment>,
+          }
+        }
+      />
+      <CodeSnippet
+        snippet="plugins {
+  id \\"org.sonarqube\\" version \\"3.0\\"
+}
+
+sonarqube {
+  properties {
+    property \\"sonar.projectKey\\", \\"my-project\\"
+    property \\"sonar.qualitygate.wait\\", true 
+  }
+}"
+      />
+    </li>
+  </ol>
+  <Button
+    onClick={[MockFunction]}
+  >
+    continue
+  </Button>
+</div>
+`;
+
+exports[`should render correctly for build tool 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",
+            "other",
+          ]
+        }
+      />
+    </li>
+    <li
+      className="abs-width-600"
+    >
+      <FormattedMessage
+        defaultMessage="onboarding.tutorial.with.gitlab_ci.project_key.maven.step2"
+        id="onboarding.tutorial.with.gitlab_ci.project_key.maven.step2"
+        values={
+          Object {
+            "file": <React.Fragment>
+              <code
+                className="rule"
+              >
+                pom.xml
+              </code>
+              <ClipboardIconButton
+                className="little-spacer-left"
+                copyValue="pom.xml"
+              />
+            </React.Fragment>,
+          }
+        }
+      />
+      <CodeSnippet
+        snippet="<properties>
+  <sonar.projectKey>my-project</sonar.projectKey>
+  <sonar.qualitygate.wait>true</sonar.qualitygate.wait>
+</properties>"
+      />
+    </li>
+  </ol>
+  <Button
+    onClick={[MockFunction]}
+  >
+    continue
+  </Button>
+</div>
+`;
+
+exports[`should render correctly for build tool 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",
+            "other",
+          ]
+        }
+      />
+    </li>
+    <li
+      className="abs-width-600"
+    >
+      <FormattedMessage
+        defaultMessage="onboarding.tutorial.with.gitlab_ci.project_key.other.step2"
+        id="onboarding.tutorial.with.gitlab_ci.project_key.other.step2"
+        values={
+          Object {
+            "file": <React.Fragment>
+              <code
+                className="rule"
+              >
+                sonar-project.properties
+              </code>
+              <ClipboardIconButton
+                className="little-spacer-left"
+                copyValue="sonar-project.properties"
+              />
+            </React.Fragment>,
+          }
+        }
+      />
+      <CodeSnippet
+        snippet="sonar.projectKey=my-project
+sonar.qualitygate.wait=true
+"
+      />
+    </li>
+  </ol>
+  <Button
+    onClick={[MockFunction]}
+  >
+    continue
+  </Button>
+</div>
+`;
+
+exports[`should render correctly: Step wrapper 1`] = `
+<Step
+  finished={false}
+  onOpen={[MockFunction]}
+  open={true}
+  renderForm={[Function]}
+  stepNumber={1}
+  stepTitle="onboarding.tutorial.with.gitlab_ci.project_key.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",
+            "other",
+          ]
+        }
+      />
+    </li>
+  </ol>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/YmlFileStep-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/YmlFileStep-test.tsx.snap
new file mode 100644 (file)
index 0000000..95bf2ab
--- /dev/null
@@ -0,0 +1,349 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly for build tool gradle 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <div
+    className="flex-columns"
+  >
+    <div
+      className="flex-column-full"
+    >
+      <React.Fragment>
+        <div
+          className="big-spacer-bottom"
+        >
+          <FormattedMessage
+            defaultMessage="onboarding.tutorial.with.gitlab_ci.yml.description"
+            id="onboarding.tutorial.with.gitlab_ci.yml.description"
+            values={
+              Object {
+                "filename": <React.Fragment>
+                  <code
+                    className="rule"
+                  >
+                    onboarding.tutorial.with.gitlab_ci.yml.filename
+                  </code>
+                  <ClipboardIconButton
+                    className="little-spacer-left"
+                    copyValue="onboarding.tutorial.with.gitlab_ci.yml.filename"
+                  />
+                </React.Fragment>,
+              }
+            }
+          />
+        </div>
+        <div
+          className="big-spacer-bottom"
+        >
+          <PipeCommandGradle />
+        </div>
+        <p
+          className="little-spacer-bottom"
+        >
+          onboarding.tutorial.with.gitlab_ci.yml.baseconfig
+        </p>
+        <p
+          className="huge-spacer-bottom"
+        >
+          onboarding.tutorial.with.gitlab_ci.yml.existing
+        </p>
+        <hr
+          className="no-horizontal-margins"
+        />
+        <div>
+          <p
+            className="big-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.gitlab_ci.yml.done
+               
+            </strong>
+             
+            <FormattedMessage
+              defaultMessage="onboarding.tutorial.with.gitlab_ci.yml.done.description"
+              id="onboarding.tutorial.with.gitlab_ci.yml.done.description"
+              values={
+                Object {
+                  "link": "onboarding.tutorial.with.gitlab_ci.yml.done.description.link",
+                }
+              }
+            />
+          </p>
+          <p
+            className="big-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.gitlab_ci.yml.done.then-what
+            </strong>
+             
+            onboarding.tutorial.with.gitlab_ci.yml.done.then-what.description
+          </p>
+          <p
+            className="big-spacer-bottom"
+          >
+            <FormattedMessage
+              defaultMessage="onboarding.tutorial.with.gitlab_ci.yml.done.links.title"
+              id="onboarding.tutorial.with.gitlab_ci.yml.done.links.title"
+              values={
+                Object {
+                  "links": <Link
+                    onlyActiveOnIndex={false}
+                    rel="noopener noreferrer"
+                    style={Object {}}
+                    target="_blank"
+                    to="/documentation/user-guide/quality-gates/"
+                  >
+                    onboarding.tutorial.with.gitlab_ci.yml.done.links.QG
+                  </Link>,
+                }
+              }
+            />
+          </p>
+        </div>
+      </React.Fragment>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`should render correctly for build tool maven 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <div
+    className="flex-columns"
+  >
+    <div
+      className="flex-column-full"
+    >
+      <React.Fragment>
+        <div
+          className="big-spacer-bottom"
+        >
+          <FormattedMessage
+            defaultMessage="onboarding.tutorial.with.gitlab_ci.yml.description"
+            id="onboarding.tutorial.with.gitlab_ci.yml.description"
+            values={
+              Object {
+                "filename": <React.Fragment>
+                  <code
+                    className="rule"
+                  >
+                    onboarding.tutorial.with.gitlab_ci.yml.filename
+                  </code>
+                  <ClipboardIconButton
+                    className="little-spacer-left"
+                    copyValue="onboarding.tutorial.with.gitlab_ci.yml.filename"
+                  />
+                </React.Fragment>,
+              }
+            }
+          />
+        </div>
+        <div
+          className="big-spacer-bottom"
+        >
+          <PipeCommandMaven />
+        </div>
+        <p
+          className="little-spacer-bottom"
+        >
+          onboarding.tutorial.with.gitlab_ci.yml.baseconfig
+        </p>
+        <p
+          className="huge-spacer-bottom"
+        >
+          onboarding.tutorial.with.gitlab_ci.yml.existing
+        </p>
+        <hr
+          className="no-horizontal-margins"
+        />
+        <div>
+          <p
+            className="big-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.gitlab_ci.yml.done
+               
+            </strong>
+             
+            <FormattedMessage
+              defaultMessage="onboarding.tutorial.with.gitlab_ci.yml.done.description"
+              id="onboarding.tutorial.with.gitlab_ci.yml.done.description"
+              values={
+                Object {
+                  "link": "onboarding.tutorial.with.gitlab_ci.yml.done.description.link",
+                }
+              }
+            />
+          </p>
+          <p
+            className="big-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.gitlab_ci.yml.done.then-what
+            </strong>
+             
+            onboarding.tutorial.with.gitlab_ci.yml.done.then-what.description
+          </p>
+          <p
+            className="big-spacer-bottom"
+          >
+            <FormattedMessage
+              defaultMessage="onboarding.tutorial.with.gitlab_ci.yml.done.links.title"
+              id="onboarding.tutorial.with.gitlab_ci.yml.done.links.title"
+              values={
+                Object {
+                  "links": <Link
+                    onlyActiveOnIndex={false}
+                    rel="noopener noreferrer"
+                    style={Object {}}
+                    target="_blank"
+                    to="/documentation/user-guide/quality-gates/"
+                  >
+                    onboarding.tutorial.with.gitlab_ci.yml.done.links.QG
+                  </Link>,
+                }
+              }
+            />
+          </p>
+        </div>
+      </React.Fragment>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`should render correctly for build tool other 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <div
+    className="flex-columns"
+  >
+    <div
+      className="flex-column-full"
+    >
+      <React.Fragment>
+        <div
+          className="big-spacer-bottom"
+        >
+          <FormattedMessage
+            defaultMessage="onboarding.tutorial.with.gitlab_ci.yml.description"
+            id="onboarding.tutorial.with.gitlab_ci.yml.description"
+            values={
+              Object {
+                "filename": <React.Fragment>
+                  <code
+                    className="rule"
+                  >
+                    onboarding.tutorial.with.gitlab_ci.yml.filename
+                  </code>
+                  <ClipboardIconButton
+                    className="little-spacer-left"
+                    copyValue="onboarding.tutorial.with.gitlab_ci.yml.filename"
+                  />
+                </React.Fragment>,
+              }
+            }
+          />
+        </div>
+        <div
+          className="big-spacer-bottom"
+        >
+          <PipeCommandOther />
+        </div>
+        <p
+          className="little-spacer-bottom"
+        >
+          onboarding.tutorial.with.gitlab_ci.yml.baseconfig
+        </p>
+        <p
+          className="huge-spacer-bottom"
+        >
+          onboarding.tutorial.with.gitlab_ci.yml.existing
+        </p>
+        <hr
+          className="no-horizontal-margins"
+        />
+        <div>
+          <p
+            className="big-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.gitlab_ci.yml.done
+               
+            </strong>
+             
+            <FormattedMessage
+              defaultMessage="onboarding.tutorial.with.gitlab_ci.yml.done.description"
+              id="onboarding.tutorial.with.gitlab_ci.yml.done.description"
+              values={
+                Object {
+                  "link": "onboarding.tutorial.with.gitlab_ci.yml.done.description.link",
+                }
+              }
+            />
+          </p>
+          <p
+            className="big-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.gitlab_ci.yml.done.then-what
+            </strong>
+             
+            onboarding.tutorial.with.gitlab_ci.yml.done.then-what.description
+          </p>
+          <p
+            className="big-spacer-bottom"
+          >
+            <FormattedMessage
+              defaultMessage="onboarding.tutorial.with.gitlab_ci.yml.done.links.title"
+              id="onboarding.tutorial.with.gitlab_ci.yml.done.links.title"
+              values={
+                Object {
+                  "links": <Link
+                    onlyActiveOnIndex={false}
+                    rel="noopener noreferrer"
+                    style={Object {}}
+                    target="_blank"
+                    to="/documentation/user-guide/quality-gates/"
+                  >
+                    onboarding.tutorial.with.gitlab_ci.yml.done.links.QG
+                  </Link>,
+                }
+              }
+            />
+          </p>
+        </div>
+      </React.Fragment>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`should render correctly: Step wrapper 1`] = `
+<Step
+  finished={false}
+  open={true}
+  renderForm={[Function]}
+  stepNumber={3}
+  stepTitle="onboarding.tutorial.with.gitlab_ci.yml.title"
+/>
+`;
+
+exports[`should render correctly: initial content 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <div
+    className="flex-columns"
+  >
+    <div
+      className="flex-column-full"
+    />
+  </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandGradle.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandGradle.tsx
new file mode 100644 (file)
index 0000000..089a6f3
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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';
+
+export default function PipeCommandGradle() {
+  const command = `sonarqube-check:
+  image: gradle:jre11-slim
+  variables:
+    SONAR_USER_HOME: "\${CI_PROJECT_DIR}/.sonar"  # Defines the location of the analysis task cache
+    GIT_DEPTH: "0"  # Tells git to fetch all the branches of the project, required by the analysis task
+  cache:
+    key: "\${CI_JOB_NAME}"
+    paths:
+      - .sonar/cache
+  script: gradle sonarqube
+  allow_failure: true
+  only:
+    - merge_requests
+    - master
+    - develop
+`;
+
+  return <CodeSnippet snippet={command} />;
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandMaven.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandMaven.tsx
new file mode 100644 (file)
index 0000000..9a665be
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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';
+
+export default function PipeCommandMaven() {
+  const command = `sonarqube-check:
+  image: maven:3.6.3-jdk-11
+  variables:
+    SONAR_USER_HOME: "\${CI_PROJECT_DIR}/.sonar"  # Defines the location of the analysis task cache
+    GIT_DEPTH: "0"  # Tells git to fetch all the branches of the project, required by the analysis task
+  cache:
+    key: "\${CI_JOB_NAME}"
+    paths:
+      - .sonar/cache
+  script:
+    - mvn verify sonar:sonar
+  allow_failure: true
+  only:
+    - merge_requests
+    - master
+    - develop
+`;
+
+  return <CodeSnippet snippet={command} />;
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandOther.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandOther.tsx
new file mode 100644 (file)
index 0000000..a83cbcd
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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';
+
+export default function PipeCommandOther() {
+  const command = `sonarqube-check:
+  image:
+    name: sonarsource/sonar-scanner-cli:latest
+    entrypoint: [""]
+  variables:
+    SONAR_USER_HOME: "\${CI_PROJECT_DIR}/.sonar"  # Defines the location of the analysis task cache
+    GIT_DEPTH: "0"  # Tells git to fetch all the branches of the project, required by the analysis task
+  cache:
+    key: "\${CI_JOB_NAME}"
+    paths:
+      - .sonar/cache
+  script:
+    - sonar-scanner
+  allow_failure: true
+  only:
+    - merge_requests
+    - master
+    - develop
+`;
+
+  return <CodeSnippet snippet={command} />;
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandGradle-test.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandGradle-test.tsx
new file mode 100644 (file)
index 0000000..6ad96b4
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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 PipeCommandGradle from '../PipeCommandGradle';
+
+it('should render correctly', () => {
+  expect(shallow(<PipeCommandGradle />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandMaven-test.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandMaven-test.tsx
new file mode 100644 (file)
index 0000000..0b6056d
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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 PipeCommandMaven from '../PipeCommandMaven';
+
+it('should render correctly', () => {
+  expect(shallow(<PipeCommandMaven />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandOther-test.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandOther-test.tsx
new file mode 100644 (file)
index 0000000..5b12da8
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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 PipeCommandOther from '../PipeCommandOther';
+
+it('should render correctly', () => {
+  expect(shallow(<PipeCommandOther />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandGradle-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandGradle-test.tsx.snap
new file mode 100644 (file)
index 0000000..61f4c68
--- /dev/null
@@ -0,0 +1,22 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<CodeSnippet
+  snippet="sonarqube-check:
+  image: gradle:jre11-slim
+  variables:
+    SONAR_USER_HOME: \\"\${CI_PROJECT_DIR}/.sonar\\"  # Defines the location of the analysis task cache
+    GIT_DEPTH: \\"0\\"  # Tells git to fetch all the branches of the project, required by the analysis task
+  cache:
+    key: \\"\${CI_JOB_NAME}\\"
+    paths:
+      - .sonar/cache
+  script: gradle sonarqube
+  allow_failure: true
+  only:
+    - merge_requests
+    - master
+    - develop
+"
+/>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandMaven-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandMaven-test.tsx.snap
new file mode 100644 (file)
index 0000000..0b84368
--- /dev/null
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<CodeSnippet
+  snippet="sonarqube-check:
+  image: maven:3.6.3-jdk-11
+  variables:
+    SONAR_USER_HOME: \\"\${CI_PROJECT_DIR}/.sonar\\"  # Defines the location of the analysis task cache
+    GIT_DEPTH: \\"0\\"  # Tells git to fetch all the branches of the project, required by the analysis task
+  cache:
+    key: \\"\${CI_JOB_NAME}\\"
+    paths:
+      - .sonar/cache
+  script:
+    - mvn verify sonar:sonar
+  allow_failure: true
+  only:
+    - merge_requests
+    - master
+    - develop
+"
+/>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandOther-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandOther-test.tsx.snap
new file mode 100644 (file)
index 0000000..74e84b8
--- /dev/null
@@ -0,0 +1,25 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<CodeSnippet
+  snippet="sonarqube-check:
+  image:
+    name: sonarsource/sonar-scanner-cli:latest
+    entrypoint: [\\"\\"]
+  variables:
+    SONAR_USER_HOME: \\"\${CI_PROJECT_DIR}/.sonar\\"  # Defines the location of the analysis task cache
+    GIT_DEPTH: \\"0\\"  # Tells git to fetch all the branches of the project, required by the analysis task
+  cache:
+    key: \\"\${CI_JOB_NAME}\\"
+    paths:
+      - .sonar/cache
+  script:
+    - sonar-scanner
+  allow_failure: true
+  only:
+    - merge_requests
+    - master
+    - develop
+"
+/>
+`;
diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/types.ts b/server/sonar-web/src/main/js/components/tutorials/gitlabci/types.ts
new file mode 100644 (file)
index 0000000..d95d7fc
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * 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 BuildTools {
+  Maven = 'maven',
+  Gradle = 'gradle',
+  // MSBuild = 'msbuild', // Not yet supported
+  Other = 'other'
+}
diff --git a/server/sonar-web/src/main/js/components/tutorials/styles.css b/server/sonar-web/src/main/js/components/tutorials/styles.css
deleted file mode 100644 (file)
index 295fb42..0000000
+++ /dev/null
@@ -1,23 +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.
- */
-.tutorial-selection {
-  margin: 0 auto;
-  max-width: 500px;
-}
index 2ad939070bc05a123e865b6631fdc33081805770..659d13aac12d86f05431854c4b4dc6f00a30a6cf 100644 (file)
@@ -19,7 +19,8 @@
  */
 export enum TutorialModes {
   Manual = 'manual',
-  Jenkins = 'jenkins'
+  Jenkins = 'jenkins',
+  GitLabCI = 'gitlab-ci'
 }
 
 export interface LanguageConfig {
index 974968435f32701fdc36b03e83ca504fbc773371..bd0013775fc34c867f82b7ae8b2ae5445bd7edff 100644 (file)
@@ -24,7 +24,8 @@ import {
   GithubBindingDefinition,
   ProjectAlmBindingResponse,
   ProjectBitbucketBindingResponse,
-  ProjectGitHubBindingResponse
+  ProjectGitHubBindingResponse,
+  ProjectGitLabBindingResponse
 } from '../types/alm-settings';
 
 export function isProjectBitbucketBindingResponse(
@@ -39,6 +40,12 @@ export function isProjectGitHubBindingResponse(
   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 {
index c89b8f0baa2a4be2db560316efe39fe1c0c746fc..c5bcc31878588629b652c53b6b69b08723b63faf 100644 (file)
@@ -26,7 +26,8 @@ import {
   GitlabBindingDefinition,
   ProjectAlmBindingResponse,
   ProjectBitbucketBindingResponse,
-  ProjectGitHubBindingResponse
+  ProjectGitHubBindingResponse,
+  ProjectGitLabBindingResponse
 } from '../../types/alm-settings';
 
 export function mockAlmSettingsInstance(
@@ -116,3 +117,15 @@ export function mockProjectGithubBindingResponse(
     ...overrides
   };
 }
+
+export function mockProjectGitLabBindingResponse(
+  overrides: Partial<ProjectGitLabBindingResponse> = {}
+): ProjectGitLabBindingResponse {
+  return {
+    alm: AlmKeys.GitLab,
+    key: 'foo',
+    repository: 'PROJECT_KEY',
+    url: 'https://gitlab.com/api/v4',
+    ...overrides
+  };
+}
index 18ce632b7853ec52370d83a24ba655490c4929c6..887d227f881430c43503eeba68548ce5a7bc6644 100644 (file)
@@ -69,6 +69,12 @@ export interface ProjectGitHubBindingResponse extends ProjectAlmBindingResponse
   repository: string;
 }
 
+export interface ProjectGitLabBindingResponse extends ProjectAlmBindingResponse {
+  alm: AlmKeys.GitLab;
+  repository: string;
+  url: string;
+}
+
 export interface ProjectAlmBindingParams {
   almSetting: string;
   project: string;
index 70115c53fc0be59f8dcf369dd5c0286ec0d5a403..71c985dbd21d6563986dd98cf1a19729cb73ff5b 100644 (file)
@@ -3239,6 +3239,7 @@ onboarding.token.generate_token=Generate a token
 onboarding.token.generate_token.placeholder=Enter a name for your token
 onboarding.token.use_existing_token=Use existing token
 onboarding.token.use_existing_token.placeholder=Enter your existing token
+onboarding.token.use_existing_token.label=Existing token value
 onboarding.token.invalid_format=The token you have entered has invalid format.
 
 onboarding.analysis.header=Run analysis on your project
@@ -3322,6 +3323,41 @@ 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.choose_method.gitlab_ci=With GitLab CI
+
+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.
+onboarding.tutorial.with.gitlab_ci.project_key.title=Set your project key
+onboarding.tutorial.with.gitlab_ci.project_key.maven.step2=Add the following to your {file} file:
+onboarding.tutorial.with.gitlab_ci.project_key.gradle.step2=Add the following to your {file} file:
+onboarding.tutorial.with.gitlab_ci.project_key.other.step2=Create a {file} file in your repository and paste the following code:
+
+onboarding.tutorial.with.gitlab_ci.env_variables.title=Add environment variables
+onboarding.tutorial.with.gitlab_ci.env_variables.enter_field_value=In the {field} field, enter {value} {extra}
+onboarding.tutorial.with.gitlab_ci.env_variables.description.link=Settings > CI/CD > Variables
+onboarding.tutorial.with.gitlab_ci.env_variables.section.title=a. Define the SonarQube Token environment variable
+onboarding.tutorial.with.gitlab_ci.env_variables.section.description=In GitLab, go to {link} to add the following variable and make sure it is available for your project:
+onboarding.tutorial.with.gitlab_ci.env_variables.edit.token.tooltip=Use an existing token or generate a new one.
+onboarding.tutorial.with.gitlab_ci.env_variables.step1=Key
+onboarding.tutorial.with.gitlab_ci.env_variables.step2=Value
+onboarding.tutorial.with.gitlab_ci.env_variables.section.step2.value=an existing token, or a newly generated one:
+onboarding.tutorial.with.gitlab_ci.env_variables.step3=Uncheck the "Protect Variable" checkbox
+onboarding.tutorial.with.gitlab_ci.env_variables.section.step4=Check the "Mask Variable" checkbox
+onboarding.tutorial.with.gitlab_ci.env_variables.section2.title=b. Define the SonarQube URL environment variable
+onboarding.tutorial.with.gitlab_ci.env_variables.section2.description=Still in {link} add a new variable and make sure it is available for your project:
+onboarding.tutorial.with.gitlab_ci.env_variables.section2.step4=Leave the "Mask variable" checkbox unchecked
+onboarding.tutorial.with.gitlab_ci.yml.title=Create or update the configuration file
+onboarding.tutorial.with.gitlab_ci.yml.description=Create or update your {filename} file with the following content.
+onboarding.tutorial.with.gitlab_ci.yml.filename=.gitlab-ci.yml
+onboarding.tutorial.with.gitlab_ci.yml.baseconfig=Note that this is a minimal base configuration to run a SonarQube analysis on your master branch and merge requests.
+onboarding.tutorial.with.gitlab_ci.yml.existing=If you already have a pipeline configured and running, you might want to add the example from this step to your existing yml file.
+onboarding.tutorial.with.gitlab_ci.yml.done=Is it done?
+onboarding.tutorial.with.gitlab_ci.yml.done.description=You should see the page refresh itself in a few moments with your analysis results if the {link}.
+onboarding.tutorial.with.gitlab_ci.yml.done.description.link=pipeline runs successfully
+onboarding.tutorial.with.gitlab_ci.yml.done.then-what=And then what?
+onboarding.tutorial.with.gitlab_ci.yml.done.then-what.description=Each new push triggers an analysis by SonarQube.
+onboarding.tutorial.with.gitlab_ci.yml.done.links.title=Check this useful link while you wait: {links}
+onboarding.tutorial.with.gitlab_ci.yml.done.links.QG=What is a Quality Gate?
 
 onboarding.tutorial.with.jenkins.title=Analyze your project with Jenkins
 onboarding.tutorial.with.jenkins.unsupported=This tutorial is only available for projects bound to Bitbucket Server or GitHub.