瀏覽代碼

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

tags/6.6-RC1
Stas Vilchik 6 年之前
父節點
當前提交
76d113a7b4

+ 1
- 1
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>

+ 103
- 24
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>
);

+ 16
- 0
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');
});

+ 5
- 5
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

+ 148
- 50
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"
>

+ 5
- 1
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.

Loading…
取消
儲存