]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22831 Migrate Change Rule details to echoes components
authorViktor Vorona <viktor.vorona@sonarsource.com>
Thu, 12 Sep 2024 15:02:57 +0000 (17:02 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 13 Sep 2024 20:02:35 +0000 (20:02 +0000)
server/sonar-web/src/main/js/api/mocks/data/rules.ts
server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
server/sonar-web/src/main/js/apps/coding-rules/components/ActivationButton.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResultActivation.tsx

index 37acd30ad075eafdf09d60b857a6b8e69f455afc..12d827391a8f9973e8d4061cd94b3ab93b92c7d3 100644 (file)
@@ -265,12 +265,7 @@ export function mockRuleDetailsList() {
 
 export function mockRulesActivationsInQP() {
   return {
-    [RULE_1]: [
-      mockRuleActivation({ qProfile: QP_1 }),
-      mockRuleActivation({ qProfile: QP_4 }),
-      mockRuleActivation({ qProfile: QP_5, inherit: 'INHERITED' }),
-      mockRuleActivation({ qProfile: QP_6 }),
-    ],
+    [RULE_1]: [mockRuleActivation({ qProfile: QP_1 }), mockRuleActivation({ qProfile: QP_6 })],
     [RULE_7]: [mockRuleActivation({ qProfile: QP_2 })],
     [RULE_8]: [mockRuleActivation({ qProfile: QP_2 })],
     [RULE_9]: [
@@ -287,5 +282,9 @@ export function mockRulesActivationsInQP() {
       mockRuleActivation({ qProfile: QP_2, inherit: 'OVERRIDES', prioritizedRule: true }),
       mockRuleActivation({ qProfile: QP_2_Parent, severity: 'MINOR' }),
     ],
+    [RULE_11]: [
+      mockRuleActivation({ qProfile: QP_4 }),
+      mockRuleActivation({ qProfile: QP_5, inherit: 'INHERITED' }),
+    ],
   };
 }
index 6fdf75b170702f73cdd6b9425b31aa2c049adc75..7e57a1be36dd76f31e8a25869110e878c1320a08 100644 (file)
@@ -669,9 +669,10 @@ describe('Rule app details', () => {
 
     // Change rule details in quality profile
     await user.click(ui.changeButton('QP FooBaz').get());
+    await user.clear(ui.paramInput('1').get());
     await user.type(ui.paramInput('1').get(), 'New');
     await user.click(ui.saveButton.get(ui.changeQPDialog.get()));
-    expect(screen.getByText('New')).toBeInTheDocument();
+    expect(await screen.findByText('New')).toBeInTheDocument();
 
     // Revert rule details in quality profile
     await user.click(ui.revertToParentDefinitionButton.get());
@@ -687,7 +688,7 @@ describe('Rule app details', () => {
   it('can deactivate an inherrited rule', async () => {
     const { ui, user } = getPageObjects();
     rulesHandler.setIsAdmin();
-    renderCodingRulesApp(mockLoggedInUser(), 'coding_rules?open=rule1');
+    renderCodingRulesApp(mockLoggedInUser(), 'coding_rules?open=rule11');
     await ui.detailsloaded();
 
     // Should show 2 deactivate buttons: one for the parent, one for the child profile.
@@ -704,7 +705,7 @@ describe('Rule app details', () => {
     const { ui } = getPageObjects();
     rulesHandler.setIsAdmin();
     settingsHandler.set(SettingsKey.QPAdminCanDisableInheritedRules, 'false');
-    renderCodingRulesApp(mockLoggedInUser(), 'coding_rules?open=rule1');
+    renderCodingRulesApp(mockLoggedInUser(), 'coding_rules?open=rule11');
     await ui.detailsloaded();
 
     // Should show 1 deactivate button: one for the parent, none for the child profile.
index 995cb49fd19700fedc6cf1c078b3fe35ff9c0c05..e88e698bc7c0b82d15722a5f6915111e09f4a326 100644 (file)
@@ -17,7 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { ButtonSecondary } from 'design-system';
+import { Button, ButtonVariety } from '@sonarsource/echoes-react';
 import * as React from 'react';
 import { Profile as BaseProfile } from '../../../api/quality-profiles';
 import { Rule, RuleActivation, RuleDetails } from '../../../types/types';
@@ -40,25 +40,26 @@ export default function ActivationButton(props: Props) {
 
   return (
     <>
-      <ButtonSecondary
+      <Button
+        variety={ButtonVariety.Default}
         aria-label={ariaLabel}
         className={className}
         id="coding-rules-quality-profile-activate"
         onClick={() => setModalOpen(true)}
       >
         {buttonText}
-      </ButtonSecondary>
+      </Button>
 
-      {modalOpen && (
-        <ActivationFormModal
-          activation={activation}
-          modalHeader={modalHeader}
-          onClose={() => setModalOpen(false)}
-          onDone={props.onDone}
-          profiles={profiles}
-          rule={rule}
-        />
-      )}
+      <ActivationFormModal
+        activation={activation}
+        modalHeader={modalHeader}
+        isOpen={modalOpen}
+        onOpenChange={setModalOpen}
+        onClose={() => setModalOpen(false)}
+        onDone={props.onDone}
+        profiles={profiles}
+        rule={rule}
+      />
     </>
   );
 }
index 94487afb68991aefe1bbda013c712147f05c993f..b6a26714588865217a0c22786bd30b8eed216734 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
+import { Button, ButtonVariety, Modal } from '@sonarsource/echoes-react';
 import {
-  ButtonPrimary,
   FlagMessage,
   FormField,
   InputField,
   InputSelect,
   InputTextArea,
   LabelValueSelectOption,
-  Modal,
   Note,
   Switch,
 } from 'design-system';
@@ -46,9 +45,11 @@ import { SeveritySelect } from './SeveritySelect';
 
 interface Props {
   activation?: RuleActivation;
+  isOpen: boolean;
   modalHeader: string;
   onClose: () => void;
   onDone?: (severity: string, prioritizedRule: boolean) => Promise<void> | void;
+  onOpenChange: (isOpen: boolean) => void;
   profiles: Profile[];
   rule: Rule | RuleDetails;
 }
@@ -61,29 +62,50 @@ const MIN_PROFILES_TO_ENABLE_SELECT = 2;
 const FORM_ID = 'rule-activation-modal-form';
 
 export default function ActivationFormModal(props: Readonly<Props>) {
-  const { activation, rule, profiles, modalHeader } = props;
+  const { activation, rule, profiles, modalHeader, isOpen, onOpenChange } = props;
   const { mutate: activateRule, isPending: submitting } = useActivateRuleMutation((data) => {
     props.onDone?.(data.severity as string, data.prioritizedRule as boolean);
     props.onClose();
   });
   const { hasFeature } = useAvailableFeatures();
   const intl = useIntl();
-  const [prioritizedRule, setPrioritizedRule] = React.useState(
-    activation ? activation.prioritizedRule : false,
+  const [changedPrioritizedRule, setChangedPrioritizedRule] = React.useState<boolean | undefined>(
+    undefined,
   );
-
-  const profilesWithDepth = getQualityProfilesWithDepth(profiles, rule.lang);
-  const [profile, setProfile] = React.useState(profilesWithDepth[0]);
-  const [params, setParams] = React.useState(getRuleParams({ activation, rule }));
-  const [severity, setSeverity] = React.useState(
-    (activation ? activation.severity : rule.severity) as IssueSeverity,
+  const [changedProfile, setChangedProfile] = React.useState<ProfileWithDepth | undefined>(
+    undefined,
+  );
+  const [changedParams, setChangedParams] = React.useState<Record<string, string> | undefined>(
+    undefined,
   );
+  const [changedSeverity, setChangedSeverity] = React.useState<IssueSeverity | undefined>(
+    undefined,
+  );
+
+  const profilesWithDepth = React.useMemo(() => {
+    return getQualityProfilesWithDepth(profiles, rule.lang);
+  }, [profiles, rule.lang]);
 
+  const prioritizedRule =
+    changedPrioritizedRule ?? (activation ? activation.prioritizedRule : false);
+  const profile = changedProfile ?? profilesWithDepth[0];
+  const params = changedParams ?? getRuleParams({ activation, rule });
+  const severity =
+    changedSeverity ?? ((activation ? activation.severity : rule.severity) as IssueSeverity);
   const profileOptions = profilesWithDepth.map((p) => ({ label: p.name, value: p }));
   const isCustomRule = !!(rule as RuleDetails).templateKey;
   const activeInAllProfiles = profilesWithDepth.length <= 0;
   const isUpdateMode = !!activation;
 
+  React.useEffect(() => {
+    if (!isOpen) {
+      setChangedPrioritizedRule(undefined);
+      setChangedProfile(undefined);
+      setChangedParams(undefined);
+      setChangedSeverity(undefined);
+    }
+  }, [isOpen]);
+
   const handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
     event.preventDefault();
     const data = {
@@ -100,27 +122,33 @@ export default function ActivationFormModal(props: Readonly<Props>) {
     event: React.SyntheticEvent<HTMLInputElement | HTMLTextAreaElement>,
   ) => {
     const { name, value } = event.currentTarget;
-    setParams({ ...params, [name]: value });
+    setChangedParams({ ...params, [name]: value });
   };
 
-  const makeScrollable = (rule.params?.length ?? 0) > 1;
-
   return (
     <Modal
-      headerTitle={modalHeader}
-      onClose={props.onClose}
-      loading={submitting}
-      isOverflowVisible={!makeScrollable}
-      isScrollable={makeScrollable}
+      title={modalHeader}
+      isOpen={isOpen}
+      onOpenChange={onOpenChange}
       primaryButton={
-        <ButtonPrimary disabled={submitting || activeInAllProfiles} form={FORM_ID} type="submit">
+        <Button
+          variety={ButtonVariety.Primary}
+          isDisabled={submitting || activeInAllProfiles}
+          isLoading={submitting}
+          form={FORM_ID}
+          type="submit"
+        >
           {isUpdateMode
             ? intl.formatMessage({ id: 'save' })
             : intl.formatMessage({ id: 'coding_rules.activate' })}
-        </ButtonPrimary>
+        </Button>
+      }
+      secondaryButton={
+        <Button variety={ButtonVariety.Default} isDisabled={submitting} onClick={props.onClose}>
+          {intl.formatMessage({ id: 'cancel' })}
+        </Button>
       }
-      secondaryButtonLabel={intl.formatMessage({ id: 'cancel' })}
-      body={
+      content={
         <form className="sw-pb-10" id={FORM_ID} onSubmit={handleFormSubmit}>
           {!isUpdateMode && activeInAllProfiles && (
             <FlagMessage className="sw-mb-2" variant="info">
@@ -139,7 +167,7 @@ export default function ActivationFormModal(props: Readonly<Props>) {
               isClearable={false}
               isDisabled={submitting || profilesWithDepth.length < MIN_PROFILES_TO_ENABLE_SELECT}
               onChange={({ value }: LabelValueSelectOption<ProfileWithDepth>) => {
-                setProfile(value);
+                setChangedProfile(value);
               }}
               getOptionLabel={({ value }: LabelValueSelectOption<ProfileWithDepth>) =>
                 '   '.repeat(value.depth) + value.name
@@ -184,7 +212,7 @@ export default function ActivationFormModal(props: Readonly<Props>) {
                       { state: 'off' },
                     ),
                   }}
-                  onChange={setPrioritizedRule}
+                  onChange={setChangedPrioritizedRule}
                   value={prioritizedRule}
                 />
                 <span className="sw-text-xs">
@@ -202,7 +230,7 @@ export default function ActivationFormModal(props: Readonly<Props>) {
             <SeveritySelect
               isDisabled={submitting}
               onChange={({ value }: LabelValueSelectOption<IssueSeverity>) => {
-                setSeverity(value);
+                setChangedSeverity(value);
               }}
               severity={severity}
             />
index e12115032a1e25b6d66da8e3d2f1001a3e2bad12..8bb71661a4d2d06776e9cbf88a372d4bab02a508 100644 (file)
@@ -39,7 +39,7 @@ export default function ComparisonResultActivation(props: React.PropsWithChildre
   const [rule, setRule] = React.useState<RuleDetails>();
   const intl = useIntl();
 
-  const isOpen = state === 'open' && rule;
+  const isOpen = state === 'open' && !!rule;
 
   const activateRuleMsg = intl.formatMessage(
     { id: 'quality_profiles.comparison.activate_rule' },
@@ -71,8 +71,10 @@ export default function ComparisonResultActivation(props: React.PropsWithChildre
         </Button>
       </Tooltip>
 
-      {isOpen && (
+      {rule && (
         <ActivationFormModal
+          isOpen={isOpen}
+          onOpenChange={(open) => setState(open ? 'open' : 'closed')}
           modalHeader={intl.formatMessage({ id: 'coding_rules.activate_in_quality_profile' })}
           onClose={() => {
             setState('closed');