Browse Source

SONAR-9707 Add possibility to reuse an existing token in the onboarding wizard

tags/6.6-RC1
Stas Vilchik 6 years ago
parent
commit
76d113a7b4

+ 1
- 1
server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js View File

@@ -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>

+ 103
- 24
server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js View File

@@ -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>
);

+ 16
- 0
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js View File

@@ -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');
});

+ 5
- 5
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap View File

@@ -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

+ 148
- 50
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap View File

@@ -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"
>

+ 5
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -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.

Loading…
Cancel
Save