]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9707 Add possibility to reuse an existing token in the onboarding wizard
authorStas Vilchik <stas.vilchik@sonarsource.com>
Wed, 4 Oct 2017 15:34:27 +0000 (17:34 +0200)
committerStas Vilchik <stas.vilchik@sonarsource.com>
Thu, 5 Oct 2017 10:12:43 +0000 (12:12 +0200)
server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index a9065a16b0ae6c8652a72dee6464ff64c7c4cd3e..d0e4b41b23405ae23707ba711327c7da57984c2e 100644 (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>
index 80ed22c805d59dbfbc3557c34c3140af2f789e19..abcdb98b84ed5d798434b80bd556b97f2bdab961 100644 (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>
     );
index 8a4608bb7abbfa1be6443dcfdbf24af59abcae5f..2c2f07d5b75f2dac565b2df62491890d4f8a9fc4 100644 (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');
+});
index f8234801320c567e529c7a57328bccd0657abce5..3fed8ff970892abebf2df85ff3abfa38ee6673c8 100644 (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
index 65138e0b498f2977deb29f47dab999298d923eb9..38b83ffa20a21b01beea55dad15424cef25838b6 100644 (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"
         >
index acd1bd356c399badec6da78c1cfd3e5a707b5dc4..2d914ebe6cf510f155de281a620c528de327c07e 100644 (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.