aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/api/components.ts9
-rw-r--r--server/sonar-web/src/main/js/api/mocks/UserTokensMock.ts2
-rw-r--r--server/sonar-web/src/main/js/api/user-tokens.ts9
-rw-r--r--server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx111
-rw-r--r--server/sonar-web/src/main/js/apps/account/account.css2
-rw-r--r--server/sonar-web/src/main/js/apps/account/security/Tokens.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx194
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/TokensFormModal.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/__tests__/TokensForm-test.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/__tests__/TokensFormItem-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensForm-test.tsx.snap10
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensFormModal-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/__tests__/utils-test.ts2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/TokenStep.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/utils.ts2
-rw-r--r--server/sonar-web/src/main/js/types/permissions.ts3
-rw-r--r--server/sonar-web/src/main/js/types/token.ts38
-rw-r--r--server/sonar-web/src/main/js/types/types.ts11
19 files changed, 307 insertions, 113 deletions
diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts
index eebd23b059f..ea9c6f2621e 100644
--- a/server/sonar-web/src/main/js/api/components.ts
+++ b/server/sonar-web/src/main/js/api/components.ts
@@ -299,3 +299,12 @@ export function getTests(
): Promise<any> {
return getJSON('/api/tests/list', data).then(r => r.tests);
}
+
+interface ProjectResponse {
+ key: string;
+ name: string;
+}
+
+export function getScannableProjects(): Promise<{ projects: ProjectResponse[] }> {
+ return getJSON('/api/projects/search_my_scannable_projects').catch(throwGlobalError);
+}
diff --git a/server/sonar-web/src/main/js/api/mocks/UserTokensMock.ts b/server/sonar-web/src/main/js/api/mocks/UserTokensMock.ts
index 9348f46a5ca..18f26798257 100644
--- a/server/sonar-web/src/main/js/api/mocks/UserTokensMock.ts
+++ b/server/sonar-web/src/main/js/api/mocks/UserTokensMock.ts
@@ -19,7 +19,7 @@
*/
import { cloneDeep } from 'lodash';
-import { NewUserToken, UserToken } from '../../types/types';
+import { NewUserToken, UserToken } from '../../types/token';
import { generateToken, getTokens, revokeToken } from '../user-tokens';
const RANDOM_RADIX = 36;
diff --git a/server/sonar-web/src/main/js/api/user-tokens.ts b/server/sonar-web/src/main/js/api/user-tokens.ts
index 53daaf04b4b..1cb261c2a8e 100644
--- a/server/sonar-web/src/main/js/api/user-tokens.ts
+++ b/server/sonar-web/src/main/js/api/user-tokens.ts
@@ -19,14 +19,19 @@
*/
import { throwGlobalError } from '../helpers/error';
import { getJSON, post, postJSON } from '../helpers/request';
-import { NewUserToken, UserToken } from '../types/types';
+import { NewUserToken, UserToken } from '../types/token';
/** List tokens for given user login */
export function getTokens(login: string): Promise<UserToken[]> {
return getJSON('/api/user_tokens/search', { login }).then(r => r.userTokens, throwGlobalError);
}
-export function generateToken(data: { name: string; login?: string }): Promise<NewUserToken> {
+export function generateToken(data: {
+ name: string;
+ projectKey?: string;
+ type?: string;
+ login?: string;
+}): Promise<NewUserToken> {
return postJSON('/api/user_tokens/generate', data).catch(throwGlobalError);
}
diff --git a/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx b/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx
index 3190d691de7..87511d9efc4 100644
--- a/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx
+++ b/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx
@@ -20,12 +20,15 @@
import { screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserEvent } from '@testing-library/user-event/dist/types/setup';
+import selectEvent from 'react-select-event';
import { getMyProjects } from '../../../api/components';
import NotificationsMock from '../../../api/mocks/NotificationsMock';
import UserTokensMock from '../../../api/mocks/UserTokensMock';
import getHistory from '../../../helpers/getHistory';
import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks';
import { renderApp } from '../../../helpers/testReactTestingUtils';
+import { Permissions } from '../../../types/permissions';
+import { TokenType } from '../../../types/token';
import { CurrentUser } from '../../../types/users';
import routes from '../routes';
@@ -96,6 +99,18 @@ jest.mock('../../../api/components', () => ({
]
}
]
+ }),
+ getScannableProjects: jest.fn().mockResolvedValue({
+ projects: [
+ {
+ key: 'project-key-1',
+ name: 'Project Name 1'
+ },
+ {
+ key: 'project-key-2',
+ name: 'Project Name 2'
+ }
+ ]
})
}));
@@ -192,47 +207,77 @@ describe('security page', () => {
const securityPagePath = 'account/security';
- it('should allow token creation/revoking and display existing tokens', async () => {
- const user = userEvent.setup();
-
- renderAccountApp(mockLoggedInUser(), securityPagePath);
-
- expect(await screen.findByText('users.tokens')).toBeInTheDocument();
- expect(screen.getAllByRole('row')).toHaveLength(3); // 2 tokens + header
-
- // Add a token
- const newTokenName = 'importantToken';
- const input = screen.getByRole('textbox');
- const generateButton = screen.getByRole('button', { name: 'users.generate' });
- expect(input).toBeInTheDocument();
- await user.click(input);
- await user.keyboard(newTokenName);
+ it.each([
+ ['user', TokenType.User],
+ ['global', TokenType.Global],
+ ['project analysis', TokenType.Project]
+ ])(
+ 'should allow %s token creation/revocation and display existing tokens',
+ async (_, tokenTypeOption) => {
+ const user = userEvent.setup();
+
+ renderAccountApp(
+ mockLoggedInUser({ permissions: { global: [Permissions.Scan] } }),
+ securityPagePath
+ );
+
+ expect(await screen.findByText('users.tokens')).toBeInTheDocument();
+ expect(screen.getAllByRole('row')).toHaveLength(3); // 2 tokens + header
+
+ // Add the token
+ const newTokenName = 'importantToken';
+ const input = screen.getByPlaceholderText('users.enter_token_name');
+ const generateButton = screen.getByRole('button', { name: 'users.generate' });
+ expect(input).toBeInTheDocument();
+ await user.click(input);
+ await user.keyboard(newTokenName);
+
+ expect(generateButton).toBeDisabled();
+
+ const tokenTypeLabel = `users.tokens.${tokenTypeOption}`;
+
+ if (tokenTypeOption === TokenType.Project) {
+ await selectEvent.select(screen.getAllByRole('textbox')[1], [tokenTypeLabel]);
+ expect(generateButton).toBeDisabled();
+ expect(screen.getAllByRole('textbox')).toHaveLength(3);
+ await selectEvent.select(screen.getAllByRole('textbox')[2], ['Project Name 1']);
+ expect(generateButton).not.toBeDisabled();
+ } else {
+ await selectEvent.select(screen.getAllByRole('textbox')[1], [tokenTypeLabel]);
+ expect(generateButton).not.toBeDisabled();
+ }
- expect(generateButton).not.toBeDisabled();
- await user.click(generateButton);
+ await user.click(generateButton);
- expect(
- await screen.findByText(`users.tokens.new_token_created.${newTokenName}`)
- ).toBeInTheDocument();
- expect(screen.getByRole('button', { name: 'copy' })).toBeInTheDocument();
+ expect(
+ await screen.findByText(`users.tokens.new_token_created.${newTokenName}`)
+ ).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'copy' })).toBeInTheDocument();
- const lastTokenCreated = tokenMock.getTokens().pop();
- expect(lastTokenCreated).toBeDefined();
- expect(screen.getByLabelText('users.new_token').textContent).toBe(lastTokenCreated!.token);
+ const lastTokenCreated = tokenMock.getTokens().pop();
+ expect(lastTokenCreated).toBeDefined();
+ expect(screen.getByLabelText('users.new_token').textContent).toBe(lastTokenCreated!.token);
- expect(screen.getAllByRole('row')).toHaveLength(4); // 3 tokens + header
+ expect(screen.getAllByRole('row')).toHaveLength(4); // 3 tokens + header
- // Revoke a token
- const revokeButtons = screen.getAllByRole('button', { name: 'users.tokens.revoke_token' });
- expect(revokeButtons).toHaveLength(3);
- await user.click(revokeButtons[1]);
+ // Revoke the token
+ const row = screen.getAllByRole('row', {
+ name: (n: string) => n.includes(newTokenName)
+ });
+ const revokeButtons = within(row[0]).getByRole('button', {
+ name: 'users.tokens.revoke_token'
+ });
+ await user.click(revokeButtons);
- expect(screen.getByRole('heading', { name: 'users.tokens.revoke_token' })).toBeInTheDocument();
+ expect(
+ screen.getByRole('heading', { name: 'users.tokens.revoke_token' })
+ ).toBeInTheDocument();
- await user.click(screen.getByText('users.tokens.revoke_token', { selector: 'button' }));
+ await user.click(screen.getByText('users.tokens.revoke_token', { selector: 'button' }));
- expect(screen.getAllByRole('row')).toHaveLength(3); // 2 tokens + header
- });
+ expect(screen.getAllByRole('row')).toHaveLength(3); // 2 tokens + header
+ }
+ );
it('should allow local users to change password', async () => {
const user = userEvent.setup();
diff --git a/server/sonar-web/src/main/js/apps/account/account.css b/server/sonar-web/src/main/js/apps/account/account.css
index 322907768fe..f05b97da368 100644
--- a/server/sonar-web/src/main/js/apps/account/account.css
+++ b/server/sonar-web/src/main/js/apps/account/account.css
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
.account-container {
- width: 600px;
+ width: 700px;
margin-left: auto;
margin-right: auto;
}
diff --git a/server/sonar-web/src/main/js/apps/account/security/Tokens.tsx b/server/sonar-web/src/main/js/apps/account/security/Tokens.tsx
index 44c67d9f9a0..ab586526275 100644
--- a/server/sonar-web/src/main/js/apps/account/security/Tokens.tsx
+++ b/server/sonar-web/src/main/js/apps/account/security/Tokens.tsx
@@ -35,7 +35,7 @@ export default function Tokens({ login }: Props) {
<InstanceMessage message={translate('my_account.tokens_description')} />
</div>
- <TokensForm deleteConfirmation="modal" login={login} />
+ <TokensForm deleteConfirmation="modal" login={login} displayTokenTypeInput={true} />
</div>
</div>
);
diff --git a/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx b/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx
index aa086e8f470..efc7a785bd2 100644
--- a/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx
+++ b/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx
@@ -18,11 +18,17 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { getScannableProjects } from '../../../api/components';
import { generateToken, getTokens } from '../../../api/user-tokens';
+import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
import { SubmitButton } from '../../../components/controls/buttons';
+import Select, { BasicSelectOption } from '../../../components/controls/Select';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
-import { UserToken } from '../../../types/types';
+import { hasGlobalPermission } from '../../../helpers/users';
+import { Permissions } from '../../../types/permissions';
+import { TokenType, UserToken } from '../../../types/token';
+import { CurrentUser } from '../../../types/users';
import TokensFormItem, { TokenDeleteConfirmation } from './TokensFormItem';
import TokensFormNewToken from './TokensFormNewToken';
@@ -30,6 +36,8 @@ interface Props {
deleteConfirmation: TokenDeleteConfirmation;
login: string;
updateTokensCount?: (login: string, tokensCount: number) => void;
+ displayTokenTypeInput: boolean;
+ currentUser: CurrentUser;
}
interface State {
@@ -37,16 +45,22 @@ interface State {
loading: boolean;
newToken?: { name: string; token: string };
newTokenName: string;
+ newTokenType?: TokenType;
tokens: UserToken[];
+ projects: BasicSelectOption[];
+ selectedProjectkey?: string;
}
-export default class TokensForm extends React.PureComponent<Props, State> {
+export class TokensForm extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
generating: false,
loading: true,
newTokenName: '',
- tokens: []
+ newTokenType: this.props.displayTokenTypeInput ? undefined : TokenType.User,
+ selectedProjectkey: '',
+ tokens: [],
+ projects: []
};
componentDidMount() {
@@ -74,34 +88,51 @@ export default class TokensForm extends React.PureComponent<Props, State> {
);
};
+ fetchProjects = async () => {
+ const { projects: projectArray } = await getScannableProjects();
+ const projects = projectArray.map(project => ({ label: project.name, value: project.key }));
+ this.setState({
+ projects
+ });
+ };
+
updateTokensCount = () => {
if (this.props.updateTokensCount) {
this.props.updateTokensCount(this.props.login, this.state.tokens.length);
}
};
- handleGenerateToken = (evt: React.SyntheticEvent<HTMLFormElement>) => {
- evt.preventDefault();
- if (this.state.newTokenName.length > 0) {
- this.setState({ generating: true });
- generateToken({ name: this.state.newTokenName, login: this.props.login }).then(
- newToken => {
- if (this.mounted) {
- this.setState(state => {
- const tokens = [
- ...state.tokens,
- { name: newToken.name, createdAt: newToken.createdAt }
- ];
- return { generating: false, newToken, newTokenName: '', tokens };
- }, this.updateTokensCount);
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ generating: false });
- }
- }
- );
+ handleGenerateToken = async (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
+ const { login } = this.props;
+ const { newTokenName, newTokenType, selectedProjectkey } = this.state;
+ this.setState({ generating: true });
+
+ try {
+ const newToken = await generateToken({
+ name: newTokenName,
+ login,
+ type: newTokenType,
+ ...(newTokenType === TokenType.Project && { projectKey: selectedProjectkey })
+ });
+
+ if (this.mounted) {
+ this.setState(state => {
+ const tokens = [...state.tokens, { name: newToken.name, createdAt: newToken.createdAt }];
+ return {
+ generating: false,
+ newToken,
+ newTokenName: '',
+ selectedProjectkey: '',
+ newTokenType: undefined,
+ tokens
+ };
+ }, this.updateTokensCount);
+ }
+ } catch (e) {
+ if (this.mounted) {
+ this.setState({ generating: false });
+ }
}
};
@@ -114,8 +145,93 @@ export default class TokensForm extends React.PureComponent<Props, State> {
);
};
- handleNewTokenChange = (evt: React.SyntheticEvent<HTMLInputElement>) =>
+ isSubmitButtonDisabled = () => {
+ const { displayTokenTypeInput } = this.props;
+ const { generating, newTokenName, newTokenType, selectedProjectkey } = this.state;
+
+ if (!displayTokenTypeInput) {
+ return generating || newTokenName.length <= 0;
+ }
+
+ if (generating || newTokenName.length <= 0) {
+ return true;
+ }
+ if (newTokenType === TokenType.Project) {
+ return !selectedProjectkey;
+ }
+
+ return !newTokenType;
+ };
+
+ handleNewTokenChange = (evt: React.SyntheticEvent<HTMLInputElement>) => {
this.setState({ newTokenName: evt.currentTarget.value });
+ };
+
+ handleNewTokenTypeChange = ({ value }: { value: TokenType }) => {
+ if (value === TokenType.Project && this.state.projects.length === 0) {
+ this.fetchProjects();
+ }
+ this.setState({ newTokenType: value });
+ };
+
+ handleProjectChange = ({ value }: { value: string }) => {
+ this.setState({ selectedProjectkey: value });
+ };
+
+ renderForm() {
+ const { newTokenName, newTokenType, projects, selectedProjectkey } = this.state;
+ const { displayTokenTypeInput, currentUser } = this.props;
+
+ const tokenTypeOptions = [
+ { label: translate('users.tokens', TokenType.Project), value: TokenType.Project },
+ { label: translate('users.tokens', TokenType.User), value: TokenType.User }
+ ];
+ if (hasGlobalPermission(currentUser, Permissions.Scan)) {
+ tokenTypeOptions.push({
+ label: translate('users.tokens', TokenType.Global),
+ value: TokenType.Global
+ });
+ }
+
+ return (
+ <form autoComplete="off" className="display-flex-center" onSubmit={this.handleGenerateToken}>
+ <input
+ className="input-large spacer-right it__token-name"
+ maxLength={100}
+ onChange={this.handleNewTokenChange}
+ placeholder={translate('users.enter_token_name')}
+ required={true}
+ type="text"
+ value={newTokenName}
+ />
+ {displayTokenTypeInput && (
+ <>
+ <Select
+ className="input-medium spacer-right it__token-type"
+ isSearchable={false}
+ onChange={this.handleNewTokenTypeChange}
+ options={tokenTypeOptions}
+ placeholder={translate('users.select_token_type')}
+ value={tokenTypeOptions.find(option => option.value === newTokenType) || null}
+ />
+ {newTokenType === TokenType.Project && (
+ <Select
+ className="input-medium spacer-right it__project"
+ onChange={this.handleProjectChange}
+ options={projects}
+ placeholder={translate('users.select_token_project')}
+ value={projects.find(project => project.value === selectedProjectkey)}
+ />
+ )}
+ </>
+ )}
+
+ <SubmitButton className="it__generate-token" disabled={this.isSubmitButtonDisabled()}>
+ {translate('users.generate')}
+ </SubmitButton>
+ </form>
+ );
+ }
renderItems() {
const { tokens } = this.state;
@@ -140,7 +256,7 @@ export default class TokensForm extends React.PureComponent<Props, State> {
}
render() {
- const { generating, loading, newToken, newTokenName, tokens } = this.state;
+ const { loading, newToken, tokens } = this.state;
const customSpinner = (
<tr>
<td>
@@ -152,27 +268,7 @@ export default class TokensForm extends React.PureComponent<Props, State> {
return (
<>
<h3 className="spacer-bottom">{translate('users.generate_tokens')}</h3>
- <form
- autoComplete="off"
- className="display-flex-center"
- id="generate-token-form"
- onSubmit={this.handleGenerateToken}>
- <input
- className="input-large spacer-right"
- maxLength={100}
- onChange={this.handleNewTokenChange}
- placeholder={translate('users.enter_token_name')}
- required={true}
- type="text"
- value={newTokenName}
- />
- <SubmitButton
- className="js-generate-token"
- disabled={generating || newTokenName.length <= 0}>
- {translate('users.generate')}
- </SubmitButton>
- </form>
-
+ {this.renderForm()}
{newToken && <TokensFormNewToken token={newToken} />}
<table className="data zebra big-spacer-top">
@@ -194,3 +290,5 @@ export default class TokensForm extends React.PureComponent<Props, State> {
);
}
}
+
+export default withCurrentUserContext(TokensForm);
diff --git a/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx b/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx
index 3bd08ee79d3..54e448af15a 100644
--- a/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx
+++ b/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx
@@ -28,7 +28,7 @@ import DateFromNow from '../../../components/intl/DateFromNow';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
import { limitComponentName } from '../../../helpers/path';
-import { UserToken } from '../../../types/types';
+import { UserToken } from '../../../types/token';
export type TokenDeleteConfirmation = 'inline' | 'modal';
diff --git a/server/sonar-web/src/main/js/apps/users/components/TokensFormModal.tsx b/server/sonar-web/src/main/js/apps/users/components/TokensFormModal.tsx
index 85981cf315e..adffa467372 100644
--- a/server/sonar-web/src/main/js/apps/users/components/TokensFormModal.tsx
+++ b/server/sonar-web/src/main/js/apps/users/components/TokensFormModal.tsx
@@ -48,6 +48,7 @@ export default function TokensFormModal(props: Props) {
deleteConfirmation="inline"
login={props.user.login}
updateTokensCount={props.updateTokensCount}
+ displayTokenTypeInput={false}
/>
</div>
<footer className="modal-foot">
diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/TokensForm-test.tsx b/server/sonar-web/src/main/js/apps/users/components/__tests__/TokensForm-test.tsx
index ca6877bdee5..c1a47007695 100644
--- a/server/sonar-web/src/main/js/apps/users/components/__tests__/TokensForm-test.tsx
+++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/TokensForm-test.tsx
@@ -20,8 +20,10 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { generateToken, getTokens } from '../../../../api/user-tokens';
+import { mockCurrentUser } from '../../../../helpers/testMocks';
import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils';
-import TokensForm from '../TokensForm';
+import { TokenType } from '../../../../types/token';
+import { TokensForm } from '../TokensForm';
jest.mock('../../../../api/user-tokens', () => ({
generateToken: jest.fn().mockResolvedValue({
@@ -63,7 +65,7 @@ it('should create new tokens', async () => {
submit(wrapper.find('form'));
await waitAndUpdate(wrapper);
- expect(generateToken).toHaveBeenCalledWith({ name: 'baz', login: 'luke' });
+ expect(generateToken).toHaveBeenCalledWith({ name: 'baz', login: 'luke', type: TokenType.User });
expect(wrapper.find('TokensFormItem')).toHaveLength(3);
});
@@ -80,6 +82,13 @@ it('should revoke tokens', async () => {
function shallowRender(props: Partial<TokensForm['props']> = {}) {
return shallow<TokensForm>(
- <TokensForm deleteConfirmation="inline" login="luke" updateTokensCount={jest.fn()} {...props} />
+ <TokensForm
+ deleteConfirmation="inline"
+ login="luke"
+ updateTokensCount={jest.fn()}
+ displayTokenTypeInput={false}
+ currentUser={mockCurrentUser()}
+ {...props}
+ />
);
}
diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/TokensFormItem-test.tsx b/server/sonar-web/src/main/js/apps/users/components/__tests__/TokensFormItem-test.tsx
index 0719dcd129c..5bbd567c3ce 100644
--- a/server/sonar-web/src/main/js/apps/users/components/__tests__/TokensFormItem-test.tsx
+++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/TokensFormItem-test.tsx
@@ -21,7 +21,7 @@ import { shallow } from 'enzyme';
import * as React from 'react';
import { revokeToken } from '../../../../api/user-tokens';
import { click, waitAndUpdate } from '../../../../helpers/testUtils';
-import { UserToken } from '../../../../types/types';
+import { UserToken } from '../../../../types/token';
import TokensFormItem from '../TokensFormItem';
jest.mock('../../../../components/intl/DateFormatter');
diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensForm-test.tsx.snap
index 4b2092b196c..e7f3202ed19 100644
--- a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensForm-test.tsx.snap
@@ -10,11 +10,10 @@ exports[`should render correctly 1`] = `
<form
autoComplete="off"
className="display-flex-center"
- id="generate-token-form"
onSubmit={[Function]}
>
<input
- className="input-large spacer-right"
+ className="input-large spacer-right it__token-name"
maxLength={100}
onChange={[Function]}
placeholder="users.enter_token_name"
@@ -23,7 +22,7 @@ exports[`should render correctly 1`] = `
value=""
/>
<SubmitButton
- className="js-generate-token"
+ className="it__generate-token"
disabled={true}
>
users.generate
@@ -85,11 +84,10 @@ exports[`should render correctly 2`] = `
<form
autoComplete="off"
className="display-flex-center"
- id="generate-token-form"
onSubmit={[Function]}
>
<input
- className="input-large spacer-right"
+ className="input-large spacer-right it__token-name"
maxLength={100}
onChange={[Function]}
placeholder="users.enter_token_name"
@@ -98,7 +96,7 @@ exports[`should render correctly 2`] = `
value=""
/>
<SubmitButton
- className="js-generate-token"
+ className="it__generate-token"
disabled={true}
>
users.generate
diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensFormModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensFormModal-test.tsx.snap
index 436ea23f061..6738ba5964c 100644
--- a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensFormModal-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensFormModal-test.tsx.snap
@@ -25,8 +25,9 @@ exports[`should render correctly 1`] = `
<div
className="modal-body modal-container"
>
- <TokensForm
+ <withCurrentUserContext(TokensForm)
deleteConfirmation="inline"
+ displayTokenTypeInput={false}
login="john.doe"
updateTokensCount={[MockFunction]}
/>
diff --git a/server/sonar-web/src/main/js/components/tutorials/__tests__/utils-test.ts b/server/sonar-web/src/main/js/components/tutorials/__tests__/utils-test.ts
index db06e7d6989..35382d5004b 100644
--- a/server/sonar-web/src/main/js/components/tutorials/__tests__/utils-test.ts
+++ b/server/sonar-web/src/main/js/components/tutorials/__tests__/utils-test.ts
@@ -22,7 +22,7 @@ import {
mockProjectBitbucketCloudBindingResponse,
mockProjectGithubBindingResponse
} from '../../../helpers/mocks/alm-settings';
-import { UserToken } from '../../../types/types';
+import { UserToken } from '../../../types/token';
import { buildBitbucketCloudLink, buildGithubLink, getUniqueTokenName } from '../utils';
describe('getUniqueTokenName', () => {
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/TokenStep.tsx b/server/sonar-web/src/main/js/components/tutorials/manual/TokenStep.tsx
index df9e3b63869..ae7bd3fd968 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/TokenStep.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/TokenStep.tsx
@@ -25,7 +25,7 @@ import { Button, DeleteButton, SubmitButton } from '../../../components/controls
import Radio from '../../../components/controls/Radio';
import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
import { translate } from '../../../helpers/l10n';
-import { UserToken } from '../../../types/types';
+import { UserToken } from '../../../types/token';
import { LoggedInUser } from '../../../types/users';
import AlertErrorIcon from '../../icons/AlertErrorIcon';
import Step from '../components/Step';
diff --git a/server/sonar-web/src/main/js/components/tutorials/utils.ts b/server/sonar-web/src/main/js/components/tutorials/utils.ts
index 9ab0680b696..6e040c3f09a 100644
--- a/server/sonar-web/src/main/js/components/tutorials/utils.ts
+++ b/server/sonar-web/src/main/js/components/tutorials/utils.ts
@@ -19,7 +19,7 @@
*/
import { convertGithubApiUrlToLink, stripTrailingSlash } from '../../helpers/urls';
import { AlmSettingsInstance, ProjectAlmBindingResponse } from '../../types/alm-settings';
-import { UserToken } from '../../types/types';
+import { UserToken } from '../../types/token';
export function quote(os: string): (s: string) => string {
return os === 'win' ? (s: string) => `"${s}"` : (s: string) => s;
diff --git a/server/sonar-web/src/main/js/types/permissions.ts b/server/sonar-web/src/main/js/types/permissions.ts
index 9145adf37f9..f87015df8c1 100644
--- a/server/sonar-web/src/main/js/types/permissions.ts
+++ b/server/sonar-web/src/main/js/types/permissions.ts
@@ -21,5 +21,6 @@ export enum Permissions {
Admin = 'admin',
ProjectCreation = 'provisioning',
ApplicationCreation = 'applicationcreator',
- QualityGateAdmin = 'gateadmin'
+ QualityGateAdmin = 'gateadmin',
+ Scan = 'scan'
}
diff --git a/server/sonar-web/src/main/js/types/token.ts b/server/sonar-web/src/main/js/types/token.ts
new file mode 100644
index 00000000000..50681e01da4
--- /dev/null
+++ b/server/sonar-web/src/main/js/types/token.ts
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+export enum TokenType {
+ Project = 'PROJECT_ANALYSIS_TOKEN',
+ Global = 'GLOBAL_ANALYSIS_TOKEN',
+ User = 'USER_TOKEN'
+}
+
+export interface UserToken {
+ name: string;
+ createdAt: string;
+ lastConnectionDate?: string;
+ type?: TokenType;
+ project?: { name: string; key: string };
+}
+
+export interface NewUserToken extends UserToken {
+ login: string;
+ token: string;
+}
diff --git a/server/sonar-web/src/main/js/types/types.ts b/server/sonar-web/src/main/js/types/types.ts
index ae12b847d84..a376854de4a 100644
--- a/server/sonar-web/src/main/js/types/types.ts
+++ b/server/sonar-web/src/main/js/types/types.ts
@@ -767,17 +767,6 @@ export interface UserSelected extends UserActive {
selected: boolean;
}
-export interface UserToken {
- name: string;
- createdAt: string;
- lastConnectionDate?: string;
-}
-
-export interface NewUserToken extends UserToken {
- login: string;
- token: string;
-}
-
export type Visibility = 'public' | 'private';
export interface Webhook {