Browse Source

SONAR-13754 Add GitLab CI tutorial

tags/8.5.0.37579
Jeremy Davis 3 years ago
parent
commit
b39a937e42
38 changed files with 2403 additions and 45 deletions
  1. 5
    0
      server/sonar-docs/src/tooltips/tutorials/use-existing-token.md
  2. 0
    1
      server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx
  3. 2
    3
      server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx
  4. 46
    16
      server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx
  5. 2
    2
      server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx
  6. 26
    2
      server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelectionRenderer-test.tsx
  7. 88
    3
      server/sonar-web/src/main/js/components/tutorials/__tests__/__snapshots__/TutorialSelectionRenderer-test.tsx.snap
  8. 186
    0
      server/sonar-web/src/main/js/components/tutorials/components/EditTokenModal.tsx
  9. 11
    12
      server/sonar-web/src/main/js/components/tutorials/components/Step.css
  10. 112
    0
      server/sonar-web/src/main/js/components/tutorials/components/__tests__/EditTokenModal-test.tsx
  11. 11
    0
      server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/EditTokenModal-test.tsx.snap
  12. 178
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/EnvironmentVariablesStep.tsx
  13. 83
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/GitLabCITutorial.tsx
  14. 125
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/ProjectKeyStep.tsx
  15. 136
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/YmlFileStep.tsx
  16. 46
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/EnvironmentVariablesStep-test.tsx
  17. 45
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/GitLabCITutorial-test.tsx
  18. 69
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/ProjectKeyStep-test.tsx
  19. 41
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/YmlFileStep-test.tsx
  20. 173
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/EnvironmentVariablesStep-test.tsx.snap
  21. 94
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/GitLabCITutorial-test.tsx.snap
  22. 226
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/ProjectKeyStep-test.tsx.snap
  23. 349
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/YmlFileStep-test.tsx.snap
  24. 42
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandGradle.tsx
  25. 43
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandMaven.tsx
  26. 45
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandOther.tsx
  27. 26
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandGradle-test.tsx
  28. 26
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandMaven-test.tsx
  29. 26
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandOther-test.tsx
  30. 22
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandGradle-test.tsx.snap
  31. 23
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandMaven-test.tsx.snap
  32. 25
    0
      server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandOther-test.tsx.snap
  33. 5
    3
      server/sonar-web/src/main/js/components/tutorials/gitlabci/types.ts
  34. 2
    1
      server/sonar-web/src/main/js/components/tutorials/types.ts
  35. 8
    1
      server/sonar-web/src/main/js/helpers/alm-settings.ts
  36. 14
    1
      server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts
  37. 6
    0
      server/sonar-web/src/main/js/types/alm-settings.ts
  38. 36
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 5
- 0
server/sonar-docs/src/tooltips/tutorials/use-existing-token.md View File

@@ -0,0 +1,5 @@
Paste an existing token value into the input field.

---

See also: [User Token](/user-guide/user-token/)

+ 0
- 1
server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx View 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 {

+ 2
- 3
server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx View 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 {

+ 46
- 16
server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx View 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}
/>
)}
</>
);
}

+ 2
- 2
server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx View 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);

+ 26
- 2
server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelectionRenderer-test.tsx View 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

+ 88
- 3
server/sonar-web/src/main/js/components/tutorials/__tests__/__snapshots__/TutorialSelectionRenderer-test.tsx.snap View 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"
>

+ 186
- 0
server/sonar-web/src/main/js/components/tutorials/components/EditTokenModal.tsx View File

@@ -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>
);
}
}

+ 11
- 12
server/sonar-web/src/main/js/components/tutorials/components/Step.css View File

@@ -80,22 +80,21 @@

.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);
}

+ 112
- 0
server/sonar-web/src/main/js/components/tutorials/components/__tests__/EditTokenModal-test.tsx View File

@@ -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}
/>
);
}

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

@@ -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>
`;

+ 178
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/EnvironmentVariablesStep.tsx View File

@@ -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')}
/>
</>
);
}

+ 83
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/GitLabCITutorial.tsx View File

@@ -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} />
</>
);
}

+ 125
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/ProjectKeyStep.tsx View File

@@ -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')}
/>
);
}

+ 136
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/YmlFileStep.tsx View File

@@ -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')}
/>
);
}

+ 46
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/EnvironmentVariablesStep-test.tsx View File

@@ -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}
/>
);
}

+ 45
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/GitLabCITutorial-test.tsx View File

@@ -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}
/>
);
}

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

@@ -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}
/>
);
}

+ 41
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/YmlFileStep-test.tsx View File

@@ -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} />);
}

+ 173
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/EnvironmentVariablesStep-test.tsx.snap View File

@@ -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>
`;

+ 94
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/GitLabCITutorial-test.tsx.snap View File

@@ -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>
`;

+ 226
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/ProjectKeyStep-test.tsx.snap View File

@@ -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>
`;

+ 349
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/YmlFileStep-test.tsx.snap View File

@@ -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>
`;

+ 42
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandGradle.tsx View File

@@ -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} />;
}

+ 43
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandMaven.tsx View File

@@ -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} />;
}

+ 45
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommandOther.tsx View File

@@ -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} />;
}

+ 26
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandGradle-test.tsx View File

@@ -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();
});

+ 26
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandMaven-test.tsx View File

@@ -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();
});

+ 26
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommandOther-test.tsx View File

@@ -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();
});

+ 22
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandGradle-test.tsx.snap View File

@@ -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
"
/>
`;

+ 23
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandMaven-test.tsx.snap View File

@@ -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
"
/>
`;

+ 25
- 0
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommandOther-test.tsx.snap View File

@@ -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
"
/>
`;

server/sonar-web/src/main/js/components/tutorials/styles.css → server/sonar-web/src/main/js/components/tutorials/gitlabci/types.ts View File

@@ -17,7 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
.tutorial-selection {
margin: 0 auto;
max-width: 500px;
export enum BuildTools {
Maven = 'maven',
Gradle = 'gradle',
// MSBuild = 'msbuild', // Not yet supported
Other = 'other'
}

+ 2
- 1
server/sonar-web/src/main/js/components/tutorials/types.ts View File

@@ -19,7 +19,8 @@
*/
export enum TutorialModes {
Manual = 'manual',
Jenkins = 'jenkins'
Jenkins = 'jenkins',
GitLabCI = 'gitlab-ci'
}

export interface LanguageConfig {

+ 8
- 1
server/sonar-web/src/main/js/helpers/alm-settings.ts View 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 {

+ 14
- 1
server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts View 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
};
}

+ 6
- 0
server/sonar-web/src/main/js/types/alm-settings.ts View 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;

+ 36
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View 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.

Loading…
Cancel
Save