]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20402 Align authentication settings card verticaly
authorMathieu Suen <mathieu.suen@sonarsource.com>
Mon, 11 Sep 2023 10:05:25 +0000 (12:05 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 12 Sep 2023 20:02:41 +0000 (20:02 +0000)
server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts
server/sonar-web/src/main/js/apps/settings/components/authentication/AuthenticationFormField.tsx
server/sonar-web/src/main/js/apps/settings/components/authentication/GithubAuthenticationTab.tsx
server/sonar-web/src/main/js/apps/settings/components/authentication/SamlAuthenticationTab.tsx
server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx
server/sonar-web/src/main/js/apps/settings/components/authentication/hook/useGithubConfiguration.ts
server/sonar-web/src/main/js/apps/settings/styles.css
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 2505f1661a08573d1e53dbbd1930bb2fb0c7f9f3..1352b8dcc971c6d573c7643fa5d5f54e927e61ef 100644 (file)
@@ -20,6 +20,7 @@
 import { cloneDeep, isArray, isObject, isString } from 'lodash';
 import { HousekeepingPolicy } from '../../apps/audit-logs/utils';
 import { mockDefinition, mockSettingFieldDefinition } from '../../helpers/mocks/settings';
+import { isDefined } from '../../helpers/types';
 import { BranchParameters } from '../../types/branch-like';
 import {
   ExtendedSettingDefinition,
@@ -101,6 +102,15 @@ export const DEFAULT_DEFINITIONS_MOCK = [
       mockSettingFieldDefinition({ key: 'value', name: 'Value' }),
     ],
   }),
+  mockDefinition({
+    category: 'authentication',
+    defaultValue: 'true',
+    key: 'sonar.auth.github.allowUsersToSignUp',
+    subCategory: 'github',
+    name: 'Compilation Constants',
+    description: 'Lets do it',
+    type: SettingType.BOOLEAN,
+  }),
 ];
 
 export default class SettingsServiceMock {
@@ -140,7 +150,17 @@ export default class SettingsServiceMock {
   };
 
   handleGetValues = (data: { keys: string[]; component?: string } & BranchParameters) => {
-    const settings = this.#settingValues.filter((s) => data.keys.includes(s.key));
+    const settings = data.keys
+      .map((k) => {
+        const def = this.#definitions.find((d) => d.key === k);
+        const v = this.#settingValues.find((s) => s.key === k);
+        if (v === undefined && def?.type === SettingType.BOOLEAN) {
+          return { key: k, value: def.defaultValue, inherited: true };
+        }
+        return v;
+      })
+      .filter(isDefined);
+
     return this.reply(settings);
   };
 
@@ -185,7 +205,7 @@ export default class SettingsServiceMock {
       setting.fieldValues = [];
     } else if (definition.multiValues === true) {
       setting.values = definition.defaultValue?.split(',') ?? [];
-    } else {
+    } else if (setting) {
       setting.value = definition.defaultValue ?? '';
     }
 
index f4197d89ebc0ee4edecf18513db23e0fa74a39e1..1467e7a87015a8a56df6e24cbe6eaff7d5513415 100644 (file)
@@ -23,7 +23,7 @@ import ValidationInput, {
 } from '../../../../components/controls/ValidationInput';
 import MandatoryFieldMarker from '../../../../components/ui/MandatoryFieldMarker';
 import { ExtendedSettingDefinition, SettingType } from '../../../../types/settings';
-import { isSecuredDefinition } from '../../utils';
+import { getPropertyDescription, getPropertyName, isSecuredDefinition } from '../../utils';
 import AuthenticationMultiValueField from './AuthenticationMultiValuesField';
 import AuthenticationSecuredField from './AuthenticationSecuredField';
 import AuthenticationToggleField from './AuthenticationToggleField';
@@ -40,16 +40,17 @@ interface SamlToggleFieldProps {
 export default function AuthenticationFormField(props: SamlToggleFieldProps) {
   const { mandatory = false, definition, settingValue, isNotSet, error } = props;
 
+  const name = getPropertyName(definition);
+  const description = getPropertyDescription(definition);
+
   return (
     <div className="settings-definition">
       <div className="settings-definition-left">
         <label className="h3" htmlFor={definition.key}>
-          {definition.name}
+          {name}
         </label>
         {mandatory && <MandatoryFieldMarker />}
-        {definition.description && (
-          <div className="markdown small spacer-top">{definition.description}</div>
-        )}
+        {definition.description && <div className="markdown small spacer-top">{description}</div>}
       </div>
       <div className="settings-definition-right big-padded-top display-flex-column">
         {definition.multiValues && (
index 9421083b828cce0356a2bb8c267b4a96565b7fa6..19e7d43ceb65c797030db2f974b3d08608980add 100644 (file)
@@ -48,11 +48,7 @@ interface GithubAuthenticationProps {
   currentTab: AuthenticationTabs;
 }
 
-const GITHUB_EXCLUDED_FIELD = [
-  'sonar.auth.github.enabled',
-  'sonar.auth.github.groupsSync',
-  'sonar.auth.github.allowUsersToSignUp',
-];
+const GITHUB_EXCLUDED_FIELD = ['sonar.auth.github.enabled', 'sonar.auth.github.allowUsersToSignUp'];
 
 export default function GithubAuthenticationTab(props: GithubAuthenticationProps) {
   const { definitions, currentTab } = props;
@@ -187,8 +183,58 @@ export default function GithubAuthenticationTab(props: GithubAuthenticationProps
                 </label>
 
                 {enabled ? (
-                  <div className="display-flex-row spacer-top">
+                  <div className="display-flex-column spacer-top">
                     <RadioCard
+                      label={translate('settings.authentication.form.provisioning_at_login')}
+                      title={translate('settings.authentication.form.provisioning_at_login')}
+                      selected={!(newGithubProvisioningStatus ?? githubProvisioningStatus)}
+                      onClick={() => setNewGithubProvisioningStatus(false)}
+                    >
+                      <p className="spacer-bottom">
+                        <FormattedMessage id="settings.authentication.github.form.provisioning_at_login.description" />
+                      </p>
+                      <p className="spacer-bottom">
+                        <FormattedMessage
+                          id="settings.authentication.github.form.description.doc"
+                          tagName="p"
+                          values={{
+                            documentation: (
+                              <DocLink
+                                to={`/instance-administration/authentication/${
+                                  DOCUMENTATION_LINK_SUFFIXES[AlmKeys.GitHub]
+                                }/`}
+                              >
+                                {translate('documentation')}
+                              </DocLink>
+                            ),
+                          }}
+                        />
+                      </p>
+
+                      {!(newGithubProvisioningStatus ?? githubProvisioningStatus) && (
+                        <>
+                          <hr />
+                          {Object.values(values).map((val) => {
+                            if (!GITHUB_JIT_FIELDS.includes(val.key)) {
+                              return null;
+                            }
+                            return (
+                              <div key={val.key}>
+                                <AuthenticationFormField
+                                  settingValue={values[val.key]?.newValue ?? values[val.key]?.value}
+                                  definition={val.definition}
+                                  mandatory={val.mandatory}
+                                  onFieldChange={setNewValue}
+                                  isNotSet={val.isNotSet}
+                                />
+                              </div>
+                            );
+                          })}
+                        </>
+                      )}
+                    </RadioCard>
+                    <RadioCard
+                      className="spacer-top"
                       label={translate(
                         'settings.authentication.github.form.provisioning_with_github'
                       )}
@@ -202,7 +248,7 @@ export default function GithubAuthenticationTab(props: GithubAuthenticationProps
                       {hasGithubProvisioning ? (
                         <>
                           {hasDifferentProvider && (
-                            <p className="spacer-bottom text-bold">
+                            <p className="spacer-bottom text-bold ">
                               {translate('settings.authentication.form.other_provisioning_enabled')}
                             </p>
                           )}
@@ -213,10 +259,7 @@ export default function GithubAuthenticationTab(props: GithubAuthenticationProps
                           </p>
                           <p className="spacer-bottom">
                             <FormattedMessage
-                              id="settings.authentication.github.form.provisioning_with_github.description.doc"
-                              defaultMessage={translate(
-                                'settings.authentication.github.form.provisioning_with_github.description.doc'
-                              )}
+                              id="settings.authentication.github.form.description.doc"
                               values={{
                                 documentation: (
                                   <DocLink
@@ -230,8 +273,10 @@ export default function GithubAuthenticationTab(props: GithubAuthenticationProps
                               }}
                             />
                           </p>
+
                           {githubProvisioningStatus && <GitHubSynchronisationWarning />}
-                          <div className="sw-flex sw-flex-1 sw-items-end">
+
+                          <div className="sw-flex sw-flex-1">
                             <Button
                               className="spacer-top width-30"
                               onClick={synchronizeNow}
@@ -260,29 +305,6 @@ export default function GithubAuthenticationTab(props: GithubAuthenticationProps
                         </p>
                       )}
                     </RadioCard>
-                    <RadioCard
-                      label={translate('settings.authentication.form.provisioning_at_login')}
-                      title={translate('settings.authentication.form.provisioning_at_login')}
-                      selected={!(newGithubProvisioningStatus ?? githubProvisioningStatus)}
-                      onClick={() => setNewGithubProvisioningStatus(false)}
-                    >
-                      {Object.values(values).map((val) => {
-                        if (!GITHUB_JIT_FIELDS.includes(val.key)) {
-                          return null;
-                        }
-                        return (
-                          <div key={val.key}>
-                            <AuthenticationFormField
-                              settingValue={values[val.key]?.newValue ?? values[val.key]?.value}
-                              definition={val.definition}
-                              mandatory={val.mandatory}
-                              onFieldChange={setNewValue}
-                              isNotSet={val.isNotSet}
-                            />
-                          </div>
-                        );
-                      })}
-                    </RadioCard>
                   </div>
                 ) : (
                   <Alert className="big-spacer-top" variant="info">
index 0deaa73e7aa7fad90f7a6647ddab8425de61905a..04263974303296399e5d3d69a4d005a1ddcf6792 100644 (file)
@@ -182,8 +182,19 @@ export default function SamlAuthenticationTab(props: SamlAuthenticationProps) {
                   {translate('settings.authentication.form.provisioning')}
                 </label>
                 {samlEnabled ? (
-                  <div className="display-flex-row spacer-top">
+                  <div className="display-flex-column spacer-top">
                     <RadioCard
+                      label={translate('settings.authentication.saml.form.provisioning_at_login')}
+                      title={translate('settings.authentication.saml.form.provisioning_at_login')}
+                      selected={!(newScimStatus ?? scimStatus)}
+                      onClick={() => setNewScimStatus(false)}
+                    >
+                      <p>
+                        {translate('settings.authentication.saml.form.provisioning_at_login.sub')}
+                      </p>
+                    </RadioCard>
+                    <RadioCard
+                      className="spacer-top"
                       label={translate('settings.authentication.saml.form.provisioning_with_scim')}
                       title={translate('settings.authentication.saml.form.provisioning_with_scim')}
                       selected={newScimStatus ?? scimStatus}
@@ -194,9 +205,6 @@ export default function SamlAuthenticationTab(props: SamlAuthenticationProps) {
                         <p>
                           <FormattedMessage
                             id="settings.authentication.saml.form.provisioning.disabled"
-                            defaultMessage={translate(
-                              'settings.authentication.saml.form.provisioning.disabled'
-                            )}
                             values={{
                               documentation: (
                                 <DocLink to="/instance-administration/authentication/saml/scim/overview">
@@ -213,12 +221,12 @@ export default function SamlAuthenticationTab(props: SamlAuthenticationProps) {
                               {translate('settings.authentication.form.other_provisioning_enabled')}
                             </p>
                           )}
-                          <p className="spacer-bottom">
+                          <p className="spacer-bottom ">
                             {translate(
                               'settings.authentication.saml.form.provisioning_with_scim.sub'
                             )}
                           </p>
-                          <p className="spacer-bottom">
+                          <p className="spacer-bottom ">
                             {translate(
                               'settings.authentication.saml.form.provisioning_with_scim.description'
                             )}
@@ -241,16 +249,6 @@ export default function SamlAuthenticationTab(props: SamlAuthenticationProps) {
                         </>
                       )}
                     </RadioCard>
-                    <RadioCard
-                      label={translate('settings.authentication.saml.form.provisioning_at_login')}
-                      title={translate('settings.authentication.saml.form.provisioning_at_login')}
-                      selected={!(newScimStatus ?? scimStatus)}
-                      onClick={() => setNewScimStatus(false)}
-                    >
-                      <p>
-                        {translate('settings.authentication.saml.form.provisioning_at_login.sub')}
-                      </p>
-                    </RadioCard>
                   </div>
                 ) : (
                   <Alert className="big-spacer-top" variant="info">
index b0e2cea255a5804a2d9cb0c556ee21621e903d25..eb65fdf02dc0af9c923046c78448c61ed2b30a4c 100644 (file)
@@ -83,12 +83,14 @@ const ui = {
   saml: {
     noSamlConfiguration: byText('settings.authentication.saml.form.not_configured'),
     createConfigButton: byRole('button', { name: 'settings.authentication.form.create' }),
-    providerName: byRole('textbox', { name: 'Provider Name' }),
-    providerId: byRole('textbox', { name: 'Provider ID' }),
-    providerCertificate: byRole('textbox', { name: 'Identity provider certificate' }),
-    loginUrl: byRole('textbox', { name: 'SAML login url' }),
-    userLoginAttribute: byRole('textbox', { name: 'SAML user login attribute' }),
-    userNameAttribute: byRole('textbox', { name: 'SAML user name attribute' }),
+    providerName: byRole('textbox', { name: 'property.sonar.auth.saml.providerName.name' }),
+    providerId: byRole('textbox', { name: 'property.sonar.auth.saml.providerId.name' }),
+    providerCertificate: byRole('textbox', {
+      name: 'property.sonar.auth.saml.certificate.secured.name',
+    }),
+    loginUrl: byRole('textbox', { name: 'property.sonar.auth.saml.loginUrl.name' }),
+    userLoginAttribute: byRole('textbox', { name: 'property.sonar.auth.saml.user.login.name' }),
+    userNameAttribute: byRole('textbox', { name: 'property.sonar.auth.saml.user.name.name' }),
     saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
     confirmProvisioningButton: byRole('button', { name: 'yes' }),
     saveScim: byRole('button', { name: 'save' }),
@@ -129,17 +131,16 @@ const ui = {
     tab: byRole('tab', { name: 'github GitHub' }),
     noGithubConfiguration: byText('settings.authentication.github.form.not_configured'),
     createConfigButton: byRole('button', { name: 'settings.authentication.form.create' }),
-    clientId: byRole('textbox', { name: 'Client ID' }),
-    clientSecret: byRole('textbox', { name: 'Client Secret' }),
-    githubAppId: byRole('textbox', { name: 'GitHub App ID' }), // not working
-    privateKey: byRole('textarea', { name: 'Private Key' }), // not working
-    githubApiUrl: byRole('textbox', { name: 'The API url for a GitHub instance.' }),
-    githubWebUrl: byRole('textbox', { name: 'The WEB url for a GitHub instance.' }),
+    clientId: byRole('textbox', { name: 'property.sonar.auth.github.clientId.secured.name' }),
+    clientSecret: byRole('textbox', {
+      name: 'property.sonar.auth.github.clientSecret.secured.name',
+    }),
+    githubApiUrl: byRole('textbox', { name: 'property.sonar.auth.github.apiUrl.name' }),
+    githubWebUrl: byRole('textbox', { name: 'property.sonar.auth.github.webUrl.name' }),
     allowUserToSignUp: byRole('switch', {
       name: 'sonar.auth.github.allowUsersToSignUp',
     }),
-    syncGroupsAsTeams: byRole('switch', { name: 'sonar.auth.github.groupsSync' }),
-    organizations: byRole('textbox', { name: 'Organizations' }),
+    organizations: byRole('textbox', { name: 'property.sonar.auth.github.organizations.name' }),
     saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
     confirmProvisioningButton: byRole('button', { name: 'yes' }),
     saveGithubProvisioning: byRole('button', { name: 'save' }),
@@ -436,7 +437,6 @@ describe('Github tab', () => {
 
     expect(github.saveGithubProvisioning.get()).toBeDisabled();
     await user.click(github.allowUserToSignUp.get());
-    await user.click(github.syncGroupsAsTeams.get());
 
     expect(github.saveGithubProvisioning.get()).toBeEnabled();
     await user.click(github.saveGithubProvisioning.get());
index e8ca3a7fb62861680dfd0e8753baf1edb6fa67a6..01c24408e4b8c94390c00483a1ddb8c28cb5558e 100644 (file)
@@ -33,14 +33,12 @@ export const GITHUB_ENABLED_FIELD = 'sonar.auth.github.enabled';
 export const GITHUB_APP_ID_FIELD = 'sonar.auth.github.appId';
 export const GITHUB_API_URL_FIELD = 'sonar.auth.github.apiUrl';
 export const GITHUB_CLIENT_ID_FIELD = 'sonar.auth.github.clientId.secured';
-export const GITHUB_JIT_FIELDS = [
-  'sonar.auth.github.allowUsersToSignUp',
-  'sonar.auth.github.groupsSync',
-];
+export const GITHUB_JIT_FIELDS = ['sonar.auth.github.allowUsersToSignUp'];
 export const OPTIONAL_FIELDS = [
   GITHUB_ENABLED_FIELD,
   ...GITHUB_JIT_FIELDS,
   'sonar.auth.github.organizations',
+  'sonar.auth.github.groupsSync',
 ];
 
 export interface SamlSettingValue {
index a886b08c3feddf6705645fba95b1b4a4032de031..11c7821a2490cebc442c2af235a544089abb1f8a 100644 (file)
 }
 
 .authentication-configuration .radio-card {
-  width: 50%;
+  width: 100%;
+  min-height: 250px;
   background-color: var(--neutral50);
   border: 1px solid var(--neutral200);
 }
   justify-content: space-between;
 }
 
-.authentication-configuration .radio-card-header {
-  justify-content: space-around;
-}
-
 .authentication-configuration .radio-card-body {
   justify-content: flex-start;
 }
 }
 
 .authentication-configuration .settings-definition-right {
-  display: flex;
-  align-items: center;
-  width: 50%;
+  align-items: end;
+  width: 20%;
 }
index 65e020d43fbbbe05b39f1b9ad4e941e9a331899c..b1fa53d3b8289c114e45d4414a993f0760a10d11 100644 (file)
@@ -1505,7 +1505,8 @@ settings.authentication.github.form.provisioning_with_github=Automatic user, gro
 settings.authentication.github.form.provisioning_with_github_short.autoProvisioning=Automatic provisioning
 settings.authentication.github.form.provisioning_with_github_short.jit=Just-in-Time provisioning
 settings.authentication.github.form.provisioning_with_github.description=Users, groups and permissions are automatically provisioned from your GitHub organizations. Once activated, users and groups can only be created and modified from your GitHub organizations/teams. Existing local users will be kept and can only be deactivated.
-settings.authentication.github.form.provisioning_with_github.description.doc=For more details, see {documentation}.
+settings.authentication.github.form.description.doc=For more details, see {documentation}.
+settings.authentication.github.form.provisioning_at_login.description=User and group will be synchronise only when user log into Sonarqube.
 settings.authentication.github.form.provisioning.disabled=Your current edition does not support provisioning with GitHub. See the {documentation} for more information.
 settings.authentication.github.synchronize_now=Synchronize now
 settings.authentication.github.synchronization_in_progress=Synchronization is in progress.