Ver código fonte

SONAR-16263 Update Token type in list

tags/9.5.0.56709
Guillaume Peoc'h 2 anos atrás
pai
commit
5a12c30d5f

+ 19
- 6
server/sonar-web/src/main/js/api/mocks/UserTokensMock.ts Ver arquivo

@@ -19,22 +19,23 @@
*/

import { cloneDeep } from 'lodash';
import { NewUserToken, UserToken } from '../../types/token';
import { mockUserToken } from '../../helpers/mocks/token';
import { NewUserToken, TokenType, UserToken } from '../../types/token';
import { generateToken, getTokens, revokeToken } from '../user-tokens';

const RANDOM_RADIX = 36;
const RANDOM_PREFIX = 2;

const defaultTokens = [
{
mockUserToken({
name: 'local-scanner',
createdAt: '2022-03-07T09:02:59+0000',
lastConnectionDate: '2022-04-07T09:51:48+0000'
},
{
}),
mockUserToken({
name: 'test',
createdAt: '2020-01-23T19:25:19+0000'
}
})
];

export default class UserTokensMock {
@@ -52,10 +53,22 @@ export default class UserTokensMock {
return Promise.resolve(cloneDeep(this.tokens));
};

handleGenerateToken = ({ name, login }: { name: string; login?: string }) => {
handleGenerateToken = ({
name,
login,
type,
projectKey
}: {
name: string;
login?: string;
type: TokenType;
projectKey: string;
}) => {
const token = {
name,
login,
type,
projectKey,
token: Math.random()
.toString(RANDOM_RADIX)
.slice(RANDOM_PREFIX),

+ 11
- 4
server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx Ver arquivo

@@ -235,6 +235,7 @@ describe('security page', () => {
expect(generateButton).toBeDisabled();

const tokenTypeLabel = `users.tokens.${tokenTypeOption}`;
const tokenTypeShortLabel = `users.tokens.${tokenTypeOption}.short`;

if (tokenTypeOption === TokenType.Project) {
await selectEvent.select(screen.getAllByRole('textbox')[1], [tokenTypeLabel]);
@@ -260,11 +261,17 @@ describe('security page', () => {

expect(screen.getAllByRole('row')).toHaveLength(4); // 3 tokens + header

// Revoke the token
const row = screen.getAllByRole('row', {
name: (n: string) => n.includes(newTokenName)
const row = screen.getByRole('row', {
name: new RegExp(`^${newTokenName}`)
});
const revokeButtons = within(row[0]).getByRole('button', {

expect(await within(row).findByText(tokenTypeShortLabel)).toBeInTheDocument();
if (tokenTypeOption === TokenType.Project) {
expect(await within(row).findByText('Project Name 1')).toBeInTheDocument();
}

// Revoke the token
const revokeButtons = within(row).getByRole('button', {
name: 'users.tokens.revoke_token'
});
await user.click(revokeButtons);

+ 1
- 1
server/sonar-web/src/main/js/apps/account/account.css Ver arquivo

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
.account-container {
width: 700px;
width: 800px;
margin-left: auto;
margin-right: auto;
}

+ 27
- 15
server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx Ver arquivo

@@ -48,7 +48,7 @@ interface State {
newTokenType?: TokenType;
tokens: UserToken[];
projects: BasicSelectOption[];
selectedProjectkey?: string;
selectedProject: { key: string; name: string };
}

export class TokensForm extends React.PureComponent<Props, State> {
@@ -58,7 +58,7 @@ export class TokensForm extends React.PureComponent<Props, State> {
loading: true,
newTokenName: '',
newTokenType: this.props.displayTokenTypeInput ? undefined : TokenType.User,
selectedProjectkey: '',
selectedProject: { key: '', name: '' },
tokens: [],
projects: []
};
@@ -105,7 +105,7 @@ export class TokensForm extends React.PureComponent<Props, State> {
handleGenerateToken = async (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();
const { login } = this.props;
const { newTokenName, newTokenType, selectedProjectkey } = this.state;
const { newTokenName, newTokenType = TokenType.User, selectedProject } = this.state;
this.setState({ generating: true });

try {
@@ -113,17 +113,27 @@ export class TokensForm extends React.PureComponent<Props, State> {
name: newTokenName,
login,
type: newTokenType,
...(newTokenType === TokenType.Project && { projectKey: selectedProjectkey })
...(newTokenType === TokenType.Project && { projectKey: selectedProject.key })
});

if (this.mounted) {
this.setState(state => {
const tokens = [...state.tokens, { name: newToken.name, createdAt: newToken.createdAt }];
const tokens = [
...state.tokens,
{
name: newToken.name,
createdAt: newToken.createdAt,
type: newTokenType,
...(newTokenType === TokenType.Project && {
project: { key: selectedProject.key, name: selectedProject.name }
})
}
];
return {
generating: false,
newToken,
newTokenName: '',
selectedProjectkey: '',
selectedProject: { key: '', name: '' },
newTokenType: undefined,
tokens
};
@@ -147,7 +157,7 @@ export class TokensForm extends React.PureComponent<Props, State> {

isSubmitButtonDisabled = () => {
const { displayTokenTypeInput } = this.props;
const { generating, newTokenName, newTokenType, selectedProjectkey } = this.state;
const { generating, newTokenName, newTokenType, selectedProject } = this.state;

if (!displayTokenTypeInput) {
return generating || newTokenName.length <= 0;
@@ -157,7 +167,7 @@ export class TokensForm extends React.PureComponent<Props, State> {
return true;
}
if (newTokenType === TokenType.Project) {
return !selectedProjectkey;
return !selectedProject.key;
}

return !newTokenType;
@@ -174,12 +184,12 @@ export class TokensForm extends React.PureComponent<Props, State> {
this.setState({ newTokenType: value });
};

handleProjectChange = ({ value }: { value: string }) => {
this.setState({ selectedProjectkey: value });
handleProjectChange = ({ value, label }: { value: string; label: string }) => {
this.setState({ selectedProject: { key: value, name: label } });
};

renderForm() {
const { newTokenName, newTokenType, projects, selectedProjectkey } = this.state;
const { newTokenName, newTokenType, projects, selectedProject } = this.state;
const { displayTokenTypeInput, currentUser } = this.props;

const tokenTypeOptions = [
@@ -207,7 +217,7 @@ export class TokensForm extends React.PureComponent<Props, State> {
{displayTokenTypeInput && (
<>
<Select
className="input-medium spacer-right it__token-type"
className="input-large spacer-right it__token-type"
isSearchable={false}
onChange={this.handleNewTokenTypeChange}
options={tokenTypeOptions}
@@ -216,11 +226,11 @@ export class TokensForm extends React.PureComponent<Props, State> {
/>
{newTokenType === TokenType.Project && (
<Select
className="input-medium spacer-right it__project"
className="input-large spacer-right it__project"
onChange={this.handleProjectChange}
options={projects}
placeholder={translate('users.select_token_project')}
value={projects.find(project => project.value === selectedProjectkey)}
value={projects.find(project => project.value === selectedProject.key)}
/>
)}
</>
@@ -271,10 +281,12 @@ export class TokensForm extends React.PureComponent<Props, State> {
{this.renderForm()}
{newToken && <TokensFormNewToken token={newToken} />}

<table className="data zebra big-spacer-top">
<table className="data zebra big-spacer-top fixed">
<thead>
<tr>
<th>{translate('name')}</th>
<th>{translate('my_account.token_type')}</th>
<th>{translate('my_account.project_name')}</th>
<th>{translate('my_account.tokens_last_usage')}</th>
<th className="text-right">{translate('created')}</th>
<th />

+ 9
- 9
server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx Ver arquivo

@@ -22,12 +22,10 @@ import { FormattedMessage } from 'react-intl';
import { revokeToken } from '../../../api/user-tokens';
import { Button } from '../../../components/controls/buttons';
import ConfirmButton from '../../../components/controls/ConfirmButton';
import Tooltip from '../../../components/controls/Tooltip';
import DateFormatter from '../../../components/intl/DateFormatter';
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/token';

export type TokenDeleteConfirmation = 'inline' | 'modal';
@@ -44,8 +42,6 @@ interface State {
showConfirmation: boolean;
}

const MAX_TOKEN_NAME_FIELD = 20;

export default class TokensFormItem extends React.PureComponent<Props, State> {
mounted = false;
state: State = { loading: false, showConfirmation: false };
@@ -87,12 +83,16 @@ export default class TokensFormItem extends React.PureComponent<Props, State> {
const { loading, showConfirmation } = this.state;
return (
<tr>
<td>
<Tooltip overlay={token.name}>
<span>{limitComponentName(token.name, MAX_TOKEN_NAME_FIELD)}</span>
</Tooltip>
<td title={token.name} className="hide-overflow nowrap">
{token.name}
</td>
<td title={translate('users.tokens', token.type)} className="hide-overflow thin">
{translate('users.tokens', token.type, 'short')}
</td>
<td title={token.project?.name} className="hide-overflow">
{token.project?.name}
</td>
<td className="nowrap">
<td className="thin nowrap">
<DateFromNow date={token.lastConnectionDate} hourPrecision={true} />
</td>
<td className="thin nowrap text-right">

+ 1
- 1
server/sonar-web/src/main/js/apps/users/components/TokensFormModal.tsx Ver arquivo

@@ -33,7 +33,7 @@ interface Props {

export default function TokensFormModal(props: Props) {
return (
<Modal contentLabel={translate('users.tokens')} onRequestClose={props.onClose}>
<Modal size="medium" contentLabel={translate('users.tokens')} onRequestClose={props.onClose}>
<header className="modal-head">
<h2>
<FormattedMessage

+ 4
- 1
server/sonar-web/src/main/js/apps/users/components/__tests__/TokensForm-test.tsx Ver arquivo

@@ -20,6 +20,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { generateToken, getTokens } from '../../../../api/user-tokens';
import { mockUserToken } from '../../../../helpers/mocks/token';
import { mockCurrentUser } from '../../../../helpers/testMocks';
import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils';
import { TokenType } from '../../../../types/token';
@@ -75,7 +76,9 @@ it('should revoke tokens', async () => {

await waitAndUpdate(wrapper);
expect(wrapper.find('TokensFormItem')).toHaveLength(2);
wrapper.instance().handleRevokeToken({ createdAt: '2019-01-15T15:06:33+0100', name: 'foo' });
wrapper
.instance()
.handleRevokeToken(mockUserToken({ createdAt: '2019-01-15T15:06:33+0100', name: 'foo' }));
expect(updateTokensCount).toHaveBeenCalledWith('luke', 1);
expect(wrapper.find('TokensFormItem')).toHaveLength(1);
});

+ 3
- 3
server/sonar-web/src/main/js/apps/users/components/__tests__/TokensFormItem-test.tsx Ver arquivo

@@ -20,8 +20,8 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { revokeToken } from '../../../../api/user-tokens';
import { mockUserToken } from '../../../../helpers/mocks/token';
import { click, waitAndUpdate } from '../../../../helpers/testUtils';
import { UserToken } from '../../../../types/token';
import TokensFormItem from '../TokensFormItem';

jest.mock('../../../../components/intl/DateFormatter');
@@ -32,11 +32,11 @@ jest.mock('../../../../api/user-tokens', () => ({
revokeToken: jest.fn().mockResolvedValue(undefined)
}));

const userToken: UserToken = {
const userToken = mockUserToken({
name: 'foo',
createdAt: '2019-01-15T15:06:33+0100',
lastConnectionDate: '2019-01-18T15:06:33+0100'
};
});

beforeEach(() => {
(revokeToken as jest.Mock).mockClear();

+ 14
- 2
server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensForm-test.tsx.snap Ver arquivo

@@ -29,13 +29,19 @@ exports[`should render correctly 1`] = `
</SubmitButton>
</form>
<table
className="data zebra big-spacer-top"
className="data zebra big-spacer-top fixed"
>
<thead>
<tr>
<th>
name
</th>
<th>
my_account.token_type
</th>
<th>
my_account.project_name
</th>
<th>
my_account.tokens_last_usage
</th>
@@ -103,13 +109,19 @@ exports[`should render correctly 2`] = `
</SubmitButton>
</form>
<table
className="data zebra big-spacer-top"
className="data zebra big-spacer-top fixed"
>
<thead>
<tr>
<th>
name
</th>
<th>
my_account.token_type
</th>
<th>
my_account.project_name
</th>
<th>
my_account.tokens_last_usage
</th>

+ 30
- 18
server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensFormItem-test.tsx.snap Ver arquivo

@@ -2,17 +2,23 @@

exports[`should render correctly 1`] = `
<tr>
<td>
<Tooltip
overlay="foo"
>
<span>
foo
</span>
</Tooltip>
<td
className="hide-overflow nowrap"
title="foo"
>
foo
</td>
<td
className="nowrap"
className="hide-overflow thin"
title="users.tokens.USER_TOKEN"
>
users.tokens.USER_TOKEN.short
</td>
<td
className="hide-overflow"
/>
<td
className="thin nowrap"
>
<DateFromNow
date="2019-01-18T15:06:33+0100"
@@ -50,17 +56,23 @@ exports[`should render correctly 1`] = `

exports[`should render correctly 2`] = `
<tr>
<td>
<Tooltip
overlay="foo"
>
<span>
foo
</span>
</Tooltip>
<td
className="hide-overflow nowrap"
title="foo"
>
foo
</td>
<td
className="nowrap"
className="hide-overflow thin"
title="users.tokens.USER_TOKEN"
>
users.tokens.USER_TOKEN.short
</td>
<td
className="hide-overflow"
/>
<td
className="thin nowrap"
>
<DateFromNow
date="2019-01-18T15:06:33+0100"

+ 1
- 0
server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensFormModal-test.tsx.snap Ver arquivo

@@ -4,6 +4,7 @@ exports[`should render correctly 1`] = `
<Modal
contentLabel="users.tokens"
onRequestClose={[MockFunction]}
size="medium"
>
<header
className="modal-head"

+ 6
- 5
server/sonar-web/src/main/js/components/tutorials/__tests__/utils-test.ts Ver arquivo

@@ -22,6 +22,7 @@ import {
mockProjectBitbucketCloudBindingResponse,
mockProjectGithubBindingResponse
} from '../../../helpers/mocks/alm-settings';
import { mockUserToken } from '../../../helpers/mocks/token';
import { UserToken } from '../../../types/token';
import { buildBitbucketCloudLink, buildGithubLink, getUniqueTokenName } from '../utils';

@@ -35,15 +36,15 @@ describe('getUniqueTokenName', () => {
});

it('should generate a token with the given name', () => {
const userTokens = [{ name: initialTokenName, createdAt: '2019-06-14T09:45:52+0200' }];
expect(getUniqueTokenName(userTokens, 'Analyze "project"')).toBe('Analyze "project"');
expect(
getUniqueTokenName([mockUserToken({ name: initialTokenName })], 'Analyze "project"')
).toBe('Analyze "project"');
});

it('should generate a unique token when the name already exists', () => {
const userTokens = [
{ name: initialTokenName, createdAt: '2019-06-15T09:45:52+0200' },
{ name: `${initialTokenName} 1`, createdAt: '2019-06-15T09:45:53+0200' }
mockUserToken({ name: initialTokenName }),
mockUserToken({ name: `${initialTokenName} 1` })
];

expect(getUniqueTokenName(userTokens, initialTokenName)).toBe('Analyze "lightsaber" 2');

+ 30
- 0
server/sonar-web/src/main/js/helpers/mocks/token.ts Ver arquivo

@@ -0,0 +1,30 @@
/*
* 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.
*/

import { TokenType, UserToken } from '../../types/token';

export function mockUserToken(overrides: Partial<UserToken> = {}): UserToken {
return {
name: 'Token name',
createdAt: '2019-06-14T09:45:52+0200',
type: TokenType.User,
...overrides
};
}

+ 1
- 1
server/sonar-web/src/main/js/types/token.ts Ver arquivo

@@ -28,7 +28,7 @@ export interface UserToken {
name: string;
createdAt: string;
lastConnectionDate?: string;
type?: TokenType;
type: TokenType;
project?: { name: string; key: string };
}


+ 5
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Ver arquivo

@@ -1996,6 +1996,8 @@ my_account.no_project_notifications=You have not set project notifications yet.
my_account.profile=Profile
my_account.security=Security
my_account.tokens_description=If you want to enforce security by not providing credentials of a real {instance} user to run your code scan or to invoke web services, you can provide a User Token as a replacement of the user login. This will increase the security of your installation by not letting your analysis user's password going through your network.
my_account.token_type=Type
my_account.project_name=Project
my_account.tokens_last_usage=Last use
my_account.projects=Projects
my_account.projects.description=Those projects are the ones you are administering.
@@ -4078,8 +4080,11 @@ users.tokens.revoke_token=Revoke token
users.no_tokens=No tokens
users.generate=Generate
users.tokens.PROJECT_ANALYSIS_TOKEN=Project Analysis Token
users.tokens.PROJECT_ANALYSIS_TOKEN.short=Project
users.tokens.GLOBAL_ANALYSIS_TOKEN=Global Analysis Token
users.tokens.GLOBAL_ANALYSIS_TOKEN.short=Global
users.tokens.USER_TOKEN=User Token
users.tokens.USER_TOKEN.short=User
users.generate_tokens=Generate Tokens
users.enter_token_name=Enter Token Name
users.select_token_type=Select Token Type

Carregando…
Cancelar
Salvar