aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps
diff options
context:
space:
mode:
authorguillaume-peoch-sonarsource <guillaume.peoch@sonarsource.com>2023-01-10 17:52:05 +0100
committersonartech <sonartech@sonarsource.com>2023-01-13 20:02:47 +0000
commit1d92a9229b3c0a7c2fe8548087c2ff71512bb999 (patch)
treeac391ef1e81540a0b7a6a03245fe37cc2cdda57d /server/sonar-web/src/main/js/apps
parentea602214bb0e0ae1a79b381c8e12a424d58b085c (diff)
downloadsonarqube-1d92a9229b3c0a7c2fe8548087c2ff71512bb999.tar.gz
sonarqube-1d92a9229b3c0a7c2fe8548087c2ff71512bb999.zip
SONAR-13109 Users should understand why they're asked for their personal access token
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/AzurePersonalAccessTokenForm.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx22
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/utils.ts23
6 files changed, 73 insertions, 6 deletions
diff --git a/server/sonar-web/src/main/js/apps/create/project/AzurePersonalAccessTokenForm.tsx b/server/sonar-web/src/main/js/apps/create/project/AzurePersonalAccessTokenForm.tsx
index 6fad0fc51f9..24a81c34506 100644
--- a/server/sonar-web/src/main/js/apps/create/project/AzurePersonalAccessTokenForm.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/AzurePersonalAccessTokenForm.tsx
@@ -23,6 +23,7 @@ import { FormattedMessage } from 'react-intl';
import Link from '../../../components/common/Link';
import { SubmitButton } from '../../../components/controls/buttons';
import ValidationInput from '../../../components/controls/ValidationInput';
+import { Alert } from '../../../components/ui/Alert';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
import { AlmSettingsInstance } from '../../../types/alm-settings';
@@ -32,6 +33,7 @@ export interface AzurePersonalAccessTokenFormProps {
onPersonalAccessTokenCreate: (token: string) => void;
submitting?: boolean;
validationFailed: boolean;
+ firstConnection?: boolean;
}
function getAzurePatUrl(url: string) {
@@ -43,6 +45,7 @@ export default function AzurePersonalAccessTokenForm(props: AzurePersonalAccessT
almSetting: { alm, url },
submitting = false,
validationFailed,
+ firstConnection,
} = props;
const [touched, setTouched] = React.useState(false);
@@ -87,6 +90,13 @@ export default function AzurePersonalAccessTokenForm(props: AzurePersonalAccessT
/>
</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>
+ )}
+
<form
onSubmit={(e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault();
diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx
index 36adf6d3a78..94a98a331ed 100644
--- a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx
@@ -31,6 +31,7 @@ import { AzureProject, AzureRepository } from '../../../types/alm-integration';
import { AlmSettingsInstance } from '../../../types/alm-settings';
import { Dict } from '../../../types/types';
import AzureCreateProjectRenderer from './AzureProjectCreateRenderer';
+import { tokenExistedBefore } from './utils';
interface Props {
canAdmin: boolean;
@@ -55,6 +56,7 @@ interface State {
selectedAlmInstance?: AlmSettingsInstance;
submittingToken?: boolean;
tokenValidationFailed: boolean;
+ firstConnection?: boolean;
}
export default class AzureProjectCreate extends React.PureComponent<Props, State> {
@@ -71,6 +73,7 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State
loadingRepositories: {},
repositories: {},
tokenValidationFailed: false,
+ firstConnection: false,
};
}
@@ -92,7 +95,7 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State
fetchData = async () => {
this.setState({ loading: true });
- const patIsValid = await this.checkPersonalAccessToken().catch(() => false);
+ const { patIsValid, error } = await this.checkPersonalAccessToken();
let projects: AzureProject[] | undefined;
if (patIsValid) {
@@ -126,6 +129,7 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State
loadingRepositories: { ...loadingRepositories },
projects,
repositories,
+ firstConnection: tokenExistedBefore(error),
};
});
}
@@ -239,10 +243,12 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State
const { selectedAlmInstance } = this.state;
if (!selectedAlmInstance) {
- return Promise.resolve(false);
+ return Promise.resolve({ patIsValid: false, error: '' });
}
- return checkPersonalAccessTokenIsValid(selectedAlmInstance.key).then(({ status }) => status);
+ return checkPersonalAccessTokenIsValid(selectedAlmInstance.key).then(({ status, error }) => {
+ return { patIsValid: status, error };
+ });
};
handlePersonalAccessTokenCreate = async (token: string) => {
@@ -256,10 +262,14 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State
try {
await setAlmPersonalAccessToken(selectedAlmInstance.key, token);
- const patIsValid = await this.checkPersonalAccessToken();
+ const { patIsValid } = await this.checkPersonalAccessToken();
if (this.mounted) {
- this.setState({ submittingToken: false, patIsValid, tokenValidationFailed: !patIsValid });
+ this.setState({
+ submittingToken: false,
+ patIsValid,
+ tokenValidationFailed: !patIsValid,
+ });
if (patIsValid) {
this.cleanUrl();
@@ -296,6 +306,7 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State
selectedAlmInstance,
submittingToken,
tokenValidationFailed,
+ firstConnection,
} = this.state;
return (
@@ -321,6 +332,7 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State
submittingToken={submittingToken}
tokenValidationFailed={tokenValidationFailed}
onSelectedAlmInstanceChange={this.onSelectedAlmInstanceChange}
+ firstConnection={firstConnection}
/>
);
}
diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx
index 6ce5722a56f..d3ad3e456b6 100644
--- a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx
@@ -59,6 +59,7 @@ export interface AzureProjectCreateRendererProps {
submittingToken?: boolean;
tokenValidationFailed: boolean;
onSelectedAlmInstanceChange: (instance: AlmSettingsInstance) => void;
+ firstConnection?: boolean;
}
export default function AzureProjectCreateRenderer(props: AzureProjectCreateRendererProps) {
@@ -78,6 +79,7 @@ export default function AzureProjectCreateRenderer(props: AzureProjectCreateRend
submittingToken,
tokenValidationFailed,
selectedAlmInstance,
+ firstConnection,
} = props;
const showCountError = !loading && (!almInstances || almInstances?.length === 0);
@@ -157,6 +159,7 @@ export default function AzureProjectCreateRenderer(props: AzureProjectCreateRend
onPersonalAccessTokenCreate={props.onPersonalAccessTokenCreate}
submitting={submittingToken}
validationFailed={tokenValidationFailed}
+ firstConnection={firstConnection}
/>
</div>
) : (
diff --git a/server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx b/server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx
index 68a478709b6..589b8c72264 100644
--- a/server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx
@@ -31,6 +31,7 @@ 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;
@@ -46,6 +47,7 @@ interface State {
username?: string;
submitting: boolean;
checkingPat: boolean;
+ firstConnection: boolean;
}
function getPatUrl(alm: AlmKeys, url = '') {
@@ -72,6 +74,7 @@ export default class PersonalAccessTokenForm extends React.PureComponent<Props,
password: '',
submitting: false,
validationFailed: false,
+ firstConnection: false,
};
}
@@ -107,9 +110,10 @@ export default class PersonalAccessTokenForm extends React.PureComponent<Props,
}
if (this.mounted) {
// This is the initial message when no token was provided
- if (error === `personal access token for '${key}' is missing`) {
+ if (tokenExistedBefore(error)) {
this.setState({
checkingPat: false,
+ firstConnection: true,
});
} else {
this.setState({
@@ -329,6 +333,7 @@ export default class PersonalAccessTokenForm extends React.PureComponent<Props,
username,
validationFailed,
validationErrorMessage,
+ firstConnection,
} = this.state;
if (checkingPat) {
@@ -352,6 +357,13 @@ export default class PersonalAccessTokenForm extends React.PureComponent<Props,
{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}
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx
index 60cbc8eb988..35ea5813023 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx
@@ -76,6 +76,13 @@ it('should ask for PAT when it is not set yet and show the import project featur
)
).toBeInTheDocument();
+ expect(
+ screen.getByText('onboarding.create_project.pat.expired.info_message')
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText('onboarding.create_project.pat.expired.info_message_contact')
+ ).toBeInTheDocument();
+
expect(screen.getByRole('button', { name: 'save' })).toBeDisabled();
await user.click(
diff --git a/server/sonar-web/src/main/js/apps/create/project/utils.ts b/server/sonar-web/src/main/js/apps/create/project/utils.ts
new file mode 100644
index 00000000000..6725280145e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/create/project/utils.ts
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+export function tokenExistedBefore(error?: string) {
+ return error?.includes('is missing');
+}