* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import classNames from 'classnames';
+import {
+ ButtonPrimary,
+ DeferredSpinner,
+ FlagErrorIcon,
+ FlagMessage,
+ FormField,
+ InputField,
+ LightPrimary,
+ Link,
+} from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import Link from '../../../../components/common/Link';
-import ValidationInput from '../../../../components/controls/ValidationInput';
-import { SubmitButton } from '../../../../components/controls/buttons';
-import { Alert } from '../../../../components/ui/Alert';
-import DeferredSpinner from '../../../../components/ui/DeferredSpinner';
import { translate } from '../../../../helpers/l10n';
import { AlmSettingsInstance } from '../../../../types/alm-settings';
export default function AzurePersonalAccessTokenForm(props: AzurePersonalAccessTokenFormProps) {
const {
- almSetting: { alm, url },
+ almSetting: { url },
submitting = false,
validationFailed,
firstConnection,
if (!token) {
errorMessage = translate('onboarding.create_project.pat_form.pat_required');
} else if (isInvalid) {
- errorMessage = translate('onboarding.create_project.pat_incorrect', alm);
+ errorMessage = translate('onboarding.create_project.pat_incorrect.azure');
}
return (
- <div className="boxed-group abs-width-600">
- <div className="boxed-group-inner">
- <h2>{translate('onboarding.create_project.pat_form.title', alm)}</h2>
+ <form
+ className="sw-mt-3 sw-w-[50%]"
+ onSubmit={(e: React.SyntheticEvent<HTMLFormElement>) => {
+ e.preventDefault();
+ props.onPersonalAccessTokenCreate(token);
+ }}
+ >
+ <LightPrimary as="h2" className="sw-heading-md">
+ {translate('onboarding.create_project.pat_form.title')}
+ </LightPrimary>
+ <LightPrimary as="p" className="sw-mt-2 sw-mb-4 sw-body-sm">
+ {translate('onboarding.create_project.pat_form.help.azure')}
+ </LightPrimary>
- <div className="big-spacer-top big-spacer-bottom">
- <FormattedMessage
- id="onboarding.create_project.pat_help.instructions"
- defaultMessage={translate('onboarding.create_project.pat_help.instructions', alm)}
- values={{
- link: url ? (
- <Link className="link-no-underline" to={getAzurePatUrl(url)} target="_blank">
- {translate('onboarding.create_project.pat_help.instructions.link', alm)}
- </Link>
- ) : (
- translate('onboarding.create_project.pat_help.instructions.link', alm)
- ),
- scope: (
- <strong>
- <em>Code (Read & Write)</em>
- </strong>
- ),
- }}
- />
+ {isInvalid && (
+ <div>
+ <FlagMessage variant="error" className="sw-mb-4">
+ <p>{errorMessage}</p>
+ </FlagMessage>
</div>
+ )}
- {!firstConnection && (
- <Alert className="big-spacer-right" variant="warning">
- <p>{translate('onboarding.create_project.pat.expired.info_message')}</p>
- <p>{translate('onboarding.create_project.pat.expired.info_message_contact')}</p>
- </Alert>
- )}
+ {!firstConnection && (
+ <FlagMessage variant="warning">
+ <p>
+ {translate('onboarding.create_project.pat.expired.info_message')}{' '}
+ {translate('onboarding.create_project.pat.expired.info_message_contact')}
+ </p>
+ </FlagMessage>
+ )}
- <form
- onSubmit={(e: React.SyntheticEvent<HTMLFormElement>) => {
- e.preventDefault();
- props.onPersonalAccessTokenCreate(token);
- }}
- >
- <ValidationInput
- error={errorMessage}
- labelHtmlFor="personal_access_token"
+ <FormField
+ htmlFor="personal_access_token"
+ className="sw-mt-6 sw-mb-3"
+ label={translate('onboarding.create_project.enter_pat')}
+ required
+ >
+ <div>
+ <InputField
+ autoFocus
+ id="personal_access_token"
+ minLength={1}
+ name="personal_access_token"
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+ setToken(e.target.value);
+ setTouched(true);
+ }}
+ type="text"
+ value={token}
+ size="large"
isInvalid={isInvalid}
- isValid={false}
- label={translate('onboarding.create_project.enter_pat')}
- required
- >
- <input
- autoFocus
- className={classNames('width-100 little-spacer-bottom', {
- 'is-invalid': isInvalid,
- })}
- id="personal_access_token"
- minLength={1}
- name="personal_access_token"
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
- setToken(e.target.value);
- setTouched(true);
+ />
+ {isInvalid && <FlagErrorIcon className="sw-ml-2" />}
+ </div>
+ </FormField>
+
+ <div className="sw-mb-6">
+ <FlagMessage variant="info">
+ <p>
+ <FormattedMessage
+ id="onboarding.create_project.pat_help.instructions.azure"
+ defaultMessage={translate('onboarding.create_project.pat_help.instructions.azure')}
+ values={{
+ link: url ? (
+ <Link to={getAzurePatUrl(url)}>
+ {translate('onboarding.create_project.pat_help.instructions.link.azure')}
+ </Link>
+ ) : (
+ translate('onboarding.create_project.pat_help.instructions.link.azure')
+ ),
}}
- type="text"
- value={token}
/>
- </ValidationInput>
+ </p>
+ </FlagMessage>
+ </div>
- <SubmitButton disabled={isInvalid || submitting || !touched}>
- {translate('onboarding.create_project.pat_form.list_repositories')}
- </SubmitButton>
- <DeferredSpinner className="spacer-left" loading={submitting} />
- </form>
+ <div className="sw-flex sw-items-center sw-mb-6">
+ <ButtonPrimary type="submit" disabled={isInvalid || submitting || !touched}>
+ {translate('save')}
+ </ButtonPrimary>
+ <DeferredSpinner className="sw-ml-2" loading={submitting} />
</div>
- </div>
+ </form>
);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 {
+ ButtonPrimary,
+ DeferredSpinner,
+ FlagErrorIcon,
+ FlagMessage,
+ FormField,
+ InputField,
+ LightPrimary,
+ Link,
+} from 'design-system';
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { translate } from '../../../../helpers/l10n';
+import { AlmSettingsInstance } from '../../../../types/alm-settings';
+import { usePersonalAccessToken } from '../usePersonalAccessToken';
+
+interface Props {
+ almSetting: AlmSettingsInstance;
+ resetPat: boolean;
+ onPersonalAccessTokenCreated: () => void;
+}
+
+export default function BitbucketCloudPersonalAccessTokenForm({
+ almSetting,
+ resetPat,
+ onPersonalAccessTokenCreated,
+}: Props) {
+ const {
+ username,
+ password,
+ firstConnection,
+ validationFailed,
+ touched,
+ submitting,
+ validationErrorMessage,
+ checkingPat,
+ handlePasswordChange,
+ handleUsernameChange,
+ handleSubmit,
+ } = usePersonalAccessToken(almSetting, resetPat, onPersonalAccessTokenCreated);
+
+ if (checkingPat) {
+ return <DeferredSpinner className="sw-ml-2" loading />;
+ }
+
+ const isInvalid = validationFailed && !touched;
+ const canSubmit = Boolean(password) && Boolean(username);
+ const submitButtonDiabled = isInvalid || submitting || !canSubmit;
+
+ const errorMessage =
+ validationErrorMessage ?? translate('onboarding.create_project.pat_incorrect.bitbucket_cloud');
+
+ return (
+ <form className="sw-mt-3 sw-w-[50%]" onSubmit={handleSubmit}>
+ <LightPrimary as="h2" className="sw-heading-md">
+ {translate('onboarding.create_project.pat_form.title')}
+ </LightPrimary>
+ <LightPrimary as="p" className="sw-mt-2 sw-mb-4 sw-body-sm">
+ {translate('onboarding.create_project.pat_form.help.bitbucket_cloud')}
+ </LightPrimary>
+
+ {isInvalid && (
+ <div>
+ <FlagMessage variant="error" className="sw-mb-4">
+ <p>{errorMessage}</p>
+ </FlagMessage>
+ </div>
+ )}
+
+ {!firstConnection && (
+ <FlagMessage variant="warning">
+ <p>
+ {translate('onboarding.create_project.pat.expired.info_message')}{' '}
+ {translate('onboarding.create_project.pat.expired.info_message_contact')}
+ </p>
+ </FlagMessage>
+ )}
+
+ <FormField
+ htmlFor="enter_username_validation"
+ className="sw-mt-6 sw-mb-3"
+ label={translate('onboarding.create_project.bitbucket_cloud.enter_username')}
+ required
+ >
+ <div>
+ <InputField
+ size="large"
+ id="enter_username_validation"
+ minLength={1}
+ value={username}
+ onChange={handleUsernameChange}
+ type="text"
+ isInvalid={isInvalid}
+ />
+ {isInvalid && <FlagErrorIcon className="sw-ml-2" />}
+ </div>
+ </FormField>
+
+ <div className="sw-mb-6">
+ <FlagMessage variant="info">
+ <p>
+ <FormattedMessage
+ id="onboarding.enter_username.instructions.bitbucket_cloud"
+ defaultMessage={translate('onboarding.enter_username.instructions.bitbucket_cloud')}
+ values={{
+ link: (
+ <Link to="https://bitbucket.org/account/settings/">
+ {translate('onboarding.enter_username.instructions.bitbucket_cloud.link')}
+ </Link>
+ ),
+ }}
+ />
+ </p>
+ </FlagMessage>
+ </div>
+
+ <FormField
+ htmlFor="enter_password_validation"
+ className="sw-mt-6 sw-mb-3"
+ label={translate('onboarding.create_project.bitbucket_cloud.enter_password')}
+ required
+ >
+ <div>
+ <InputField
+ size="large"
+ id="enter_password_validation"
+ minLength={1}
+ value={password}
+ onChange={handlePasswordChange}
+ type="text"
+ isInvalid={isInvalid}
+ />
+ {isInvalid && <FlagErrorIcon className="sw-ml-2" />}
+ </div>
+ </FormField>
+
+ <div className="sw-mb-6">
+ <FlagMessage variant="info">
+ <p>
+ <FormattedMessage
+ id="onboarding.create_project.enter_password.instructions.bitbucket_cloud"
+ defaultMessage={translate(
+ 'onboarding.create_project.enter_password.instructions.bitbucket_cloud'
+ )}
+ values={{
+ link: (
+ <Link to="https://bitbucket.org/account/settings/app-passwords/new">
+ {translate(
+ 'onboarding.create_project.enter_password.instructions.bitbucket_cloud.link'
+ )}
+ </Link>
+ ),
+ }}
+ />
+ </p>
+ </FlagMessage>
+ </div>
+
+ <ButtonPrimary type="submit" disabled={submitButtonDiabled} className="sw-mb-6">
+ {translate('save')}
+ </ButtonPrimary>
+ <DeferredSpinner className="sw-ml-2" loading={submitting} />
+ </form>
+ );
+}
import { BitbucketCloudRepository } from '../../../../types/alm-integration';
import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings';
import AlmSettingsInstanceDropdown from '../components/AlmSettingsInstanceDropdown';
-import PersonalAccessTokenForm from '../components/PersonalAccessTokenForm';
import WrongBindingCountAlert from '../components/WrongBindingCountAlert';
+import BitbucketCloudPersonalAccessTokenForm from './BitbucketCloudPersonalAccessTokenForm';
import BitbucketCloudSearchForm from './BitbucketCloudSearchForm';
export interface BitbucketCloudProjectCreateRendererProps {
{!loading &&
selectedAlmInstance &&
(showPersonalAccessTokenForm ? (
- <PersonalAccessTokenForm
+ <BitbucketCloudPersonalAccessTokenForm
almSetting={selectedAlmInstance}
resetPat={resetPat}
onPersonalAccessTokenCreated={props.onPersonalAccessTokenCreated}
} from '../../../../types/alm-integration';
import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings';
import AlmSettingsInstanceDropdown from '../components/AlmSettingsInstanceDropdown';
-import PersonalAccessTokenForm from '../components/PersonalAccessTokenForm';
import WrongBindingCountAlert from '../components/WrongBindingCountAlert';
import BitbucketImportRepositoryForm from './BitbucketImportRepositoryForm';
+import BitbucketServerPersonalAccessTokenForm from './BitbucketServerPersonalAccessTokenForm';
export interface BitbucketProjectCreateRendererProps {
selectedAlmInstance?: AlmSettingsInstance;
{selectedAlmInstance &&
(showPersonalAccessTokenForm ? (
- <PersonalAccessTokenForm
+ <BitbucketServerPersonalAccessTokenForm
almSetting={selectedAlmInstance}
onPersonalAccessTokenCreated={props.onPersonalAccessTokenCreated}
resetPat={resetPat}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 {
+ ButtonPrimary,
+ DeferredSpinner,
+ FlagErrorIcon,
+ FlagMessage,
+ FormField,
+ InputField,
+ LightPrimary,
+ Link,
+} from 'design-system';
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { translate } from '../../../../helpers/l10n';
+import { AlmSettingsInstance } from '../../../../types/alm-settings';
+import { usePersonalAccessToken } from '../usePersonalAccessToken';
+
+interface Props {
+ almSetting: AlmSettingsInstance;
+ resetPat: boolean;
+ onPersonalAccessTokenCreated: () => void;
+}
+
+export default function BitbucketServerPersonalAccessTokenForm({
+ almSetting,
+ resetPat,
+ onPersonalAccessTokenCreated,
+}: Props) {
+ const {
+ password,
+ firstConnection,
+ validationFailed,
+ touched,
+ submitting,
+ validationErrorMessage,
+ checkingPat,
+ handlePasswordChange,
+ handleSubmit,
+ } = usePersonalAccessToken(almSetting, resetPat, onPersonalAccessTokenCreated);
+
+ if (checkingPat) {
+ return <DeferredSpinner className="sw-ml-2" loading />;
+ }
+
+ const { url } = almSetting;
+ const isInvalid = validationFailed && !touched;
+ const canSubmit = Boolean(password);
+ const submitButtonDiabled = isInvalid || submitting || !canSubmit;
+
+ const errorMessage =
+ validationErrorMessage ?? translate('onboarding.create_project.pat_incorrect.bitbucket');
+
+ return (
+ <form className="sw-mt-3 sw-w-[50%]" onSubmit={handleSubmit}>
+ <LightPrimary as="h2" className="sw-heading-md">
+ {translate('onboarding.create_project.pat_form.title')}
+ </LightPrimary>
+ <LightPrimary as="p" className="sw-mt-2 sw-mb-4 sw-body-sm">
+ {translate('onboarding.create_project.pat_form.help.bitbucket')}
+ </LightPrimary>
+
+ {isInvalid && (
+ <div>
+ <FlagMessage variant="error" className="sw-mb-4">
+ <p>{errorMessage}</p>
+ </FlagMessage>
+ </div>
+ )}
+
+ {!firstConnection && (
+ <FlagMessage variant="warning">
+ <p>
+ {translate('onboarding.create_project.pat.expired.info_message')}{' '}
+ {translate('onboarding.create_project.pat.expired.info_message_contact')}
+ </p>
+ </FlagMessage>
+ )}
+
+ <FormField
+ htmlFor="personal_access_token_validation"
+ className="sw-mt-6 sw-mb-3"
+ label={translate('onboarding.create_project.enter_pat')}
+ required
+ >
+ <div>
+ <InputField
+ autoFocus
+ size="large"
+ id="personal_access_token_validation"
+ minLength={1}
+ value={password}
+ onChange={handlePasswordChange}
+ type="text"
+ isInvalid={isInvalid}
+ />
+ {isInvalid && <FlagErrorIcon className="sw-ml-2" />}
+ </div>
+ </FormField>
+
+ <div className="sw-mb-6">
+ <FlagMessage variant="info">
+ <p>
+ <FormattedMessage
+ id="onboarding.create_project.pat_help.instructions.bitbucket_server"
+ defaultMessage={translate(
+ 'onboarding.create_project.pat_help.instructions.bitbucket_server'
+ )}
+ values={{
+ link: url ? (
+ <Link to={`${url.replace(/\/$/, '')}/account`}>
+ {translate(
+ 'onboarding.create_project.pat_help.instructions.bitbucket_server.link'
+ )}
+ </Link>
+ ) : (
+ translate('onboarding.create_project.pat_help.instructions.bitbucket_server.link')
+ ),
+ }}
+ />
+ </p>
+ </FlagMessage>
+ </div>
+
+ <ButtonPrimary type="submit" disabled={submitButtonDiabled} className="sw-mb-6">
+ {translate('save')}
+ </ButtonPrimary>
+ <DeferredSpinner className="sw-ml-2" loading={submitting} />
+ </form>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 {
+ ButtonPrimary,
+ DeferredSpinner,
+ FlagErrorIcon,
+ FlagMessage,
+ FormField,
+ InputField,
+ LightPrimary,
+ Link,
+} from 'design-system';
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { translate } from '../../../../helpers/l10n';
+import { AlmSettingsInstance } from '../../../../types/alm-settings';
+import { usePersonalAccessToken } from '../usePersonalAccessToken';
+
+interface Props {
+ almSetting: AlmSettingsInstance;
+ resetPat: boolean;
+ onPersonalAccessTokenCreated: () => void;
+}
+
+export default function GitlabPersonalAccessTokenForm({
+ almSetting,
+ resetPat,
+ onPersonalAccessTokenCreated,
+}: Props) {
+ const {
+ password,
+ firstConnection,
+ validationFailed,
+ touched,
+ submitting,
+ validationErrorMessage,
+ checkingPat,
+ handlePasswordChange,
+ handleSubmit,
+ } = usePersonalAccessToken(almSetting, resetPat, onPersonalAccessTokenCreated);
+
+ if (checkingPat) {
+ return <DeferredSpinner className="sw-ml-2" loading />;
+ }
+
+ const isInvalid = validationFailed && !touched;
+ const canSubmit = Boolean(password);
+ const submitButtonDiabled = isInvalid || submitting || !canSubmit;
+
+ const errorMessage =
+ validationErrorMessage ?? translate('onboarding.create_project.pat_incorrect.gitlab');
+
+ return (
+ <form className="sw-mt-3 sw-w-[50%]" onSubmit={handleSubmit}>
+ <LightPrimary as="h2" className="sw-heading-md">
+ {translate('onboarding.create_project.pat_form.title')}
+ </LightPrimary>
+ <LightPrimary as="p" className="sw-mt-2 sw-mb-4 sw-body-sm">
+ {translate('onboarding.create_project.pat_form.help.gitlab')}
+ </LightPrimary>
+
+ {isInvalid && (
+ <div>
+ <FlagMessage variant="error" className="sw-mb-4">
+ <p>{errorMessage}</p>
+ </FlagMessage>
+ </div>
+ )}
+
+ {!firstConnection && (
+ <FlagMessage variant="warning">
+ <p>
+ {translate('onboarding.create_project.pat.expired.info_message')}{' '}
+ {translate('onboarding.create_project.pat.expired.info_message_contact')}
+ </p>
+ </FlagMessage>
+ )}
+
+ <FormField
+ htmlFor="personal_access_token_validation"
+ className="sw-mt-6 sw-mb-3"
+ label={translate('onboarding.create_project.enter_pat')}
+ required
+ >
+ <div>
+ <InputField
+ autoFocus
+ size="large"
+ id="personal_access_token_validation"
+ minLength={1}
+ value={password}
+ onChange={handlePasswordChange}
+ type="text"
+ isInvalid={isInvalid}
+ />
+ {isInvalid && <FlagErrorIcon className="sw-ml-2" />}
+ </div>
+ </FormField>
+
+ <div className="sw-mb-6">
+ <FlagMessage variant="info">
+ <p>
+ <FormattedMessage
+ id="onboarding.create_project.pat_help.instructions.gitlab"
+ defaultMessage={translate('onboarding.create_project.pat_help.instructions.gitlab')}
+ values={{
+ link: (
+ <Link to="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html">
+ {translate('onboarding.create_project.pat_help.instructions.gitlab.link')}
+ </Link>
+ ),
+ }}
+ />
+ </p>
+ </FlagMessage>
+ </div>
+
+ <ButtonPrimary type="submit" disabled={submitButtonDiabled} className="sw-mb-6">
+ {translate('save')}
+ </ButtonPrimary>
+ <DeferredSpinner className="sw-ml-2" loading={submitting} />
+ </form>
+ );
+}
import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings';
import { Paging } from '../../../../types/types';
import AlmSettingsInstanceDropdown from '../components/AlmSettingsInstanceDropdown';
-import PersonalAccessTokenForm from '../components/PersonalAccessTokenForm';
import WrongBindingCountAlert from '../components/WrongBindingCountAlert';
+import GitlabPersonalAccessTokenForm from './GItlabPersonalAccessTokenForm';
import GitlabProjectSelectionForm from './GitlabProjectSelectionForm';
export interface GitlabProjectCreateRendererProps {
{!loading &&
selectedAlmInstance &&
(showPersonalAccessTokenForm ? (
- <PersonalAccessTokenForm
+ <GitlabPersonalAccessTokenForm
almSetting={selectedAlmInstance}
resetPat={resetPat}
onPersonalAccessTokenCreated={props.onPersonalAccessTokenCreated}
expect(screen.getByText('alm.configuration.selector.label.alm.azure.long')).toBeInTheDocument();
expect(screen.getByText('onboarding.create_project.enter_pat')).toBeInTheDocument();
- expect(screen.getByText('onboarding.create_project.pat_form.title.azure')).toBeInTheDocument();
- expect(
- screen.getByRole('button', { name: 'onboarding.create_project.pat_form.list_repositories' })
- ).toBeInTheDocument();
+ expect(screen.getByText('onboarding.create_project.pat_form.title')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'save' })).toBeInTheDocument();
await user.click(ui.personalAccessTokenInput.get());
await user.keyboard('secret');
- await user.click(
- screen.getByRole('button', { name: 'onboarding.create_project.pat_form.list_repositories' })
- );
+ await user.click(screen.getByRole('button', { name: 'save' }));
expect(screen.getByText('Azure project')).toBeInTheDocument();
expect(screen.getByText('Azure project 2')).toBeInTheDocument();
expect(screen.getByText('onboarding.create_project.from_bbs')).toBeInTheDocument();
expect(await ui.instanceSelector.find()).toBeInTheDocument();
- expect(
- screen.getByText('onboarding.create_project.pat_form.title.bitbucket')
- ).toBeInTheDocument();
+ expect(screen.getByText('onboarding.create_project.pat_form.title')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'save' })).toBeDisabled();
expect(await ui.instanceSelector.find()).toBeInTheDocument();
expect(
- screen.getByText('onboarding.create_project.enter_pat.bitbucketcloud')
+ screen.getByText('onboarding.create_project.bitbucket_cloud.enter_password')
).toBeInTheDocument();
expect(
- screen.getByText(
- 'onboarding.create_project.pat_help.instructions_username.bitbucketcloud.title'
- )
+ screen.getByText('onboarding.create_project.enter_password.instructions.bitbucket_cloud')
).toBeInTheDocument();
expect(
- screen.getByText('onboarding.create_project.pat.expired.info_message')
- ).toBeInTheDocument();
- expect(
- screen.getByText('onboarding.create_project.pat.expired.info_message_contact')
+ screen.getByText(
+ 'onboarding.create_project.pat.expired.info_message onboarding.create_project.pat.expired.info_message_contact'
+ )
).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'save' })).toBeDisabled();
await user.click(
screen.getByRole('textbox', {
- name: /onboarding.create_project.enter_username/,
+ name: /onboarding.create_project.bitbucket_cloud.enter_username/,
})
);
await user.click(
screen.getByRole('textbox', {
- name: /onboarding.create_project.enter_pat.bitbucketcloud/,
+ name: /onboarding.create_project.bitbucket_cloud.enter_password/,
})
);
expect(ui.instanceSelector.get()).toBeInTheDocument();
expect(screen.getByText('onboarding.create_project.enter_pat')).toBeInTheDocument();
- expect(screen.getByText('onboarding.create_project.pat_help.title')).toBeInTheDocument();
+ expect(
+ screen.getByText('onboarding.create_project.pat_help.instructions.gitlab')
+ ).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'save' })).toBeInTheDocument();
await act(async () => {
await user.click(ui.personalAccessTokenInput.get());
</LightPrimary>
)}
</div>
- <div className="sw-max-w-[50%] sw-min-w-0 sw-ml-2 sw-flex sw-items-center sw-truncate">
- <LightLabel className="sw-body-sm">{secondaryTextNode}</LightLabel>
+ <div className="sw-max-w-[50%] sw-min-w-0 sw-ml-2 sw-flex sw-items-center">
+ <LightLabel className="sw-body-sm sw-truncate">{secondaryTextNode}</LightLabel>
</div>
</div>
{almUrl !== undefined && (
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 classNames from 'classnames';
-import * as React from 'react';
-import { FormattedMessage } from 'react-intl';
-import {
- checkPersonalAccessTokenIsValid,
- setAlmPersonalAccessToken,
-} from '../../../../api/alm-integrations';
-import ValidationInput from '../../../../components/controls/ValidationInput';
-import { SubmitButton } from '../../../../components/controls/buttons';
-import { Alert } from '../../../../components/ui/Alert';
-import DeferredSpinner from '../../../../components/ui/DeferredSpinner';
-import { translate } from '../../../../helpers/l10n';
-import { getBaseUrl } from '../../../../helpers/system';
-import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings';
-import { tokenExistedBefore } from '../utils';
-
-interface Props {
- almSetting: AlmSettingsInstance;
- resetPat: boolean;
- onPersonalAccessTokenCreated: () => void;
-}
-
-interface State {
- validationFailed: boolean;
- validationErrorMessage?: string;
- touched: boolean;
- password: string;
- username?: string;
- submitting: boolean;
- checkingPat: boolean;
- firstConnection: boolean;
-}
-
-function getPatUrl(alm: AlmKeys, url = '') {
- if (alm === AlmKeys.BitbucketServer) {
- return `${url.replace(/\/$/, '')}/account`;
- } else if (alm === AlmKeys.BitbucketCloud) {
- return 'https://bitbucket.org/account/settings/app-passwords/new';
- } else if (alm === AlmKeys.GitLab) {
- return 'https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html';
- }
-
- return '';
-}
-
-export default class PersonalAccessTokenForm extends React.PureComponent<Props, State> {
- mounted = false;
-
- constructor(props: Props) {
- super(props);
-
- this.state = {
- checkingPat: false,
- touched: false,
- password: '',
- submitting: false,
- validationFailed: false,
- firstConnection: false,
- };
- }
-
- componentDidMount() {
- this.mounted = true;
- this.checkPATAndUpdateView();
- }
-
- componentDidUpdate(prevProps: Props) {
- if (this.props.almSetting !== prevProps.almSetting) {
- this.checkPATAndUpdateView();
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- checkPATAndUpdateView = async () => {
- const {
- almSetting: { key },
- resetPat,
- } = this.props;
-
- // We don't need to check PAT if we want to reset
- if (!resetPat) {
- this.setState({ checkingPat: true });
- const { patIsValid, error } = await checkPersonalAccessTokenIsValid(key)
- .then(({ status, error }) => ({ patIsValid: status, error }))
- .catch(() => ({ patIsValid: status, error: translate('default_error_message') }));
- if (patIsValid) {
- this.props.onPersonalAccessTokenCreated();
- }
- if (this.mounted) {
- // This is the initial message when no token was provided
- if (tokenExistedBefore(error)) {
- this.setState({
- checkingPat: false,
- firstConnection: true,
- });
- } else {
- this.setState({
- checkingPat: false,
- validationFailed: true,
- validationErrorMessage: error,
- });
- }
- }
- }
- };
-
- handleUsernameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
- this.setState({
- touched: true,
- username: event.target.value,
- });
- };
-
- handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => {
- this.setState({
- touched: true,
- password: event.target.value,
- });
- };
-
- handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
- const { password, username } = this.state;
- const {
- almSetting: { key },
- } = this.props;
-
- e.preventDefault();
- if (password) {
- this.setState({ submitting: true });
-
- await setAlmPersonalAccessToken(key, password, username).catch(() => {
- /* Set will not check pat validity. We need to check again so we will catch issue after */
- });
-
- const { status, error } = await checkPersonalAccessTokenIsValid(key)
- .then(({ status, error }) => ({ status, error }))
- .catch(() => ({ status: false, error: translate('default_error_message') }));
-
- if (this.mounted && status) {
- // Let's reset status,
- this.setState({
- checkingPat: false,
- touched: false,
- password: '',
- submitting: false,
- username: '',
- validationFailed: false,
- });
- this.props.onPersonalAccessTokenCreated();
- } else if (this.mounted) {
- this.setState({
- submitting: false,
- touched: false,
- validationFailed: true,
- validationErrorMessage: error,
- });
- }
- }
- };
-
- renderHelpBox(suffixTranslationKey: string) {
- const {
- almSetting: { alm, url },
- } = this.props;
-
- return (
- <Alert className="big-spacer-left width-50" display="block" variant="info">
- {alm === AlmKeys.BitbucketCloud && (
- <>
- <h3>
- {translate(
- 'onboarding.create_project.pat_help.instructions_username.bitbucketcloud.title'
- )}
- </h3>
- <p className="big-spacer-top big-spacer-bottom">
- {translate('onboarding.create_project.pat_help.instructions_username.bitbucketcloud')}
- </p>
-
- <div className="text-middle big-spacer-bottom">
- <img
- alt="" // Should be ignored by screen readers
- className="spacer-right"
- height="16"
- src={`${getBaseUrl()}/images/alm/${AlmKeys.BitbucketServer}.svg`}
- />
- <a
- href="https://bitbucket.org/account/settings/"
- rel="noopener noreferrer"
- target="_blank"
- >
- {translate(
- 'onboarding.create_project.pat_help.instructions_username.bitbucketcloud.link'
- )}
- </a>
- </div>
- </>
- )}
-
- <h3>{translate(`onboarding.create_project.pat_help${suffixTranslationKey}.title`)}</h3>
-
- <p className="big-spacer-top big-spacer-bottom">
- {alm === AlmKeys.BitbucketServer ? (
- <FormattedMessage
- id="onboarding.create_project.pat_help.instructions"
- defaultMessage={translate(
- `onboarding.create_project.pat_help.bitbucket.instructions`
- )}
- values={{
- menu: (
- <strong>
- {translate('onboarding.create_project.pat_help.bitbucket.instructions.menu')}
- </strong>
- ),
- button: (
- <strong>
- {translate('onboarding.create_project.pat_help.bitbucket.instructions.button')}
- </strong>
- ),
- }}
- />
- ) : (
- <FormattedMessage
- id="onboarding.create_project.pat_help.instructions"
- defaultMessage={translate(
- `onboarding.create_project.pat_help${suffixTranslationKey}.instructions`
- )}
- values={{
- alm: translate('onboarding.alm', alm),
- }}
- />
- )}
- </p>
-
- {(url || alm === AlmKeys.BitbucketCloud) && (
- <div className="text-middle">
- <img
- alt="" // Should be ignored by screen readers
- className="spacer-right"
- height="16"
- src={`${getBaseUrl()}/images/alm/${
- alm === AlmKeys.BitbucketCloud ? AlmKeys.BitbucketServer : alm
- }.svg`}
- />
- <a href={getPatUrl(alm, url)} rel="noopener noreferrer" target="_blank">
- {translate(`onboarding.create_project.pat_help${suffixTranslationKey}.link`)}
- </a>
- </div>
- )}
-
- <p className="big-spacer-top big-spacer-bottom">
- {translate('onboarding.create_project.pat_help.instructions2', alm)}
- </p>
-
- <ul>
- {alm === AlmKeys.BitbucketServer && (
- <li>
- <FormattedMessage
- defaultMessage={translate(
- 'onboarding.create_project.pat_help.bbs_permission_projects'
- )}
- id="onboarding.create_project.pat_help.bbs_permission_projects"
- values={{
- perm: (
- <strong>
- {translate('onboarding.create_project.pat_help.read_permission')}
- </strong>
- ),
- }}
- />
- </li>
- )}
- {(alm === AlmKeys.BitbucketServer || alm === AlmKeys.BitbucketCloud) && (
- <li>
- <FormattedMessage
- defaultMessage={translate(
- 'onboarding.create_project.pat_help.bbs_permission_repos'
- )}
- id="onboarding.create_project.pat_help.bbs_permission_repos"
- values={{
- perm: (
- <strong>
- {translate('onboarding.create_project.pat_help.read_permission')}
- </strong>
- ),
- }}
- />
- </li>
- )}
-
- {alm === AlmKeys.GitLab && (
- <li className="spacer-bottom">
- <strong>
- {translate('onboarding.create_project.pat_help.gitlab.read_api_permission')}
- </strong>
- </li>
- )}
- </ul>
- </Alert>
- );
- }
-
- render() {
- const {
- almSetting: { alm },
- } = this.props;
- const {
- checkingPat,
- submitting,
- touched,
- password,
- username,
- validationFailed,
- validationErrorMessage,
- firstConnection,
- } = this.state;
-
- if (checkingPat) {
- return <DeferredSpinner className="spacer-left" loading />;
- }
-
- const suffixTranslationKey = alm === AlmKeys.BitbucketCloud ? '.bitbucketcloud' : '';
-
- const isInvalid = validationFailed && !touched;
- const canSubmit = Boolean(password) && (alm !== AlmKeys.BitbucketCloud || Boolean(username));
- const submitButtonDiabled = isInvalid || submitting || !canSubmit;
-
- const errorMessage =
- validationErrorMessage ?? translate('onboarding.create_project.pat_incorrect', alm);
-
- return (
- <div className="display-flex-start">
- <form className="width-50" onSubmit={this.handleSubmit}>
- <h2 className="big">{translate('onboarding.create_project.pat_form.title', alm)}</h2>
- <p className="big-spacer-top big-spacer-bottom">
- {translate('onboarding.create_project.pat_form.help', alm)}
- </p>
-
- {!firstConnection && (
- <Alert className="big-spacer-right" variant="warning">
- <p>{translate('onboarding.create_project.pat.expired.info_message')}</p>
- <p>{translate('onboarding.create_project.pat.expired.info_message_contact')}</p>
- </Alert>
- )}
-
- {alm === AlmKeys.BitbucketCloud && (
- <ValidationInput
- error={undefined}
- labelHtmlFor="enter_username_validation"
- isInvalid={false}
- isValid={false}
- label={translate('onboarding.create_project.enter_username')}
- required
- >
- <input
- autoFocus
- className={classNames('input-super-large', {
- 'is-invalid': isInvalid,
- })}
- id="enter_username_validation"
- minLength={1}
- name="username"
- value={username}
- onChange={this.handleUsernameChange}
- type="text"
- />
- </ValidationInput>
- )}
-
- <ValidationInput
- error={errorMessage}
- labelHtmlFor="personal_access_token_validation"
- isInvalid={false}
- isValid={false}
- label={translate(`onboarding.create_project.enter_pat${suffixTranslationKey}`)}
- required
- >
- <input
- autoFocus={alm !== AlmKeys.BitbucketCloud}
- className={classNames('input-super-large', {
- 'is-invalid': isInvalid,
- })}
- id="personal_access_token_validation"
- minLength={1}
- value={password}
- onChange={this.handlePasswordChange}
- type="text"
- />
- </ValidationInput>
-
- <ValidationInput
- error={errorMessage}
- labelHtmlFor="personal_access_token_submit"
- isInvalid={isInvalid}
- isValid={false}
- label={null}
- >
- <SubmitButton disabled={submitButtonDiabled}>{translate('save')}</SubmitButton>
- <DeferredSpinner className="spacer-left" loading={submitting} />
- </ValidationInput>
- </form>
-
- {this.renderHelpBox(suffixTranslationKey)}
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { useEffect, useState } from 'react';
+import {
+ checkPersonalAccessTokenIsValid,
+ setAlmPersonalAccessToken,
+} from '../../../api/alm-integrations';
+import { translate } from '../../../helpers/l10n';
+import { AlmSettingsInstance } from '../../../types/alm-settings';
+import { tokenExistedBefore } from './utils';
+
+export interface PATType {
+ validationFailed: boolean;
+ validationErrorMessage?: string;
+ touched: boolean;
+ password: string;
+ username?: string;
+ submitting: boolean;
+ checkingPat: boolean;
+ firstConnection: boolean;
+ handleUsernameChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
+ handlePasswordChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
+ handleSubmit: (e: React.SyntheticEvent<HTMLFormElement>) => Promise<void>;
+}
+
+export const usePersonalAccessToken = (
+ almSetting: AlmSettingsInstance,
+ resetPat: boolean,
+ onPersonalAccessTokenCreated: () => void
+): PATType => {
+ const [checkingPat, setCheckingPat] = useState(false);
+ const [touched, setTouched] = useState(false);
+ const [password, setPassword] = useState('');
+ const [submitting, setSubmitting] = useState(false);
+ const [validationFailed, setValidationFailed] = useState(false);
+ const [validationErrorMessage, setValidationErrorMessage] = useState<string | undefined>();
+ const [firstConnection, setFirstConnection] = useState(false);
+ const [username, setUsername] = useState('');
+
+ useEffect(() => {
+ const checkPATAndUpdateView = async () => {
+ const { key } = almSetting;
+
+ // We don't need to check PAT if we want to reset
+ if (!resetPat) {
+ setCheckingPat(true);
+ const { patIsValid, error } = await checkPersonalAccessTokenIsValid(key)
+ .then(({ status, error }) => ({ patIsValid: status, error }))
+ .catch(() => ({ patIsValid: status, error: translate('default_error_message') }));
+ if (patIsValid) {
+ onPersonalAccessTokenCreated();
+ return;
+ }
+ // This is the initial message when no token was provided
+ if (tokenExistedBefore(error)) {
+ setCheckingPat(false);
+ setFirstConnection(true);
+ } else {
+ setCheckingPat(false);
+ setValidationFailed(true);
+ setValidationErrorMessage(error);
+ }
+ }
+ };
+ checkPATAndUpdateView();
+ }, [almSetting, resetPat, onPersonalAccessTokenCreated]);
+
+ const handleUsernameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+ setTouched(true);
+ setUsername(event.target.value);
+ };
+
+ const handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+ setTouched(true);
+ setPassword(event.target.value);
+ };
+
+ const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
+ const { key } = almSetting;
+
+ e.preventDefault();
+ if (password) {
+ setSubmitting(true);
+
+ await setAlmPersonalAccessToken(key, password, username).catch(() => {
+ /* Set will not check pat validity. We need to check again so we will catch issue after */
+ });
+
+ const { status, error } = await checkPersonalAccessTokenIsValid(key)
+ .then(({ status, error }) => ({ status, error }))
+ .catch(() => ({ status: false, error: translate('default_error_message') }));
+
+ if (status) {
+ // Let's reset status,
+ setCheckingPat(false);
+ setTouched(false);
+ setPassword('');
+ setSubmitting(false);
+ setUsername('');
+ setValidationFailed(false);
+
+ onPersonalAccessTokenCreated();
+ } else {
+ setSubmitting(false);
+ setTouched(false);
+ setValidationFailed(true);
+ setValidationErrorMessage(error);
+ }
+ }
+ };
+
+ return {
+ username,
+ password,
+ firstConnection,
+ validationFailed,
+ touched,
+ submitting,
+ checkingPat,
+ validationErrorMessage,
+ handleUsernameChange,
+ handlePasswordChange,
+ handleSubmit,
+ };
+};
onboarding.create_project.from_bbs=Bitbucket Server project onboarding
onboarding.create_application.key.description=If specified, this value is used as the key instead of generating it from the name of the Application. Only letters, digits, dashes and underscores can be used.
-
-onboarding.create_project.pat_form.title.azure=Allow SonarQube to access and list your Azure DevOps repositories
-onboarding.create_project.pat_form.title.bitbucket=Grant access to your repositories
-onboarding.create_project.pat_form.title.bitbucketcloud=Grant access to your repositories
-onboarding.create_project.pat_form.title.gitlab=Grant access to your projects
+onboarding.create_project.pat_form.title=Grant access to your repositories
onboarding.create_project.pat_form.help.azure=SonarQube needs a personal access token to access and list your repositories from Azure DevOps.
onboarding.create_project.pat_form.help.bitbucket=SonarQube needs a personal access token to access and list your repositories from Bitbucket Server.
-onboarding.create_project.pat_form.help.bitbucketcloud=SonarQube needs an app password to access and list your repositories from Bitbucket Cloud.
+onboarding.create_project.pat_form.help.bitbucket_cloud=SonarQube needs an app password to access and list your repositories from Bitbucket Cloud.
onboarding.create_project.pat_form.help.gitlab=SonarQube needs a personal access token to access and list your projects from GitLab.
onboarding.create_project.pat_form.pat_required=Please enter a personal access token
-onboarding.create_project.pat_form.list_repositories=List repositories
onboarding.create_project.wrong_binding_count=You must have at least 1 {alm} instance configured in order to use this method, but none were found. Either create the project manually, or contact your system administrator.
onboarding.create_project.wrong_binding_count.admin=You must have at least 1 {alm} instance configured in order to use this method. You can configure instances under {url}.
onboarding.create_project.azure.no_url.admin=Your Azure DevOps instance configuration is missing a URL. We cannot import projects in the current state. You can configure instances under {url}.
onboarding.create_project.azure.no_url=Your Azure DevOps instance configuration is missing a URL. We cannot import projects in the current state. Please contact your system administrator.
-onboarding.create_project.enter_pat=Enter personal access token
-onboarding.create_project.enter_pat.bitbucketcloud=Enter your app password
-onboarding.create_project.enter_username=Enter your Bitbucket username
+onboarding.create_project.enter_pat=Personal Access Token
+onboarding.create_project.bitbucket_cloud.enter_password=App password
+onboarding.create_project.bitbucket_cloud.enter_username=BitBucket username
onboarding.create_project.pat_incorrect.azure=Your personal access couldn't be validated.
onboarding.create_project.pat_incorrect.bitbucket=Your personal access couldn't be validated.
-onboarding.create_project.pat_incorrect.bitbucketcloud=Your app password couldn't be validated.
+onboarding.create_project.pat_incorrect.bitbucket_cloud=Your app password couldn't be validated.
onboarding.create_project.pat_incorrect.gitlab=Your personal access couldn't be validated. Please make sure it has the right scope and that it is not expired.
-onboarding.create_project.pat_help.title=How to create a personal access token?
-onboarding.create_project.pat_help.bitbucketcloud.title=How to create an app password?
onboarding.create_project.pat.expired.info_message=You have to do this again as your token may have expired or has been revoked.
onboarding.create_project.pat.expired.info_message_contact=If this does not fix the issue, please contact your system administrator.
-onboarding.create_project.pat_help.instructions.azure=Create and provide an Azure DevOps {link}. You need to select the {scope} scope so we can display a list of your repositories which are available for analysis.
-onboarding.create_project.pat_help.instructions.link.azure=personal access token
-
-onboarding.create_project.pat_help.instructions=Click the following link to generate a token in {alm}, and copy-paste it into the personal access token field.
-onboarding.create_project.pat_help.bitbucketcloud.instructions=Click the following link to generate an app password, and copy-paste it into the app password field.
-onboarding.create_project.pat_help.bitbucket.instructions=Click the following link and go to {menu} and click {button} to generate a token. Then, copy-paste it into the personal access token field.
-onboarding.create_project.pat_help.bitbucket.instructions.menu=HTTP access tokens
-onboarding.create_project.pat_help.bitbucket.instructions.button=Create token
+onboarding.create_project.pat_help.instructions.azure=To create a Personal Access Token on Azure, {link} and make sure to select the “Code (Read & Write)” scope.
+onboarding.create_project.pat_help.instructions.link.azure=generate a token
+onboarding.create_project.pat_help.instructions.gitlab=To create a Personal Access Token on GitLab, {link} by setting a name, for example “SonarQube” and selecting the “read_api” scope.
+onboarding.create_project.pat_help.instructions.gitlab.link=generate a token
-onboarding.create_project.pat_help.instructions2.bitbucket=Set a name, for example "SonarQube", and select the following permissions:
-onboarding.create_project.pat_help.instructions2.bitbucketcloud=Set a name, for example "SonarQube", and select the following permissions:
+onboarding.enter_username.instructions.bitbucket_cloud=You can find your username in your {link}
+onboarding.enter_username.instructions.bitbucket_cloud.link=BitBucket profile settings
+onboarding.create_project.enter_password.instructions.bitbucket_cloud=To create an app password on BitBucket, {link} by setting a name, for example “SonarQube” and selecting the “Repositories: Read” permissions.
+onboarding.create_project.enter_password.instructions.bitbucket_cloud.link=add an app password
-onboarding.create_project.pat_help.instructions_username.bitbucketcloud.title=How to find your username?
-onboarding.create_project.pat_help.instructions_username.bitbucketcloud=Click the following link to find your username in the Bitbucket profile settings
-onboarding.create_project.pat_help.instructions_username.bitbucketcloud.link=Personal settings
+onboarding.create_project.pat_help.instructions.bitbucket_server=To create a Personal Access Token on BitBucket Server, go to {link} and click on “Create token”. Set a name, for example “SonarQube” and select the following permissions “Projects: Read” “Repositories: Read”.
+onboarding.create_project.pat_help.instructions.bitbucket_server.link=HTTP access tokens
-onboarding.create_project.pat_help.link=Create personal access token
-onboarding.create_project.pat_help.bitbucketcloud.link=Add app password
-onboarding.create_project.pat_help.bbs_permission_projects=Projects: {perm}
-onboarding.create_project.pat_help.bbs_permission_repos=Repositories: {perm}
-onboarding.create_project.pat_help.read_permission=Read
+onboarding.create_project.pat_help.instructions2.bitbucket=Set a name, for example "SonarQube", and select the following permissions:
+onboarding.create_project.pat_help.instructions2.bitbucketcloud=Set a name, for example "SonarQube", and select the following permissions:
onboarding.create_project.pat_help.instructions2.gitlab=Set a name, for example "SonarQube", and select the following scope:
onboarding.create_project.pat_help.gitlab.read_api_permission=read_api