]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20086 - Migrating manually create page
authorKevin Silva <kevin.silva@sonarsource.com>
Wed, 9 Aug 2023 13:40:02 +0000 (15:40 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 14 Aug 2023 20:02:57 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx
server/sonar-web/src/main/js/apps/create/project/manual/ManualProjectCreate.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index cffe67b5283c459b6b9db1b1b39c25d60d9ca3c8..3f8770284d952f1f442eabe7eb91fb7b1166565b 100644 (file)
@@ -131,14 +131,14 @@ export function CreateProjectModeSelection(props: CreateProjectModeSelectionProp
             {translate('onboarding.create_project.select_method.no_alm_yet.admin')}
           </LightPrimary>
         )}
-        <div className="sw-grid sw-gap-x-12 sw-gap-y-6 sw-grid-cols-12 sw-mt-6">
+        <div className="sw-grid sw-gap-x-12 sw-gap-y-6 sw-grid-cols-12 sw-mt-4">
           {renderAlmOption(props, AlmKeys.Azure, CreateProjectModes.AzureDevOps)}
           {renderAlmOption(props, AlmKeys.BitbucketServer, CreateProjectModes.BitbucketServer)}
           {renderAlmOption(props, AlmKeys.BitbucketCloud, CreateProjectModes.BitbucketCloud)}
           {renderAlmOption(props, AlmKeys.GitHub, CreateProjectModes.GitHub)}
           {renderAlmOption(props, AlmKeys.GitLab, CreateProjectModes.GitLab)}
         </div>
-        <LightPrimary className="sw-mb-6 sw-mt-10">
+        <LightPrimary className="sw-mb-4 sw-mt-10">
           {translate('onboarding.create_project.select_method.manually')}
         </LightPrimary>
         <div className="sw-grid sw-gap-6 sw-grid-cols-12">
index 479b6df5c16572b420327acd7bf21674480d1326..1cfce61f2248e4a779e8f6dadadf74428b2f7276 100644 (file)
@@ -61,83 +61,72 @@ it('should validate form input', async () => {
   // All input valid
   await user.click(
     await screen.findByRole('textbox', {
-      name: 'onboarding.create_project.display_name field_required',
+      name: /onboarding.create_project.display_name/,
     })
   );
   await user.keyboard('test');
   expect(
-    screen.getByRole('textbox', { name: 'onboarding.create_project.project_key field_required' })
+    screen.getByRole('textbox', { name: /onboarding.create_project.project_key/ })
   ).toHaveValue('test');
   expect(ui.nextButton.get()).toBeEnabled();
 
   // Sanitize the key
   await user.click(
     await screen.findByRole('textbox', {
-      name: 'onboarding.create_project.display_name field_required',
+      name: /onboarding.create_project.display_name/,
     })
   );
   await user.keyboard('{Control>}a{/Control}This is not a key%^$');
   expect(
-    screen.getByRole('textbox', { name: 'onboarding.create_project.project_key field_required' })
+    screen.getByRole('textbox', { name: /onboarding.create_project.project_key/ })
   ).toHaveValue('This-is-not-a-key-');
 
   // Clear name
   await user.clear(
     screen.getByRole('textbox', {
-      name: 'onboarding.create_project.display_name field_required',
+      name: /onboarding.create_project.display_name/,
     })
   );
   expect(
-    screen.getByRole('textbox', { name: 'onboarding.create_project.project_key field_required' })
+    screen.getByRole('textbox', { name: /onboarding.create_project.project_key/ })
   ).toHaveValue('');
-  expect(
-    screen.getByText('onboarding.create_project.display_name.error.empty')
-  ).toBeInTheDocument();
+
   expect(ui.nextButton.get()).toBeDisabled();
 
   // Only key
   await user.click(
     await screen.findByRole('textbox', {
-      name: 'onboarding.create_project.project_key field_required',
+      name: /onboarding.create_project.project_key/,
     })
   );
   await user.keyboard('awsome-key');
   expect(
-    screen.getByRole('textbox', { name: 'onboarding.create_project.display_name field_required' })
+    screen.getByRole('textbox', { name: /onboarding.create_project.display_name/ })
   ).toHaveValue('');
-  expect(screen.getByText('valid_input')).toBeInTheDocument();
-  expect(
-    screen.getByText('onboarding.create_project.display_name.error.empty')
-  ).toBeInTheDocument();
+  expect(ui.nextButton.get()).toBeDisabled();
 
   // Invalid key
   await user.click(
     await screen.findByRole('textbox', {
-      name: 'onboarding.create_project.project_key field_required',
+      name: /onboarding.create_project.project_key/,
     })
   );
   await user.keyboard('{Control>}a{/Control}123');
-  expect(
-    await screen.findByText('onboarding.create_project.project_key.error.only_digits')
-  ).toBeInTheDocument();
+  expect(ui.nextButton.get()).toBeDisabled();
+
   await user.keyboard('{Control>}a{/Control}@');
-  expect(
-    await screen.findByText('onboarding.create_project.project_key.error.invalid_char')
-  ).toBeInTheDocument();
+  expect(ui.nextButton.get()).toBeDisabled();
+
   await user.keyboard('{Control>}a{/Control}exists');
-  expect(
-    await screen.findByText('onboarding.create_project.project_key.taken')
-  ).toBeInTheDocument();
+  expect(ui.nextButton.get()).toBeDisabled();
 
   // Invalid main branch name
   await user.clear(
     screen.getByRole('textbox', {
-      name: 'onboarding.create_project.main_branch_name field_required',
+      name: /onboarding.create_project.main_branch_name/,
     })
   );
-  expect(
-    await screen.findByText('onboarding.create_project.main_branch_name.error.empty')
-  ).toBeInTheDocument();
+  expect(ui.nextButton.get()).toBeDisabled();
 });
 
 it('should submit form input', async () => {
@@ -148,7 +137,7 @@ it('should submit form input', async () => {
   // All input valid
   await user.click(
     await screen.findByRole('textbox', {
-      name: 'onboarding.create_project.display_name field_required',
+      name: /onboarding.create_project.display_name/,
     })
   );
   await user.keyboard('test');
@@ -164,12 +153,12 @@ it('should handle component exists failure', async () => {
   // All input valid
   await user.click(
     await screen.findByRole('textbox', {
-      name: 'onboarding.create_project.display_name field_required',
+      name: /onboarding.create_project.display_name/,
     })
   );
   await user.keyboard('test');
   expect(
-    screen.getByRole('textbox', { name: 'onboarding.create_project.display_name field_required' })
+    screen.getByRole('textbox', { name: /onboarding.create_project.display_name/ })
   ).toHaveValue('test');
 });
 
index c6b0bfc89dfe897666119d6c6471bb8643f7c7b3..2d3b7373f4e588d0b79a8fcdc3b8de9bf37715dd 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import classNames from 'classnames';
+import {
+  ButtonPrimary,
+  FlagErrorIcon,
+  FlagMessage,
+  FlagSuccessIcon,
+  FormField,
+  InputField,
+  Link,
+  Note,
+  Title,
+} from 'design-system';
 import { debounce, isEmpty } from 'lodash';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { doesComponentExists } from '../../../../api/components';
 import { setupManualProjectCreation } from '../../../../api/project-management';
 import { getValue } from '../../../../api/settings';
-import DocLink from '../../../../components/common/DocLink';
-import ProjectKeyInput from '../../../../components/common/ProjectKeyInput';
-import ValidationInput from '../../../../components/controls/ValidationInput';
-import { SubmitButton } from '../../../../components/controls/buttons';
-import { Alert } from '../../../../components/ui/Alert';
-import MandatoryFieldsExplanation from '../../../../components/ui/MandatoryFieldsExplanation';
+import { useDocUrl } from '../../../../helpers/docs';
 import { translate } from '../../../../helpers/l10n';
 import { PROJECT_KEY_INVALID_CHARACTERS, validateProjectKey } from '../../../../helpers/projects';
 import { ProjectKeyValidationResult } from '../../../../types/component';
 import { GlobalSettingKeys } from '../../../../types/settings';
-import CreateProjectPageHeader from '../components/CreateProjectPageHeader';
 import { PROJECT_NAME_MAX_LEN } from '../constants';
 import { CreateProjectApiCallback } from '../types';
 
@@ -45,14 +50,14 @@ interface Props {
 
 interface State {
   projectName: string;
-  projectNameError?: string;
+  projectNameError?: boolean;
   projectNameTouched: boolean;
   projectKey: string;
-  projectKeyError?: string;
+  projectKeyError?: boolean;
   projectKeyTouched: boolean;
   validatingProjectKey: boolean;
   mainBranchName: string;
-  mainBranchNameError?: string;
+  mainBranchNameError?: boolean;
   mainBranchNameTouched: boolean;
 }
 
@@ -101,9 +106,7 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
       .then((alreadyExist) => {
         if (this.mounted && key === this.state.projectKey) {
           this.setState({
-            projectKeyError: alreadyExist
-              ? translate('onboarding.create_project.project_key.taken')
-              : undefined,
+            projectKeyError: alreadyExist ? true : undefined,
             validatingProjectKey: false,
           });
         }
@@ -182,21 +185,19 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
 
   validateKey = (projectKey: string) => {
     const result = validateProjectKey(projectKey);
-    return result === ProjectKeyValidationResult.Valid
-      ? undefined
-      : translate('onboarding.create_project.project_key.error', result);
+    return result === ProjectKeyValidationResult.Valid ? undefined : true;
   };
 
   validateName = (projectName: string) => {
     if (isEmpty(projectName)) {
-      return translate('onboarding.create_project.display_name.error.empty');
+      return true;
     }
     return undefined;
   };
 
   validateMainBranchName = (mainBranchName: string) => {
     if (isEmpty(mainBranchName)) {
-      return translate('onboarding.create_project.main_branch_name.error.empty');
+      return true;
     }
     return undefined;
   };
@@ -219,93 +220,133 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
     const touched = Boolean(projectKeyTouched || projectNameTouched);
     const projectNameIsInvalid = projectNameTouched && projectNameError !== undefined;
     const projectNameIsValid = projectNameTouched && projectNameError === undefined;
+    const projectKeyIsInvalid = touched && projectKeyError !== undefined;
+    const projectKeyIsValid = touched && !validatingProjectKey && projectKeyError === undefined;
     const mainBranchNameIsValid = mainBranchNameTouched && mainBranchNameError === undefined;
     const mainBranchNameIsInvalid = mainBranchNameTouched && mainBranchNameError !== undefined;
 
     return (
-      <>
-        <CreateProjectPageHeader title={translate('onboarding.create_project.setup_manually')} />
-
-        <form id="create-project-manual" onSubmit={this.handleFormSubmit}>
-          <MandatoryFieldsExplanation className="big-spacer-bottom" />
-
-          <ValidationInput
-            className="form-field"
-            description={translate('onboarding.create_project.display_name.description')}
-            error={projectNameError}
-            labelHtmlFor="project-name"
-            isInvalid={projectNameIsInvalid}
-            isValid={projectNameIsValid}
+      <div className="sw-max-w-[50%]">
+        <Title className="sw-mt-8">{translate('onboarding.create_project.setup_manually')}</Title>
+        {branchesEnabled && (
+          <FlagMessage className="sw-my-4" variant="info">
+            {translate('onboarding.create_project.pr_decoration.information')}
+          </FlagMessage>
+        )}
+        <form
+          id="create-project-manual"
+          className="sw-flex-col sw-body-sm"
+          onSubmit={this.handleFormSubmit}
+        >
+          <FormField
+            htmlFor="project-name"
             label={translate('onboarding.create_project.display_name')}
             required
           >
-            <input
-              className={classNames('input-super-large', {
-                'is-invalid': projectNameIsInvalid,
-                'is-valid': projectNameIsValid,
-              })}
-              id="project-name"
-              maxLength={PROJECT_NAME_MAX_LEN}
-              minLength={1}
-              onChange={(e) => this.handleProjectNameChange(e.currentTarget.value, true)}
-              type="text"
-              value={projectName}
-              autoFocus
-            />
-          </ValidationInput>
-          <ProjectKeyInput
-            error={projectKeyError}
-            label={translate('onboarding.create_project.project_key')}
-            onProjectKeyChange={(e) => this.handleProjectKeyChange(e.currentTarget.value, true)}
-            projectKey={projectKey}
-            touched={touched}
-            validating={validatingProjectKey}
-          />
+            <div>
+              <InputField
+                className={classNames({
+                  'js__is-invalid': projectNameIsInvalid,
+                })}
+                size="large"
+                id="project-name"
+                maxLength={PROJECT_NAME_MAX_LEN}
+                minLength={1}
+                onChange={(e) => this.handleProjectNameChange(e.currentTarget.value, true)}
+                type="text"
+                value={projectName}
+                autoFocus
+                isInvalid={projectNameIsInvalid}
+                isValid={projectNameIsValid}
+                required
+              />
+              {projectNameIsInvalid && <FlagErrorIcon className="sw-ml-2" />}
+              {projectNameIsValid && <FlagSuccessIcon className="sw-ml-2" />}
+            </div>
+            <Note className="sw-mt-2">
+              {translate('onboarding.create_project.display_name.description')}
+            </Note>
+          </FormField>
 
-          <ValidationInput
-            className="form-field"
-            description={
-              <FormattedMessage
-                id="onboarding.create_project.main_branch_name.description"
-                defaultMessage={translate('onboarding.create_project.main_branch_name.description')}
-                values={{
-                  learn_more: (
-                    <DocLink to="/analyzing-source-code/branches/branch-analysis">
-                      {translate('learn_more')}
-                    </DocLink>
-                  ),
-                }}
+          <FormField
+            htmlFor="project-key"
+            label={translate('onboarding.create_project.project_key')}
+            required
+          >
+            <div>
+              <InputField
+                className={classNames({
+                  'js__is-invalid': projectKeyIsInvalid,
+                })}
+                size="large"
+                id="project-key"
+                minLength={1}
+                onChange={(e) => this.handleProjectKeyChange(e.currentTarget.value, true)}
+                type="text"
+                value={projectKey}
+                isInvalid={projectKeyIsInvalid}
+                isValid={projectKeyIsValid}
+                required
               />
-            }
-            error={mainBranchNameError}
-            labelHtmlFor="main-branch-name"
-            isInvalid={mainBranchNameIsInvalid}
-            isValid={mainBranchNameIsValid}
+              {projectKeyIsInvalid && <FlagErrorIcon className="sw-ml-2" />}
+              {projectKeyIsValid && <FlagSuccessIcon className="sw-ml-2" />}
+            </div>
+            <Note className="sw-mt-2">
+              {translate('onboarding.create_project.project_key.description')}
+            </Note>
+          </FormField>
+
+          <FormField
+            htmlFor="main-branch-name"
             label={translate('onboarding.create_project.main_branch_name')}
             required
           >
-            <input
-              id="main-branch-name"
-              className={classNames('input-super-large', {
-                'is-invalid': mainBranchNameIsInvalid,
-                'is-valid': mainBranchNameIsValid,
-              })}
-              minLength={1}
-              onChange={(e) => this.handleBranchNameChange(e.currentTarget.value, true)}
-              type="text"
-              value={mainBranchName}
-            />
-          </ValidationInput>
+            <div>
+              <InputField
+                className={classNames({
+                  'js__is-invalid': mainBranchNameIsInvalid,
+                })}
+                size="large"
+                id="main-branch-name"
+                minLength={1}
+                onChange={(e) => this.handleBranchNameChange(e.currentTarget.value, true)}
+                type="text"
+                value={mainBranchName}
+                isInvalid={mainBranchNameIsInvalid}
+                isValid={mainBranchNameIsValid}
+                required
+              />
+              {mainBranchNameIsInvalid && <FlagErrorIcon className="sw-ml-2" />}
+              {mainBranchNameIsValid && <FlagSuccessIcon className="sw-ml-2" />}
+            </div>
+            <Note className="sw-mt-2">
+              <FormattedMessageWithDocLink />
+            </Note>
+          </FormField>
 
-          <SubmitButton disabled={!this.canSubmit(this.state)}>{translate('next')}</SubmitButton>
+          <ButtonPrimary type="submit" className="sw-mt-4" disabled={!this.canSubmit(this.state)}>
+            {translate('next')}
+          </ButtonPrimary>
         </form>
-
-        {branchesEnabled && (
-          <Alert variant="info" display="inline" className="big-spacer-top">
-            {translate('onboarding.create_project.pr_decoration.information')}
-          </Alert>
-        )}
-      </>
+      </div>
     );
   }
 }
+
+function FormattedMessageWithDocLink() {
+  const docUrl = useDocUrl();
+
+  return (
+    <FormattedMessage
+      id="onboarding.create_project.main_branch_name.description"
+      defaultMessage={translate('onboarding.create_project.main_branch_name.description')}
+      values={{
+        learn_more: (
+          <Link to={docUrl('/analyzing-source-code/branches/branch-analysis')}>
+            {translate('learn_more')}
+          </Link>
+        ),
+      }}
+    />
+  );
+}
index 2f8553b6a2cedcdb8f2eaacb417abb893e4b9278..c6b6ca90abfcb053e182669c54f146ddf4b6cc59 100644 (file)
@@ -3870,13 +3870,10 @@ onboarding.create_project.project_key.error.empty=You must provide at least one
 onboarding.create_project.project_key.error.too_long=The provided key is too long.
 onboarding.create_project.project_key.error.invalid_char=The provided key contains invalid characters.
 onboarding.create_project.project_key.error.only_digits=The provided key contains only digits.
-onboarding.create_project.project_key.taken=This project key is already taken.
 onboarding.create_project.display_name=Project display name
-onboarding.create_project.display_name.error.empty=The display name is required.
 onboarding.create_project.display_name.description=Up to 255 characters. Some scanners might override the value you provide.
 
 onboarding.create_project.main_branch_name=Main branch name
-onboarding.create_project.main_branch_name.error.empty=The main branch name is required.
 onboarding.create_project.main_branch_name.description=The name of your project’s default branch { learn_more }
 
 onboarding.create_project.pr_decoration.information=Manually created projects won’t benefit from the features associated with DevOps Platforms integration unless you configure them in the project settings.