]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23191 Quality Profile details adopts MQR mode
authorIsmail Cherri <ismail.cherri@sonarsource.com>
Tue, 15 Oct 2024 13:48:51 +0000 (15:48 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 16 Oct 2024 20:03:00 +0000 (20:03 +0000)
server/sonar-web/src/main/js/api/quality-profiles.ts
server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfileApp-it.tsx
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.tsx
server/sonar-web/src/main/js/queries/quality-profiles.ts
server/sonar-web/src/main/js/queries/rules.ts
server/sonar-web/src/main/js/types/types.ts

index 4ec559294c493299e9d0ec8870ef6b171102221d..d2b2898e3308e86169a7e03d31702844d6a990fd 100644 (file)
@@ -81,7 +81,10 @@ export function getQualityProfile({
 }: {
   compareToSonarWay?: boolean;
   profile: Profile;
-}): Promise<any> {
+}): Promise<{
+  compareToSonarWay?: { missingRuleCount: number; profile: string; profileName: string };
+  profile: Profile;
+}> {
   return getJSON('/api/qualityprofiles/show', { compareToSonarWay, key });
 }
 
index 18bf2780237d693e872bc29f0ab9b55418cf25b3..f4292e8e7f6bd82e97ed998a25c48fa83fd1858c 100644 (file)
@@ -21,7 +21,9 @@ import { screen, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import { byRole, byText } from '~sonar-aligned/helpers/testSelector';
 import QualityProfilesServiceMock from '../../../api/mocks/QualityProfilesServiceMock';
+import SettingsServiceMock from '../../../api/mocks/SettingsServiceMock';
 import { renderAppRoutes } from '../../../helpers/testReactTestingUtils';
+import { SettingsKey } from '../../../types/settings';
 import routes from '../routes';
 
 jest.mock('../../../api/quality-profiles');
@@ -29,14 +31,17 @@ jest.mock('../../../api/rules');
 
 beforeEach(() => {
   serviceMock.reset();
+  settingsMock.reset();
 });
 
 const serviceMock = new QualityProfilesServiceMock();
+const settingsMock = new SettingsServiceMock();
 const ui = {
   loading: byRole('status', { name: 'loading' }),
   permissionSection: byRole('region', { name: 'permissions.page' }),
   projectSection: byRole('region', { name: 'projects' }),
   rulesSection: byRole('region', { name: 'rules' }),
+  rulesSectionHeader: byRole('heading', { name: 'quality_profile.rules.breakdown' }),
   exportersSection: byRole('region', { name: 'quality_profiles.exporters' }),
   inheritanceSection: byRole('region', { name: 'quality_profiles.profile_inheritance' }),
   grantPermissionButton: byRole('button', {
@@ -59,8 +64,8 @@ const ui = {
   }),
   qualityProfilesHeader: byRole('heading', { name: 'quality_profiles.page' }),
   deleteQualityProfileButton: byRole('menuitem', { name: 'delete' }),
-  activateMoreRulesButton: byRole('button', { name: 'quality_profiles.activate_more' }),
   activateMoreLink: byRole('link', { name: 'quality_profiles.activate_more' }),
+  activateMoreButton: byRole('button', { name: 'quality_profiles.activate_more' }),
   activateMoreRulesLink: byRole('menuitem', { name: 'quality_profiles.activate_more_rules' }),
   backUpLink: byRole('menuitem', { name: 'backup_verb open_in_new_tab' }),
   compareLink: byRole('menuitem', { name: 'compare' }),
@@ -224,7 +229,7 @@ describe('Admin or user with permission', () => {
 
       expect(await ui.rulesSection.find()).toBeInTheDocument();
 
-      expect(ui.activateMoreLink.get()).toBeInTheDocument();
+      expect(await ui.activateMoreLink.find()).toBeInTheDocument();
       expect(ui.activateMoreLink.get()).toHaveAttribute(
         'href',
         '/coding_rules?qprofile=old-php-qp&activation=false',
@@ -235,8 +240,8 @@ describe('Admin or user with permission', () => {
       renderQualityProfile('sonar');
       await ui.waitForDataLoaded();
       expect(await ui.rulesSection.find()).toBeInTheDocument();
-      expect(ui.activateMoreRulesButton.get()).toBeInTheDocument();
-      expect(ui.activateMoreRulesButton.get()).toBeDisabled();
+      expect(await ui.activateMoreButton.find()).toBeInTheDocument();
+      expect(ui.activateMoreButton.get()).toBeDisabled();
     });
   });
 
@@ -450,7 +455,7 @@ describe('Every Users', () => {
     renderQualityProfile();
     await ui.waitForDataLoaded();
 
-    expect(await ui.rulesSection.find()).toBeInTheDocument();
+    expect(await ui.rulesSectionHeader.find()).toBeInTheDocument();
 
     ui.checkRuleRow('rule.clean_code_attribute_category.INTENTIONAL', 23, 4);
     ui.checkRuleRow('rule.clean_code_attribute_category.CONSISTENT', 2, 18);
@@ -461,6 +466,19 @@ describe('Every Users', () => {
     ui.checkRuleRow('software_quality.SECURITY', 0, 14);
   });
 
+  it('should be able to see active/inactive rules for a Quality Profile in Legacy mode', async () => {
+    settingsMock.set(SettingsKey.MQRMode, 'false');
+    renderQualityProfile();
+    await ui.waitForDataLoaded();
+
+    expect(await ui.rulesSectionHeader.find()).toBeInTheDocument();
+
+    ui.checkRuleRow('issue.type.BUG.plural', 60, 0);
+    ui.checkRuleRow('issue.type.VULNERABILITY.plural', 40, 0);
+    ui.checkRuleRow('issue.type.CODE_SMELL.plural', 250, 0);
+    ui.checkRuleRow('issue.type.SECURITY_HOTSPOT.plural', 50, 0);
+  });
+
   it('should be able to see a warning when some rules are missing compare to Sonar way', async () => {
     renderQualityProfile();
     await ui.waitForDataLoaded();
index b23f72ebb9cf132d233f67ae3310d5e1772447e0..eebcabef175f3483efd42102560f027e8b5b4a8b 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import styled from '@emotion/styled';
+import { Heading, Spinner } from '@sonarsource/echoes-react';
 import {
   ButtonPrimary,
   ContentCell,
   NumericalCell,
-  Spinner,
-  SubTitle,
   Table,
   TableRow,
   themeColor,
 } from 'design-system';
 import { keyBy } from 'lodash';
 import * as React from 'react';
-import { useEffect, useState } from 'react';
 import DocHelpTooltip from '~sonar-aligned/components/controls/DocHelpTooltip';
-import { getQualityProfile } from '../../../api/quality-profiles';
-import { searchRules } from '../../../api/rules';
 import { translate } from '../../../helpers/l10n';
 import { isDefined } from '../../../helpers/types';
 import { getRulesUrl } from '../../../helpers/urls';
+import { useGetQualityProfile } from '../../../queries/quality-profiles';
+import { useSearchRulesQuery } from '../../../queries/rules';
+import { useIsLegacyCCTMode } from '../../../queries/settings';
 import { CleanCodeAttributeCategory, SoftwareQuality } from '../../../types/clean-code-taxonomy';
 import { SearchRulesResponse } from '../../../types/coding-rules';
 import { RulesFacetName } from '../../../types/rules';
-import { Dict } from '../../../types/types';
+import { RuleTypes } from '../../../types/types';
 import { Profile } from '../types';
 import ProfileRulesDeprecatedWarning from './ProfileRulesDeprecatedWarning';
 import ProfileRulesRow from './ProfileRulesRow';
@@ -56,176 +55,206 @@ interface ByType {
 }
 
 export default function ProfileRules({ profile }: Readonly<Props>) {
+  const { data: isLegacy } = useIsLegacyCCTMode();
   const activateMoreUrl = getRulesUrl({ qprofile: profile.key, activation: 'false' });
   const { actions = {} } = profile;
 
-  const [loading, setLoading] = useState(false);
-  const [countsByCctCategory, setCountsByCctCategory] = useState<Dict<ByType>>({});
-  const [totalByCctCategory, setTotalByCctCategory] = useState<Dict<ByType>>({});
-  const [countsBySoftwareImpact, setCountsBySoftwareImpact] = useState<Dict<ByType>>({});
-  const [totalBySoftwareQuality, setTotalBySoftwareQuality] = useState<Dict<ByType>>({});
-  const [sonarWayDiff, setSonarWayDiff] = useState<{
-    missingRuleCount: number;
-    profile: string;
-    profileName: string;
-  } | null>(null);
-
-  useEffect(() => {
-    async function loadRules() {
-      function findFacet(response: SearchRulesResponse, property: string) {
-        const facet = response.facets?.find((f) => f.property === property);
-        return facet ? facet.values : [];
-      }
+  const { data: allRules, isLoading: isAllRulesLoading } = useSearchRulesQuery({
+    ps: 1,
+    languages: profile.language,
+    facets: isLegacy
+      ? `${RulesFacetName.Types}`
+      : `${RulesFacetName.CleanCodeAttributeCategories},${RulesFacetName.ImpactSoftwareQualities}`,
+  });
+
+  const { data: activatedRules, isLoading: isActivatedRulesLoading } = useSearchRulesQuery(
+    {
+      ps: 1,
+      activation: 'true',
+      facets: isLegacy
+        ? `${RulesFacetName.Types}`
+        : `${RulesFacetName.CleanCodeAttributeCategories},${RulesFacetName.ImpactSoftwareQualities}`,
+      qprofile: profile.key,
+    },
+    { enabled: !!allRules },
+  );
+
+  const { data: sonarWayDiff, isLoading: isShowProfileLoading } = useGetQualityProfile(
+    { compareToSonarWay: true, profile },
+    { enabled: !profile.isBuiltIn, select: (data) => data.compareToSonarWay },
+  );
+
+  const findFacet = React.useCallback((response: SearchRulesResponse, property: string) => {
+    const facet = response.facets?.find((f) => f.property === property);
+    return facet ? facet.values : [];
+  }, []);
 
-      try {
-        setLoading(true);
-        return await Promise.all([
-          searchRules({
-            languages: profile.language,
-            facets: `${RulesFacetName.CleanCodeAttributeCategories},${RulesFacetName.ImpactSoftwareQualities}`,
-          }),
-          searchRules({
-            activation: 'true',
-            facets: `${RulesFacetName.CleanCodeAttributeCategories},${RulesFacetName.ImpactSoftwareQualities}`,
-            qprofile: profile.key,
-          }),
-          !profile.isBuiltIn &&
-            getQualityProfile({
-              compareToSonarWay: true,
-              profile,
-            }),
-        ]).then((responses) => {
-          const [allRules, activatedRules, showProfile] = responses;
-          const extractFacetData = (facetName: string, response: SearchRulesResponse) => {
-            return keyBy<ByType>(findFacet(response, facetName), 'val');
-          };
-          setTotalByCctCategory(
-            extractFacetData(RulesFacetName.CleanCodeAttributeCategories, allRules),
-          );
-          setCountsByCctCategory(
-            extractFacetData(RulesFacetName.CleanCodeAttributeCategories, activatedRules),
-          );
-          setTotalBySoftwareQuality(
-            extractFacetData(RulesFacetName.ImpactSoftwareQualities, allRules),
-          );
-          setCountsBySoftwareImpact(
-            extractFacetData(RulesFacetName.ImpactSoftwareQualities, activatedRules),
-          );
-          setSonarWayDiff(showProfile?.compareToSonarWay);
-        });
-      } finally {
-        setLoading(false);
+  const extractFacetData = React.useCallback(
+    (facetName: string, response: SearchRulesResponse | null | undefined) => {
+      if (!response) {
+        return {};
       }
-    }
 
-    loadRules();
-  }, [profile]);
+      return keyBy<ByType>(findFacet(response, facetName), 'val');
+    },
+    [findFacet],
+  );
 
-  if (loading) {
-    return <Spinner />;
-  }
+  const totalByCctCategory = extractFacetData(
+    RulesFacetName.CleanCodeAttributeCategories,
+    allRules,
+  );
+  const countsByCctCategory = extractFacetData(
+    RulesFacetName.CleanCodeAttributeCategories,
+    activatedRules,
+  );
+  const totalBySoftwareQuality = extractFacetData(RulesFacetName.ImpactSoftwareQualities, allRules);
+  const countsBySoftwareImpact = extractFacetData(
+    RulesFacetName.ImpactSoftwareQualities,
+    activatedRules,
+  );
+  const totalByTypes = extractFacetData(RulesFacetName.Types, allRules);
+  const countsByTypes = extractFacetData(RulesFacetName.Types, activatedRules);
 
   return (
     <section aria-label={translate('rules')} className="it__quality-profiles__rules">
-      <SubTitle>{translate('quality_profile.rules.breakdown')}</SubTitle>
-
-      <Table
-        columnCount={3}
-        columnWidths={['50%', '25%', '25%']}
-        header={
-          <StyledTableRowHeader>
-            <ContentCell className="sw-font-semibold sw-pl-4">
-              {translate('quality_profile.rules.cct_categories_title')}
-            </ContentCell>
-            <NumericalCell className="sw-font-regular">{translate('active')}</NumericalCell>
-            <NumericalCell className="sw-pr-4 sw-font-regular">
-              {translate('inactive')}
-            </NumericalCell>
-          </StyledTableRowHeader>
-        }
-        noHeaderTopBorder
-        noSidePadding
-        withRoundedBorder
-      >
-        {Object.values(CleanCodeAttributeCategory).map((category) => (
-          <ProfileRulesRow
-            title={translate('rule.clean_code_attribute_category', category)}
-            total={totalByCctCategory[category]?.count}
-            count={countsByCctCategory[category]?.count}
-            key={category}
-            qprofile={profile.key}
-            propertyName={RulesFacetName.CleanCodeAttributeCategories}
-            propertyValue={category}
-          />
-        ))}
-      </Table>
-
-      <Table
-        className="sw-mt-4"
-        columnCount={3}
-        columnWidths={['50%', '25%', '25%']}
-        header={
-          <StyledTableRowHeader>
-            <ContentCell className="sw-font-semibold sw-pl-4">
-              {translate('quality_profile.rules.software_qualities_title')}
-            </ContentCell>
-            <NumericalCell className="sw-font-regular">{translate('active')}</NumericalCell>
-            <NumericalCell className="sw-pr-4 sw-font-regular">
-              {translate('inactive')}
-            </NumericalCell>
-          </StyledTableRowHeader>
-        }
-        noHeaderTopBorder
-        noSidePadding
-        withRoundedBorder
-      >
-        {Object.values(SoftwareQuality).map((quality) => (
-          <ProfileRulesRow
-            title={translate('software_quality', quality)}
-            total={totalBySoftwareQuality[quality]?.count}
-            count={countsBySoftwareImpact[quality]?.count}
-            key={quality}
-            qprofile={profile.key}
-            propertyName={RulesFacetName.ImpactSoftwareQualities}
-            propertyValue={quality}
-          />
-        ))}
-      </Table>
-
-      <div className="sw-mt-6 sw-flex sw-flex-col sw-gap-4 sw-items-start">
-        {profile.activeDeprecatedRuleCount > 0 && (
-          <ProfileRulesDeprecatedWarning
-            activeDeprecatedRules={profile.activeDeprecatedRuleCount}
-            profile={profile.key}
-          />
-        )}
+      <Spinner isLoading={isActivatedRulesLoading || isAllRulesLoading || isShowProfileLoading}>
+        <Heading className="sw-mb-4" as="h2">
+          {translate('quality_profile.rules.breakdown')}
+        </Heading>
 
-        {isDefined(sonarWayDiff) && sonarWayDiff.missingRuleCount > 0 && (
-          <ProfileRulesSonarWayComparison
-            language={profile.language}
-            profile={profile.key}
-            sonarWayMissingRules={sonarWayDiff.missingRuleCount}
-            sonarway={sonarWayDiff.profile}
-          />
+        {isLegacy && (
+          <Table
+            columnCount={3}
+            columnWidths={['50%', '25%', '25%']}
+            header={
+              <StyledTableRowHeader>
+                <ContentCell className="sw-font-semibold sw-pl-4">{translate('type')}</ContentCell>
+                <NumericalCell className="sw-font-regular">{translate('active')}</NumericalCell>
+                <NumericalCell className="sw-pr-4 sw-font-regular">
+                  {translate('inactive')}
+                </NumericalCell>
+              </StyledTableRowHeader>
+            }
+            noHeaderTopBorder
+            noSidePadding
+            withRoundedBorder
+          >
+            {RuleTypes.filter((type) => type !== 'UNKNOWN').map((type) => (
+              <ProfileRulesRow
+                title={translate('issue.type', type, 'plural')}
+                total={totalByTypes[type]?.count}
+                count={countsByTypes[type]?.count}
+                key={type}
+                qprofile={profile.key}
+                type={type}
+              />
+            ))}
+          </Table>
         )}
 
-        {actions.edit && !profile.isBuiltIn && (
-          <ButtonPrimary className="it__quality-profiles__activate-rules" to={activateMoreUrl}>
-            {translate('quality_profiles.activate_more')}
-          </ButtonPrimary>
+        {!isLegacy && (
+          <>
+            <Table
+              className="sw-mb-4"
+              columnCount={3}
+              columnWidths={['50%', '25%', '25%']}
+              header={
+                <StyledTableRowHeader>
+                  <ContentCell className="sw-font-semibold sw-pl-4">
+                    {translate('quality_profile.rules.software_qualities_title')}
+                  </ContentCell>
+                  <NumericalCell className="sw-font-regular">{translate('active')}</NumericalCell>
+                  <NumericalCell className="sw-pr-4 sw-font-regular">
+                    {translate('inactive')}
+                  </NumericalCell>
+                </StyledTableRowHeader>
+              }
+              noHeaderTopBorder
+              noSidePadding
+              withRoundedBorder
+            >
+              {Object.values(SoftwareQuality).map((quality) => (
+                <ProfileRulesRow
+                  title={translate('software_quality', quality)}
+                  total={totalBySoftwareQuality[quality]?.count}
+                  count={countsBySoftwareImpact[quality]?.count}
+                  key={quality}
+                  qprofile={profile.key}
+                  propertyName={RulesFacetName.ImpactSoftwareQualities}
+                  propertyValue={quality}
+                />
+              ))}
+            </Table>
+
+            <Table
+              columnCount={3}
+              columnWidths={['50%', '25%', '25%']}
+              header={
+                <StyledTableRowHeader>
+                  <ContentCell className="sw-font-semibold sw-pl-4">
+                    {translate('quality_profile.rules.cct_categories_title')}
+                  </ContentCell>
+                  <NumericalCell className="sw-font-regular">{translate('active')}</NumericalCell>
+                  <NumericalCell className="sw-pr-4 sw-font-regular">
+                    {translate('inactive')}
+                  </NumericalCell>
+                </StyledTableRowHeader>
+              }
+              noHeaderTopBorder
+              noSidePadding
+              withRoundedBorder
+            >
+              {Object.values(CleanCodeAttributeCategory).map((category) => (
+                <ProfileRulesRow
+                  title={translate('rule.clean_code_attribute_category', category)}
+                  total={totalByCctCategory[category]?.count}
+                  count={countsByCctCategory[category]?.count}
+                  key={category}
+                  qprofile={profile.key}
+                  propertyName={RulesFacetName.CleanCodeAttributeCategories}
+                  propertyValue={category}
+                />
+              ))}
+            </Table>
+          </>
         )}
 
-        {/* if a user is allowed to `copy` a profile if they are a global admin */}
-        {/* this user could potentially activate more rules if the profile was not built-in */}
-        {/* in such cases it's better to show the button but disable it with a tooltip */}
-        {actions.copy && profile.isBuiltIn && (
-          <DocHelpTooltip content={translate('quality_profiles.activate_more.help.built_in')}>
-            <ButtonPrimary className="it__quality-profiles__activate-rules" disabled>
+        <div className="sw-mt-6 sw-flex sw-flex-col sw-gap-4 sw-items-start">
+          {profile.activeDeprecatedRuleCount > 0 && (
+            <ProfileRulesDeprecatedWarning
+              activeDeprecatedRules={profile.activeDeprecatedRuleCount}
+              profile={profile.key}
+            />
+          )}
+
+          {isDefined(sonarWayDiff) && sonarWayDiff.missingRuleCount > 0 && (
+            <ProfileRulesSonarWayComparison
+              language={profile.language}
+              profile={profile.key}
+              sonarWayMissingRules={sonarWayDiff.missingRuleCount}
+              sonarway={sonarWayDiff.profile}
+            />
+          )}
+
+          {actions.edit && !profile.isBuiltIn && (
+            <ButtonPrimary className="it__quality-profiles__activate-rules" to={activateMoreUrl}>
               {translate('quality_profiles.activate_more')}
             </ButtonPrimary>
-          </DocHelpTooltip>
-        )}
-      </div>
+          )}
+
+          {/* if a user is allowed to `copy` a profile if they are a global admin */}
+          {/* this user could potentially activate more rules if the profile was not built-in */}
+          {/* in such cases it's better to show the button but disable it with a tooltip */}
+          {actions.copy && profile.isBuiltIn && (
+            <DocHelpTooltip content={translate('quality_profiles.activate_more.help.built_in')}>
+              <ButtonPrimary className="it__quality-profiles__activate-rules" disabled>
+                {translate('quality_profiles.activate_more')}
+              </ButtonPrimary>
+            </DocHelpTooltip>
+          )}
+        </div>
+      </Spinner>
     </section>
   );
 }
index 698eb5e917feac4cec9f62b70e46a00391ec13ae..8cd7641e64ca4c4058cc47273781ba46a9815315 100644 (file)
@@ -25,49 +25,57 @@ import { translateWithParameters } from '../../../helpers/l10n';
 import { isDefined } from '../../../helpers/types';
 import { getRulesUrl } from '../../../helpers/urls';
 import { RulesFacetName } from '../../../types/rules';
+import { RuleType } from '../../../types/types';
 
 interface Props {
   className?: string;
   count: number | null;
-  propertyName:
+  propertyName?:
     | RulesFacetName.CleanCodeAttributeCategories
     | RulesFacetName.ImpactSoftwareQualities;
-  propertyValue: string;
+  propertyValue?: string;
   qprofile: string;
   title: string;
   total: number | null;
+  type?: RuleType;
 }
 
 export default function ProfileRulesRow(props: Readonly<Props>) {
+  const { qprofile, count, className, propertyName, propertyValue, title, total, type } = props;
+
+  const typeOrCCTQuery = {
+    ...(propertyName ? { [propertyName]: propertyValue } : {}),
+    ...(type ? { types: type } : {}),
+  };
   const activeRulesUrl = getRulesUrl({
-    qprofile: props.qprofile,
+    qprofile,
     activation: 'true',
-    [props.propertyName]: props.propertyValue,
+    ...typeOrCCTQuery,
   });
   const inactiveRulesUrl = getRulesUrl({
-    qprofile: props.qprofile,
+    qprofile,
     activation: 'false',
-    [props.propertyName]: props.propertyValue,
+    ...typeOrCCTQuery,
   });
   let inactiveCount = null;
-  if (props.count != null && props.total != null) {
-    inactiveCount = props.total - props.count;
+  if (count != null && total != null) {
+    inactiveCount = total - count;
   }
 
   return (
-    <TableRow className={props.className}>
-      <ContentCell className="sw-pl-4">{props.title}</ContentCell>
+    <TableRow className={className}>
+      <ContentCell className="sw-pl-4">{title}</ContentCell>
       <NumericalCell>
-        {isDefined(props.count) && props.count > 0 ? (
+        {isDefined(count) && count > 0 ? (
           <Link
             aria-label={translateWithParameters(
               'quality_profile.rules.see_x_active_x_rules',
-              props.count,
+              count,
               props.title,
             )}
             to={activeRulesUrl}
           >
-            {formatMeasure(props.count, MetricType.ShortInteger)}
+            {formatMeasure(count, MetricType.ShortInteger)}
           </Link>
         ) : (
           <Note>0</Note>
@@ -79,7 +87,7 @@ export default function ProfileRulesRow(props: Readonly<Props>) {
             aria-label={translateWithParameters(
               'quality_profile.rules.see_x_inactive_x_rules',
               inactiveCount,
-              props.title,
+              title,
             )}
             to={inactiveRulesUrl}
           >
index 1b4d40c3f8381f0b91c34c2b834780ae1c21bf6b..572a54a375853c1250a6a7e9f78b59aa2ed91fbf 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { UseQueryResult, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import {
+  UseQueryResult,
+  queryOptions,
+  useMutation,
+  useQuery,
+  useQueryClient,
+} from '@tanstack/react-query';
 import {
   ActivateRuleParameters,
   AddRemoveGroupParameters,
@@ -30,8 +36,10 @@ import {
   compareProfiles,
   deactivateRule,
   getProfileInheritance,
+  getQualityProfile,
 } from '../api/quality-profiles';
 import { ProfileInheritanceDetails } from '../types/types';
+import { createQueryHook } from './common';
 
 export function useProfileInheritanceQuery(
   profile?: Pick<Profile, 'language' | 'name' | 'parentKey'>,
@@ -54,6 +62,17 @@ export function useProfileInheritanceQuery(
   });
 }
 
+export const useGetQualityProfile = createQueryHook(
+  (data: Parameters<typeof getQualityProfile>[0]) => {
+    return queryOptions({
+      queryKey: ['quality-profile', 'details', data.profile, data.compareToSonarWay],
+      queryFn: () => {
+        return getQualityProfile(data);
+      },
+    });
+  },
+);
+
 export function useProfilesCompareQuery(leftKey: string, rightKey: string) {
   return useQuery({
     queryKey: ['quality-profiles', 'compare', leftKey, rightKey],
index 46baf6bf2174e66ff78ed8f972a041bfb92df903..07f83be487e9c8844d1c8899858fd0cbc7ae8b3b 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { queryOptions, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import {
+  queryOptions,
+  useMutation,
+  useQueryClient,
+} from '@tanstack/react-query';
 import { createRule, deleteRule, getRuleDetails, searchRules, updateRule } from '../api/rules';
 import { mapRestRuleToRule } from '../apps/coding-rules/utils';
 import { SearchRulesResponse } from '../types/coding-rules';
@@ -33,8 +37,8 @@ function getRulesQueryKey(type: 'search' | 'details', data?: SearchRulesQuery |
   return key;
 }
 
-export function useSearchRulesQuery(data: SearchRulesQuery) {
-  return useQuery({
+export const useSearchRulesQuery = createQueryHook((data: SearchRulesQuery) => {
+  return queryOptions({
     queryKey: getRulesQueryKey('search', data),
     queryFn: ({ queryKey: [, , query] }) => {
       if (!query) {
@@ -45,7 +49,7 @@ export function useSearchRulesQuery(data: SearchRulesQuery) {
     },
     staleTime: StaleTime.NEVER,
   });
-}
+});
 
 export const useRuleDetailsQuery = createQueryHook((data: { actives?: boolean; key: string }) => {
   return queryOptions({
index bc254a234c7e7337f0911d6f625fc9f7d62e27c7..c0ce6ab4654bdd30bffefeee683faa02a555c60a 100644 (file)
@@ -598,7 +598,14 @@ export interface RuleParameter {
 
 export type RuleScope = 'MAIN' | 'TEST' | 'ALL';
 
-export type RuleType = 'BUG' | 'VULNERABILITY' | 'CODE_SMELL' | 'SECURITY_HOTSPOT' | 'UNKNOWN';
+export const RuleTypes = [
+  'BUG',
+  'VULNERABILITY',
+  'CODE_SMELL',
+  'SECURITY_HOTSPOT',
+  'UNKNOWN',
+] as const;
+export type RuleType = (typeof RuleTypes)[number];
 
 export interface Snippet {
   end: number;