aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/tutorials
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2017-10-18 14:35:54 +0200
committerStas Vilchik <stas.vilchik@sonarsource.com>2017-10-19 11:07:32 +0200
commite462c69f762dd0e775d7ca85e6c1e87e0551a08e (patch)
tree3cd5e38989816bddb945932191c5d81426f02c4e /server/sonar-web/src/main/js/apps/tutorials
parent1ff74014e9933a15fcad0f835c3206e746c54a50 (diff)
downloadsonarqube-e462c69f762dd0e775d7ca85e6c1e87e0551a08e.tar.gz
sonarqube-e462c69f762dd0e775d7ca85e6c1e87e0551a08e.zip
pre-validate exising token during onboarding
Diffstat (limited to 'server/sonar-web/src/main/js/apps/tutorials')
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js1
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js123
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js19
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap30
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap30
5 files changed, 155 insertions, 48 deletions
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js
index c5ec5148511..ad825f28a36 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js
+++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js
@@ -190,6 +190,7 @@ export default class Onboarding extends React.PureComponent {
)}
<TokenStep
+ currentUser={this.props.currentUser}
finished={this.state.token != null}
onContinue={this.handleTokenDone}
onOpen={this.handleTokenOpen}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js
index abcdb98b84e..1bbaa9df1b4 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js
+++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js
@@ -21,12 +21,14 @@
import React from 'react';
import classNames from 'classnames';
import Step from './Step';
+import { getTokens, generateToken, revokeToken } from '../../../api/user-tokens';
+import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon';
import CloseIcon from '../../../components/icons-components/CloseIcon';
-import { generateToken, revokeToken } from '../../../api/user-tokens';
import { translate } from '../../../helpers/l10n';
/*::
type Props = {|
+ currentUser: { login: string },
finished: boolean,
open: boolean,
onContinue: (token: string) => void,
@@ -37,7 +39,8 @@ type Props = {|
/*::
type State = {
- existingToken:string,
+ canUseExisting: boolean,
+ existingToken: string,
loading: boolean,
selection: string,
tokenName?: string,
@@ -49,6 +52,7 @@ export default class TokenStep extends React.PureComponent {
/*:: mounted: boolean; */
/*:: props: Props; */
state /*: State */ = {
+ canUseExisting: false,
existingToken: '',
loading: false,
selection: 'generate'
@@ -56,6 +60,14 @@ export default class TokenStep extends React.PureComponent {
componentDidMount() {
this.mounted = true;
+ getTokens(this.props.currentUser.login).then(
+ tokens => {
+ if (this.mounted) {
+ this.setState({ canUseExisting: tokens.length > 0 });
+ }
+ },
+ () => {}
+ );
}
componentWillUnmount() {
@@ -65,6 +77,15 @@ export default class TokenStep extends React.PureComponent {
getToken = () =>
this.state.selection === 'generate' ? this.state.token : this.state.existingToken;
+ canContinue = () => {
+ const { existingToken, selection, token } = this.state;
+ const validExistingToken = existingToken.match(/^[a-z0-9]+$/) != null;
+ return (
+ (selection === 'generate' && token != null) ||
+ (selection === 'use-existing' && existingToken && validExistingToken)
+ );
+ };
+
handleTokenNameChange = (event /*: { target: HTMLInputElement } */) => {
this.setState({ tokenName: event.target.value });
};
@@ -133,17 +154,21 @@ export default class TokenStep extends React.PureComponent {
renderGenerateOption = () => (
<div>
- <a
- className="js-new link-base-color link-no-underline"
- href="#"
- onClick={this.handleGenerateClick}>
- <i
- className={classNames('icon-radio', 'spacer-right', {
- 'is-checked': this.state.selection === 'generate'
- })}
- />
- {translate('onboading.token.generate_token')}
- </a>
+ {this.state.canUseExisting ? (
+ <a
+ className="js-new link-base-color link-no-underline"
+ href="#"
+ onClick={this.handleGenerateClick}>
+ <i
+ className={classNames('icon-radio', 'spacer-right', {
+ 'is-checked': this.state.selection === 'generate'
+ })}
+ />
+ {translate('onboading.token.generate_token')}
+ </a>
+ ) : (
+ translate('onboading.token.generate_token')
+ )}
{this.state.selection === 'generate' && (
<div className="big-spacer-top">
<form onSubmit={this.handleTokenGenerate}>
@@ -169,37 +194,48 @@ export default class TokenStep extends React.PureComponent {
</div>
);
- renderUseExistingOption = () => (
- <div className="big-spacer-top">
- <a
- className="js-new link-base-color link-no-underline"
- href="#"
- onClick={this.handleUseExistingClick}>
- <i
- className={classNames('icon-radio', 'spacer-right', {
- 'is-checked': this.state.selection === 'use-existing'
- })}
- />
- {translate('onboarding.token.use_existing_token')}
- </a>
- {this.state.selection === 'use-existing' && (
- <div className="big-spacer-top">
- <input
- autoFocus={true}
- className="input-large spacer-right text-middle"
- onChange={this.handleExisingTokenChange}
- placeholder={translate('onboarding.token.use_existing_token.placeholder')}
- required={true}
- type="text"
- value={this.state.existingToken}
+ renderUseExistingOption = () => {
+ const { existingToken } = this.state;
+ const validInput = !existingToken || existingToken.match(/^[a-z0-9]+$/) != null;
+
+ return (
+ <div className="big-spacer-top">
+ <a
+ className="js-new link-base-color link-no-underline"
+ href="#"
+ onClick={this.handleUseExistingClick}>
+ <i
+ className={classNames('icon-radio', 'spacer-right', {
+ 'is-checked': this.state.selection === 'use-existing'
+ })}
/>
- </div>
- )}
- </div>
- );
+ {translate('onboarding.token.use_existing_token')}
+ </a>
+ {this.state.selection === 'use-existing' && (
+ <div className="big-spacer-top">
+ <input
+ autoFocus={true}
+ className="input-large spacer-right text-middle"
+ onChange={this.handleExisingTokenChange}
+ placeholder={translate('onboarding.token.use_existing_token.placeholder')}
+ required={true}
+ type="text"
+ value={this.state.existingToken}
+ />
+ {!validInput && (
+ <span className="text-danger">
+ <AlertErrorIcon className="little-spacer-right text-text-top" />
+ {translate('onboarding.token.invalid_format')}
+ </span>
+ )}
+ </div>
+ )}
+ </div>
+ );
+ };
renderForm = () => {
- const { existingToken, loading, selection, token, tokenName } = this.state;
+ const { canUseExisting, loading, token, tokenName } = this.state;
return (
<div className="boxed-group-inner">
@@ -221,14 +257,13 @@ export default class TokenStep extends React.PureComponent {
) : (
<div>
{this.renderGenerateOption()}
- {this.renderUseExistingOption()}
+ {canUseExisting && this.renderUseExistingOption()}
</div>
)}
<div className="note big-spacer-top width-50">{translate('onboarding.token.text')}</div>
- {((selection === 'generate' && token != null) ||
- (selection === 'use-existing' && existingToken)) && (
+ {this.canContinue() && (
<div className="big-spacer-top">
<button className="js-continue" onClick={this.handleContinueClick}>
{translate('continue')}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js
index 2c2f07d5b75..200a9030052 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js
+++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js
@@ -24,13 +24,17 @@ import TokenStep from '../TokenStep';
import { change, click, doAsync, submit } from '../../../../helpers/testUtils';
jest.mock('../../../../api/user-tokens', () => ({
+ getTokens: () => Promise.resolve([{ name: 'foo' }]),
generateToken: () => Promise.resolve({ token: 'abcd1234' }),
revokeToken: () => Promise.resolve()
}));
-it('generates token', () => {
+const currentUser = { login: 'user' };
+
+it('generates token', async () => {
const wrapper = mount(
<TokenStep
+ currentUser={currentUser}
finished={false}
open={true}
onContinue={jest.fn()}
@@ -38,6 +42,7 @@ it('generates token', () => {
stepNumber={1}
/>
);
+ await new Promise(setImmediate);
expect(wrapper).toMatchSnapshot();
change(wrapper.find('input'), 'my token');
submit(wrapper.find('form'));
@@ -45,9 +50,10 @@ it('generates token', () => {
return doAsync(() => expect(wrapper).toMatchSnapshot());
});
-it('revokes token', () => {
+it('revokes token', async () => {
const wrapper = mount(
<TokenStep
+ currentUser={currentUser}
finished={false}
open={true}
onContinue={jest.fn()}
@@ -55,6 +61,7 @@ it('revokes token', () => {
stepNumber={1}
/>
);
+ await new Promise(setImmediate);
wrapper.setState({ token: 'abcd1234', tokenName: 'my token' });
expect(wrapper).toMatchSnapshot();
submit(wrapper.find('form'));
@@ -62,10 +69,11 @@ it('revokes token', () => {
return doAsync(() => expect(wrapper).toMatchSnapshot());
});
-it('continues', () => {
+it('continues', async () => {
const onContinue = jest.fn();
const wrapper = mount(
<TokenStep
+ currentUser={currentUser}
finished={false}
open={true}
onContinue={onContinue}
@@ -73,15 +81,17 @@ it('continues', () => {
stepNumber={1}
/>
);
+ await new Promise(setImmediate);
wrapper.setState({ token: 'abcd1234', tokenName: 'my token' });
click(wrapper.find('.js-continue'));
expect(onContinue).toBeCalledWith('abcd1234');
});
-it('uses existing token', () => {
+it('uses existing token', async () => {
const onContinue = jest.fn();
const wrapper = mount(
<TokenStep
+ currentUser={currentUser}
finished={false}
open={true}
onContinue={onContinue}
@@ -89,6 +99,7 @@ it('uses existing token', () => {
stepNumber={1}
/>
);
+ await new Promise(setImmediate);
wrapper.setState({ existingToken: 'abcd1234', selection: 'use-existing' });
click(wrapper.find('.js-continue'));
expect(onContinue).toBeCalledWith('abcd1234');
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap
index 3fed8ff9708..55409e86250 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap
@@ -43,6 +43,12 @@ exports[`guides for on-premise 1`] = `
</div>
</header>
<TokenStep
+ currentUser={
+ Object {
+ "isLoggedIn": true,
+ "login": "admin",
+ }
+ }
finished={false}
onContinue={[Function]}
onOpen={[Function]}
@@ -103,6 +109,12 @@ exports[`guides for on-premise 2`] = `
</div>
</header>
<TokenStep
+ currentUser={
+ Object {
+ "isLoggedIn": true,
+ "login": "admin",
+ }
+ }
finished={true}
onContinue={[Function]}
onOpen={[Function]}
@@ -177,6 +189,12 @@ exports[`guides for sonarcloud 1`] = `
stepNumber={1}
/>
<TokenStep
+ currentUser={
+ Object {
+ "isLoggedIn": true,
+ "login": "admin",
+ }
+ }
finished={false}
onContinue={[Function]}
onOpen={[Function]}
@@ -250,6 +268,12 @@ exports[`guides for sonarcloud 2`] = `
stepNumber={1}
/>
<TokenStep
+ currentUser={
+ Object {
+ "isLoggedIn": true,
+ "login": "admin",
+ }
+ }
finished={false}
onContinue={[Function]}
onOpen={[Function]}
@@ -324,6 +348,12 @@ exports[`guides for sonarcloud 3`] = `
stepNumber={1}
/>
<TokenStep
+ currentUser={
+ Object {
+ "isLoggedIn": true,
+ "login": "admin",
+ }
+ }
finished={true}
onContinue={[Function]}
onOpen={[Function]}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap
index 38b83ffa20a..5afe9b488f1 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap
@@ -2,6 +2,11 @@
exports[`generates token 1`] = `
<TokenStep
+ currentUser={
+ Object {
+ "login": "user",
+ }
+ }
finished={false}
onContinue={[Function]}
onOpen={[Function]}
@@ -99,6 +104,11 @@ exports[`generates token 1`] = `
exports[`generates token 2`] = `
<TokenStep
+ currentUser={
+ Object {
+ "login": "user",
+ }
+ }
finished={false}
onContinue={[Function]}
onOpen={[Function]}
@@ -193,6 +203,11 @@ exports[`generates token 2`] = `
exports[`generates token 3`] = `
<TokenStep
+ currentUser={
+ Object {
+ "login": "user",
+ }
+ }
finished={false}
onContinue={[Function]}
onOpen={[Function]}
@@ -285,6 +300,11 @@ exports[`generates token 3`] = `
exports[`revokes token 1`] = `
<TokenStep
+ currentUser={
+ Object {
+ "login": "user",
+ }
+ }
finished={false}
onContinue={[Function]}
onOpen={[Function]}
@@ -377,6 +397,11 @@ exports[`revokes token 1`] = `
exports[`revokes token 2`] = `
<TokenStep
+ currentUser={
+ Object {
+ "login": "user",
+ }
+ }
finished={false}
onContinue={[Function]}
onOpen={[Function]}
@@ -451,6 +476,11 @@ exports[`revokes token 2`] = `
exports[`revokes token 3`] = `
<TokenStep
+ currentUser={
+ Object {
+ "login": "user",
+ }
+ }
finished={false}
onContinue={[Function]}
onOpen={[Function]}