aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js2
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js127
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js16
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap10
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap198
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties6
6 files changed, 278 insertions, 81 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 a9065a16b0a..d0e4b41b234 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
@@ -172,7 +172,7 @@ export default class Onboarding extends React.PureComponent {
</div>
<div className="page-description">
{translateWithParameters(
- 'onboarding.header.description_x',
+ 'onboarding.header.description',
organizationsEnabled ? 3 : 2
)}
</div>
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 80ed22c805d..abcdb98b84e 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
@@ -19,6 +19,7 @@
*/
// @flow
import React from 'react';
+import classNames from 'classnames';
import Step from './Step';
import CloseIcon from '../../../components/icons-components/CloseIcon';
import { generateToken, revokeToken } from '../../../api/user-tokens';
@@ -36,7 +37,9 @@ type Props = {|
/*::
type State = {
+ existingToken:string,
loading: boolean,
+ selection: string,
tokenName?: string,
token?: string
};
@@ -46,7 +49,9 @@ export default class TokenStep extends React.PureComponent {
/*:: mounted: boolean; */
/*:: props: Props; */
state /*: State */ = {
- loading: false
+ existingToken: '',
+ loading: false,
+ selection: 'generate'
};
componentDidMount() {
@@ -57,6 +62,9 @@ export default class TokenStep extends React.PureComponent {
this.mounted = false;
}
+ getToken = () =>
+ this.state.selection === 'generate' ? this.state.token : this.state.existingToken;
+
handleTokenNameChange = (event /*: { target: HTMLInputElement } */) => {
this.setState({ tokenName: event.target.value });
};
@@ -103,13 +111,95 @@ export default class TokenStep extends React.PureComponent {
handleContinueClick = (event /*: Event */) => {
event.preventDefault();
- if (this.state.token) {
- this.props.onContinue(this.state.token);
+ const token = this.getToken();
+ if (token) {
+ this.props.onContinue(token);
}
};
+ handleGenerateClick = (event /*: Event */) => {
+ event.preventDefault();
+ this.setState({ selection: 'generate' });
+ };
+
+ handleUseExistingClick = (event /*: Event */) => {
+ event.preventDefault();
+ this.setState({ selection: 'use-existing' });
+ };
+
+ handleExisingTokenChange = (event /*: { currentTarget: HTMLInputElement } */) => {
+ this.setState({ existingToken: event.currentTarget.value });
+ };
+
+ 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.selection === 'generate' && (
+ <div className="big-spacer-top">
+ <form onSubmit={this.handleTokenGenerate}>
+ <input
+ autoFocus={true}
+ className="input-large spacer-right text-middle"
+ onChange={this.handleTokenNameChange}
+ placeholder={translate('onboading.token.generate_token.placeholder')}
+ required={true}
+ type="text"
+ value={this.state.tokenName || ''}
+ />
+ {this.state.loading ? (
+ <i className="spinner text-middle" />
+ ) : (
+ <button className="text-middle" disabled={!this.state.tokenName}>
+ {translate('onboarding.token.generate')}
+ </button>
+ )}
+ </form>
+ </div>
+ )}
+ </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}
+ />
+ </div>
+ )}
+ </div>
+ );
+
renderForm = () => {
- const { loading, token, tokenName } = this.state;
+ const { existingToken, loading, selection, token, tokenName } = this.state;
return (
<div className="boxed-group-inner">
@@ -129,27 +219,16 @@ export default class TokenStep extends React.PureComponent {
)}
</form>
) : (
- <form onSubmit={this.handleTokenGenerate}>
- <input
- autoFocus={true}
- className="input-large spacer-right text-middle"
- onChange={this.handleTokenNameChange}
- placeholder={translate('onboarding.token.placeholder')}
- required={true}
- type="text"
- value={tokenName || ''}
- />
- {loading ? (
- <i className="spinner text-middle" />
- ) : (
- <button className="text-middle">{translate('onboarding.token.generate')}</button>
- )}
- </form>
+ <div>
+ {this.renderGenerateOption()}
+ {this.renderUseExistingOption()}
+ </div>
)}
<div className="note big-spacer-top width-50">{translate('onboarding.token.text')}</div>
- {token != null && (
+ {((selection === 'generate' && token != null) ||
+ (selection === 'use-existing' && existingToken)) && (
<div className="big-spacer-top">
<button className="js-continue" onClick={this.handleContinueClick}>
{translate('continue')}
@@ -161,7 +240,8 @@ export default class TokenStep extends React.PureComponent {
};
renderResult = () => {
- const { token, tokenName } = this.state;
+ const { selection, tokenName } = this.state;
+ const token = this.getToken();
if (!token) {
return null;
@@ -170,8 +250,7 @@ export default class TokenStep extends React.PureComponent {
return (
<div className="boxed-group-actions">
<i className="icon-check spacer-right" />
- {tokenName}
- {': '}
+ {selection === 'generate' && tokenName && `${tokenName}: `}
<strong>{token}</strong>
</div>
);
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 8a4608bb7ab..2c2f07d5b75 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
@@ -77,3 +77,19 @@ it('continues', () => {
click(wrapper.find('.js-continue'));
expect(onContinue).toBeCalledWith('abcd1234');
});
+
+it('uses existing token', () => {
+ const onContinue = jest.fn();
+ const wrapper = mount(
+ <TokenStep
+ finished={false}
+ open={true}
+ onContinue={onContinue}
+ onOpen={jest.fn()}
+ stepNumber={1}
+ />
+ );
+ 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 f8234801320..3fed8ff9708 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
@@ -39,7 +39,7 @@ exports[`guides for on-premise 1`] = `
<div
className="page-description"
>
- onboarding.header.description_x.2
+ onboarding.header.description.2
</div>
</header>
<TokenStep
@@ -99,7 +99,7 @@ exports[`guides for on-premise 2`] = `
<div
className="page-description"
>
- onboarding.header.description_x.2
+ onboarding.header.description.2
</div>
</header>
<TokenStep
@@ -160,7 +160,7 @@ exports[`guides for sonarcloud 1`] = `
<div
className="page-description"
>
- onboarding.header.description_x.3
+ onboarding.header.description.3
</div>
</header>
<OrganizationStep
@@ -233,7 +233,7 @@ exports[`guides for sonarcloud 2`] = `
<div
className="page-description"
>
- onboarding.header.description_x.3
+ onboarding.header.description.3
</div>
</header>
<OrganizationStep
@@ -307,7 +307,7 @@ exports[`guides for sonarcloud 3`] = `
<div
className="page-description"
>
- onboarding.header.description_x.3
+ onboarding.header.description.3
</div>
</header>
<OrganizationStep
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 65138e0b498..38b83ffa20a 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
@@ -35,24 +35,57 @@ exports[`generates token 1`] = `
<div
className="boxed-group-inner"
>
- <form
- onSubmit={[Function]}
- >
- <input
- autoFocus={true}
- className="input-large spacer-right text-middle"
- onChange={[Function]}
- placeholder="onboarding.token.placeholder"
- required={true}
- type="text"
- value=""
- />
- <button
- className="text-middle"
+ <div>
+ <div>
+ <a
+ className="js-new link-base-color link-no-underline"
+ href="#"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio spacer-right is-checked"
+ />
+ onboading.token.generate_token
+ </a>
+ <div
+ className="big-spacer-top"
+ >
+ <form
+ onSubmit={[Function]}
+ >
+ <input
+ autoFocus={true}
+ className="input-large spacer-right text-middle"
+ onChange={[Function]}
+ placeholder="onboading.token.generate_token.placeholder"
+ required={true}
+ type="text"
+ value=""
+ />
+ <button
+ className="text-middle"
+ disabled={true}
+ >
+ onboarding.token.generate
+ </button>
+ </form>
+ </div>
+ </div>
+ <div
+ className="big-spacer-top"
>
- onboarding.token.generate
- </button>
- </form>
+ <a
+ className="js-new link-base-color link-no-underline"
+ href="#"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio spacer-right"
+ />
+ onboarding.token.use_existing_token
+ </a>
+ </div>
+ </div>
<div
className="note big-spacer-top width-50"
>
@@ -99,22 +132,54 @@ exports[`generates token 2`] = `
<div
className="boxed-group-inner"
>
- <form
- onSubmit={[Function]}
- >
- <input
- autoFocus={true}
- className="input-large spacer-right text-middle"
- onChange={[Function]}
- placeholder="onboarding.token.placeholder"
- required={true}
- type="text"
- value="my token"
- />
- <i
- className="spinner text-middle"
- />
- </form>
+ <div>
+ <div>
+ <a
+ className="js-new link-base-color link-no-underline"
+ href="#"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio spacer-right is-checked"
+ />
+ onboading.token.generate_token
+ </a>
+ <div
+ className="big-spacer-top"
+ >
+ <form
+ onSubmit={[Function]}
+ >
+ <input
+ autoFocus={true}
+ className="input-large spacer-right text-middle"
+ onChange={[Function]}
+ placeholder="onboading.token.generate_token.placeholder"
+ required={true}
+ type="text"
+ value="my token"
+ />
+ <i
+ className="spinner text-middle"
+ />
+ </form>
+ </div>
+ </div>
+ <div
+ className="big-spacer-top"
+ >
+ <a
+ className="js-new link-base-color link-no-underline"
+ href="#"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio spacer-right"
+ />
+ onboarding.token.use_existing_token
+ </a>
+ </div>
+ </div>
<div
className="note big-spacer-top width-50"
>
@@ -419,24 +484,57 @@ exports[`revokes token 3`] = `
<div
className="boxed-group-inner"
>
- <form
- onSubmit={[Function]}
- >
- <input
- autoFocus={true}
- className="input-large spacer-right text-middle"
- onChange={[Function]}
- placeholder="onboarding.token.placeholder"
- required={true}
- type="text"
- value=""
- />
- <button
- className="text-middle"
+ <div>
+ <div>
+ <a
+ className="js-new link-base-color link-no-underline"
+ href="#"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio spacer-right is-checked"
+ />
+ onboading.token.generate_token
+ </a>
+ <div
+ className="big-spacer-top"
+ >
+ <form
+ onSubmit={[Function]}
+ >
+ <input
+ autoFocus={true}
+ className="input-large spacer-right text-middle"
+ onChange={[Function]}
+ placeholder="onboading.token.generate_token.placeholder"
+ required={true}
+ type="text"
+ value=""
+ />
+ <button
+ className="text-middle"
+ disabled={true}
+ >
+ onboarding.token.generate
+ </button>
+ </form>
+ </div>
+ </div>
+ <div
+ className="big-spacer-top"
>
- onboarding.token.generate
- </button>
- </form>
+ <a
+ className="js-new link-base-color link-no-underline"
+ href="#"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio spacer-right"
+ />
+ onboarding.token.use_existing_token
+ </a>
+ </div>
+ </div>
<div
className="note big-spacer-top width-50"
>
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index acd1bd356c3..2d914ebe6cf 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -2470,10 +2470,14 @@ onboarding.header=Welcome to SonarQube!
onboarding.header.sonarcloud=Welcome to SonarCloud!
onboarding.header.description=Want to quickly analyze a first project? Follow these {0} easy steps.
-onboarding.token.header=Generate a token
+onboarding.token.header=Provide a token
onboarding.token.text=The token is used to identify you when an analysis is performed. If it has been compromised, you can revoke it at any point of time in your user account.
onboarding.token.generate=Generate
onboarding.token.placeholder=Enter a name for your token
+onboading.token.generate_token=Generate a token
+onboading.token.generate_token.placeholder=Enter a name for your token
+onboarding.token.use_existing_token=Use existing token
+onboarding.token.use_existing_token.placeholder=Enter your existing token
onboarding.organization.header=Choose an organization for your project
onboarding.organization.text=Organizations are where your projects belong. You can add your team members to your organization later to allow them to contribute to your projects.