]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23263 Redesign Quality Profile select in activation modal
authorViktor Vorona <viktor.vorona@sonarsource.com>
Mon, 7 Oct 2024 12:36:18 +0000 (14:36 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 16 Oct 2024 20:03:01 +0000 (20:03 +0000)
server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRuleDetails-it.ts
server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx
server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 805a4fcb5c73b02f1bd558e0d7c894566ce91a23..a5010470c8675a9236698c5ca747a332263dd98a 100644 (file)
@@ -177,6 +177,15 @@ it('can activate/change/deactivate rule in quality profile', async () => {
   renderCodingRulesApp(mockLoggedInUser(), 'coding_rules?open=rule1', [Feature.PrioritizedRules]);
   expect(await ui.qpLink('QP Foo').find()).toBeInTheDocument();
 
+  // Activate profile with inherited ones java rule
+  await user.click(ui.activateButton.get());
+  await user.click(ui.qualityProfileSelect.get());
+  await user.click(byRole('option', { name: 'QP FooBarBaz' }).get());
+  await user.type(ui.paramInput('1').get(), 'paramInput');
+  await user.click(ui.activateButton.get(ui.activateQPDialog.get()));
+  expect(ui.qpLink('QP FooBarBaz').get()).toBeInTheDocument();
+  expect(ui.qpLink('QP FooBaz').get()).toBeInTheDocument();
+
   // Activate rule in quality profile
   expect(ui.prioritizedRuleCell.query()).not.toBeInTheDocument();
   await user.click(ui.activateButton.get());
@@ -194,13 +203,6 @@ it('can activate/change/deactivate rule in quality profile', async () => {
     'coding_rules.impact_customized.detailsoftware_quality.MAINTAINABILITYseverity_impact.MEDIUMseverity_impact.LOW',
   );
 
-  // Activate last java rule
-  await user.click(ui.activateButton.get());
-  await user.type(ui.paramInput('1').get(), 'paramInput');
-  await user.click(ui.activateButton.get(ui.activateQPDialog.get()));
-  expect(ui.qpLink('QP FooBarBaz').get()).toBeInTheDocument();
-  expect(ui.qpLink('QP FooBaz').get()).toBeInTheDocument();
-
   // Rule is activated in all quality profiles - show notification in dialog
   await user.click(ui.activateButton.get(screen.getByRole('main')));
   expect(ui.activaInAllQPs.get()).toBeInTheDocument();
@@ -215,10 +217,10 @@ it('can activate/change/deactivate rule in quality profile', async () => {
   await user.click(ui.newSeveritySelect(SoftwareQuality.Maintainability).get());
   await user.click(byRole('option', { name: 'severity_impact.BLOCKER' }).get());
   await user.click(ui.saveButton.get(ui.changeQPDialog.get()));
-  expect(await ui.qualityProfileRow.findAt(5)).toHaveTextContent('QP FooBaz');
-  expect(ui.qualityProfileRow.getAt(5)).toHaveTextContent('New');
+  expect(await ui.qualityProfileRow.findAt(4)).toHaveTextContent('QP FooBaz');
+  expect(ui.qualityProfileRow.getAt(4)).toHaveTextContent('New');
   await expect(
-    ui.newSeverityCustomizedCell.get(ui.qualityProfileRow.getAt(5)),
+    ui.newSeverityCustomizedCell.get(ui.qualityProfileRow.getAt(4)),
   ).toHaveATooltipWithContent(
     'coding_rules.impact_customized.detailsoftware_quality.MAINTAINABILITYseverity_impact.MEDIUMseverity_impact.BLOCKER',
   );
@@ -226,9 +228,9 @@ it('can activate/change/deactivate rule in quality profile', async () => {
   // Revert rule details in quality profile
   await user.click(ui.revertToParentDefinitionButton.get());
   await user.click(ui.yesButton.get());
-  expect(await ui.qualityProfileRow.findAt(5)).toHaveTextContent('QP FooBaz');
-  expect(await ui.qualityProfileRow.findAt(5)).not.toHaveTextContent('New');
-  expect(ui.newSeverityCustomizedCell.query(ui.qualityProfileRow.getAt(5))).not.toBeInTheDocument();
+  expect(await ui.qualityProfileRow.findAt(4)).toHaveTextContent('QP FooBaz');
+  expect(await ui.qualityProfileRow.findAt(4)).not.toHaveTextContent('New');
+  expect(ui.newSeverityCustomizedCell.query(ui.qualityProfileRow.getAt(4))).not.toBeInTheDocument();
 
   // Deactivate rule in quality profile
   await user.click(ui.deactivateInQPButton('QP FooBar').get());
index a06bb85d86d7e845651685722b22e47048a4e6ea..6c439093d7c6197524530cef04aef36ad3c4073f 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { Button, ButtonVariety, Checkbox, Modal, Text } from '@sonarsource/echoes-react';
+import { Button, ButtonVariety, Checkbox, Modal, Select, Text } from '@sonarsource/echoes-react';
 import {
   FlagMessage,
   FormField,
   InputField,
-  InputSelect,
   InputTextArea,
-  LabelValueSelectOption,
   Note,
   SafeHTMLInjection,
   SanitizeLevel,
@@ -74,9 +72,7 @@ export default function ActivationFormModal(props: Readonly<Props>) {
   const [changedPrioritizedRule, setChangedPrioritizedRule] = React.useState<boolean | undefined>(
     undefined,
   );
-  const [changedProfile, setChangedProfile] = React.useState<ProfileWithDepth | undefined>(
-    undefined,
-  );
+  const [changedProfile, setChangedProfile] = React.useState<string | undefined>(undefined);
   const [changedParams, setChangedParams] = React.useState<Record<string, string> | undefined>(
     undefined,
   );
@@ -94,7 +90,7 @@ export default function ActivationFormModal(props: Readonly<Props>) {
 
   const prioritizedRule =
     changedPrioritizedRule ?? (activation ? activation.prioritizedRule : false);
-  const profile = changedProfile ?? profilesWithDepth[0];
+  const profile = profiles.find((p) => p.key === changedProfile) ?? profilesWithDepth[0];
   const params = changedParams ?? getRuleParams({ activation, rule });
   const severity =
     changedSeverity ?? ((activation ? activation.severity : rule.severity) as IssueSeverity);
@@ -105,7 +101,11 @@ export default function ActivationFormModal(props: Readonly<Props>) {
       .map((impact) => [impact.softwareQuality, impact.severity] as const) ?? []),
     ...changedImpactSeveritiesMap,
   ]);
-  const profileOptions = profilesWithDepth.map((p) => ({ label: p.name, value: p }));
+  const profileOptions = profilesWithDepth.map((p) => ({
+    label: p.name,
+    value: p.key,
+    prefix: '   '.repeat(p.depth),
+  }));
   const isCustomRule = !!(rule as RuleDetails).templateKey;
   const activeInAllProfiles = profilesWithDepth.length <= 0;
   const isUpdateMode = !!activation;
@@ -167,48 +167,56 @@ export default function ActivationFormModal(props: Readonly<Props>) {
       }
       content={
         <form className="sw-pb-10" id={FORM_ID} onSubmit={handleFormSubmit}>
+          <Text as="div">
+            <FormattedMessage
+              id="coding_rules.rule_name.title"
+              values={{
+                name: <Text isSubdued>{rule.name}</Text>,
+              }}
+            />
+          </Text>
+
           {!isUpdateMode && activeInAllProfiles && (
-            <FlagMessage className="sw-mb-2" variant="info">
+            <FlagMessage className="sw-mt-4 sw-mb-6" variant="info">
               {intl.formatMessage({ id: 'coding_rules.active_in_all_profiles' })}
             </FlagMessage>
           )}
 
-          <FormField
-            ariaLabel={intl.formatMessage({ id: 'coding_rules.quality_profile' })}
-            label={intl.formatMessage({ id: 'coding_rules.quality_profile' })}
-            htmlFor="coding-rules-quality-profile-select-input"
-          >
-            <InputSelect
-              id="coding-rules-quality-profile-select"
-              inputId="coding-rules-quality-profile-select-input"
-              isClearable={false}
-              isDisabled={submitting || profilesWithDepth.length < MIN_PROFILES_TO_ENABLE_SELECT}
-              onChange={({ value }: LabelValueSelectOption<ProfileWithDepth>) => {
-                setChangedProfile(value);
-              }}
-              getOptionLabel={({ value }: LabelValueSelectOption<ProfileWithDepth>) =>
-                '   '.repeat(value.depth) + value.name
-              }
-              options={profileOptions}
-              value={profileOptions.find(({ value }) => value.key === profile?.key)}
-            />
-          </FormField>
+          {profilesWithDepth.length >= MIN_PROFILES_TO_ENABLE_SELECT ? (
+            <FormField
+              className="sw-mt-4"
+              ariaLabel={intl.formatMessage({ id: 'coding_rules.quality_profile' })}
+              label={intl.formatMessage({ id: 'coding_rules.quality_profile' })}
+              htmlFor="coding-rules-quality-profile-select"
+            >
+              <Select
+                id="coding-rules-quality-profile-select"
+                isNotClearable
+                isDisabled={submitting}
+                onChange={(value) => setChangedProfile(value ?? undefined)}
+                data={profileOptions}
+                value={profile?.key}
+              />
+            </FormField>
+          ) : (
+            <>
+              {(isUpdateMode || !activeInAllProfiles) && (
+                <Text as="div" className="sw-mb-6">
+                  <FormattedMessage
+                    id="coding_rules.quality_profile.title"
+                    values={{
+                      name: <Text isSubdued>{profile?.name}</Text>,
+                    }}
+                  />
+                </Text>
+              )}
+            </>
+          )}
 
           {hasFeature(Feature.PrioritizedRules) && (
             <FormField
               ariaLabel={intl.formatMessage({ id: 'coding_rules.prioritized_rule.title' })}
               label={intl.formatMessage({ id: 'coding_rules.prioritized_rule.title' })}
-              description={
-                <div className="sw-text-xs">
-                  {intl.formatMessage({ id: 'coding_rules.prioritized_rule.note' })}
-                  <DocumentationLink
-                    className="sw-ml-2 sw-whitespace-nowrap"
-                    to={DocLink.InstanceAdminQualityProfilesPrioritizingRules}
-                  >
-                    {intl.formatMessage({ id: 'learn_more' })}
-                  </DocumentationLink>
-                </div>
-              }
             >
               <Checkbox
                 onCheck={(checked) => setChangedPrioritizedRule(!!checked)}
@@ -216,6 +224,17 @@ export default function ActivationFormModal(props: Readonly<Props>) {
                 id="coding-rules-prioritized-rule"
                 checked={prioritizedRule}
               />
+              {prioritizedRule && (
+                <FlagMessage className="sw-mt-2" variant="info">
+                  {intl.formatMessage({ id: 'coding_rules.prioritized_rule.note' })}
+                  <DocumentationLink
+                    className="sw-ml-2 sw-whitespace-nowrap"
+                    to={DocLink.InstanceAdminQualityProfilesPrioritizingRules}
+                  >
+                    {intl.formatMessage({ id: 'learn_more' })}
+                  </DocumentationLink>
+                </FlagMessage>
+              )}
             </FormField>
           )}
 
@@ -360,10 +379,7 @@ export default function ActivationFormModal(props: Readonly<Props>) {
   );
 }
 
-function getQualityProfilesWithDepth(
-  profiles: Profile[] = [],
-  ruleLang?: string,
-): ProfileWithDepth[] {
+function getQualityProfilesWithDepth(profiles: Profile[], ruleLang?: string): ProfileWithDepth[] {
   return sortProfiles(
     profiles.filter(
       (profile) =>
@@ -387,7 +403,7 @@ function getRuleParams({
   rule: RuleDetails | Rule;
 }) {
   const params: Record<string, string> = {};
-  if (rule?.params) {
+  if (rule.params) {
     for (const param of rule.params) {
       params[param.key] = param.defaultValue ?? '';
     }
index ce8bbefbd6152a9936e546d77b3f0346bb46fdcc..6ae58cfd19a73d91d3f052059a17df94b23b7f92 100644 (file)
@@ -161,7 +161,7 @@ const selectors = {
     name: /coding_rules.deactivate_in_quality_profile/,
     hidden: true,
   }),
-  qualityProfileSelect: byLabelText('coding_rules.quality_profile'),
+  qualityProfileSelect: byRole('combobox', { name: 'coding_rules.quality_profile' }),
   oldSeveritySelect: byRole('combobox', { name: 'coding_rules.custom_severity.choose_severity' }),
   mqrSwitch: byRole('switch'),
   newSeveritySelect: (quality: SoftwareQuality) =>
index a2320cce5d910634a3f129c31db6bbfcb339884f..8dc49efc51c7dd5bdb90f3acfa21ddbb4e301aaa 100644 (file)
@@ -2619,7 +2619,9 @@ coding_rules.impact_customized.message=Rule severity was customized in this prof
 coding_rules.impact_customized.detail={softwareQuality} changed from {recommended} to {customized}
 coding_rules.severity_customized.message=Rule severity was customized from {recommended} to {customized}
 coding_rules.prioritized_rule.switch_label=All corresponding issues in the overall code should be fixed
-coding_rules.prioritized_rule.note=For your Quality Gate to fail when corresponding issues exist in the overall code, you must add a condition that checks whether any issues have been raised from prioritized rules.
+coding_rules.prioritized_rule.note=For the quality gate to fail, you must add a condition that checks whether any issues have been raised from prioritised rules
+coding_rules.rule_name.title=Rule Name: {name}
+coding_rules.quality_profile.title=Quality profile: {name}
 
 coding_rules.update_custom_rule=Update Custom Rule