]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21467 Fix clickable software quality breakdown links during reindexing
author7PH <benjamin.raymond@sonarsource.com>
Tue, 30 Jan 2024 18:13:25 +0000 (19:13 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 31 Jan 2024 20:03:37 +0000 (20:03 +0000)
server/sonar-web/design-system/src/components/Link.tsx
server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx
server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureBreakdownCard.tsx
server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx
server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx
server/sonar-web/src/main/js/apps/overview/components/MeasuresCard.tsx

index edd9f192a4086b61eaff3021f4335b241ced736f..d97489785d7fbfcf1c0d530786d5e5d3fcc25b5c 100644 (file)
@@ -155,11 +155,14 @@ export const NakedLink = styled(BaseLink)`
   font-weight: 600;
   color: ${themeColor('linkNaked')};
 
-  &:hover,
-  &:focus,
-  &:active {
-    color: ${themeColor('linkActive')};
-  }
+  ${({ disabled, theme }) =>
+    disabled
+      ? tw`sw-cursor-default`
+      : `&:hover,
+         &:focus,
+         &:active {
+           color: ${themeColor('linkActive')({ theme })};
+         }`};
 `;
 
 export const DrilldownLink = styled(StyledBaseLink)`
@@ -219,6 +222,8 @@ export const DiscreetLinkBox = styled(StyledBaseLink)`
     background-color: none;
     display: block;
   }
+
+  ${({ disabled }) => (disabled ? tw`sw-cursor-default` : '')};
 `;
 LinkBox.displayName = 'DiscreetLinkBox';
 
index 4c228520cc7b10a47e3e7d0bf9939cdb361d35f2..1d66c017c3e68ccebba36dbc63ab6cca65759ccd 100644 (file)
@@ -38,6 +38,7 @@ import { QualityGateStatus } from '../../../types/quality-gates';
 import { Component, MeasureEnhanced } from '../../../types/types';
 import MeasuresCard from '../components/MeasuresCard';
 import MeasuresCardNumber from '../components/MeasuresCardNumber';
+import { OverviewDisabledLinkTooltip } from '../components/OverviewDisabledLinkTooltip';
 import { MeasuresTabs } from '../utils';
 import MeasuresPanelPercentCards from './MeasuresPanelPercentCards';
 import SoftwareImpactMeasureCard from './SoftwareImpactMeasureCard';
@@ -99,6 +100,8 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas
               color={acceptedIssues === '0' ? 'overviewCardDefaultIcon' : 'overviewCardWarningIcon'}
             />
           }
+          disabled={component.needIssueSync}
+          tooltip={component.needIssueSync ? <OverviewDisabledLinkTooltip /> : null}
         >
           <TextSubdued className="sw-body-xs sw-mt-3">
             {intl.formatMessage({
index d8755ae82adf8949c0b16fabd03a61b011cacf51..fcab9056e6502d22283225934ea1948156b15a1b 100644 (file)
@@ -92,6 +92,7 @@ export function SoftwareImpactMeasureBreakdownCard(
             }),
           },
         )}
+        disabled={component.needIssueSync}
         to={url}
       >
         <span>{formatMeasure(value, MetricType.ShortInteger)}</span>
index d0ea22018fb1ed29af8bc052f6b168be5abba0c2..3d7bab789e508159bf7b0b93f1d0cf02fb7a6122 100644 (file)
@@ -29,6 +29,7 @@ import {
 } from 'design-system';
 import * as React from 'react';
 import { useIntl } from 'react-intl';
+import Tooltip from '../../../components/controls/Tooltip';
 import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
 import { formatMeasure } from '../../../helpers/measures';
 import { getComponentIssuesUrl } from '../../../helpers/urls';
@@ -39,6 +40,7 @@ import {
 } from '../../../types/clean-code-taxonomy';
 import { MetricKey, MetricType } from '../../../types/metrics';
 import { Component, MeasureEnhanced } from '../../../types/types';
+import { OverviewDisabledLinkTooltip } from '../components/OverviewDisabledLinkTooltip';
 import { softwareQualityToMeasure } from '../utils';
 import SoftwareImpactMeasureBreakdownCard from './SoftwareImpactMeasureBreakdownCard';
 import SoftwareImpactMeasureRating from './SoftwareImpactMeasureRating';
@@ -60,6 +62,8 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow
   const measureRaw = measures.find((m) => m.metric.key === metricKey);
   const measure = JSON.parse(measureRaw?.value ?? 'null') as SoftwareImpactMeasureData;
 
+  const renderDisabled = !measure || component.needIssueSync;
+
   // Find rating measure
   const ratingMeasure = measures.find((m) => m.metric.key === ratingMetricKey);
 
@@ -85,28 +89,31 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow
       <div className="sw-flex sw-flex-col sw-gap-3">
         <div
           className={classNames('sw-flex sw-gap-1 sw-items-end', {
-            'sw-opacity-60': !measure,
+            'sw-opacity-60': renderDisabled,
           })}
         >
           {measure ? (
-            <NakedLink
-              data-testid={`overview__software-impact-${softwareQuality}`}
-              aria-label={intl.formatMessage(
-                {
-                  id: `overview.measures.software_impact.see_list_of_x_open_issues`,
-                },
-                {
-                  count: measure.total,
-                  softwareQuality: intl.formatMessage({
-                    id: `software_quality.${softwareQuality}`,
-                  }),
-                },
-              )}
-              className="sw-text-xl"
-              to={totalLinkHref}
-            >
-              {formatMeasure(measure.total, MetricType.ShortInteger)}
-            </NakedLink>
+            <Tooltip overlay={component.needIssueSync ? <OverviewDisabledLinkTooltip /> : null}>
+              <NakedLink
+                data-testid={`overview__software-impact-${softwareQuality}`}
+                aria-label={intl.formatMessage(
+                  {
+                    id: `overview.measures.software_impact.see_list_of_x_open_issues`,
+                  },
+                  {
+                    count: measure.total,
+                    softwareQuality: intl.formatMessage({
+                      id: `software_quality.${softwareQuality}`,
+                    }),
+                  },
+                )}
+                className="sw-text-xl"
+                to={totalLinkHref}
+                disabled={component.needIssueSync}
+              >
+                {formatMeasure(measure.total, MetricType.ShortInteger)}
+              </NakedLink>
+            </Tooltip>
           ) : (
             <StyledDash className="sw-self-center sw-font-bold" name="-" />
           )}
index a956a8b75e33329fbcde2d67468e006cfd5df504..e8710eaca52c51dc4e9d7fe85a06132ecd2eae0b 100644 (file)
@@ -348,6 +348,41 @@ describe('project overview', () => {
       false,
     ]);
   });
+
+  it('should disable software impact measure card links during reindexing', async () => {
+    const { user, ui } = getPageObjects();
+    renderBranchOverview({
+      component: mockComponent({
+        breadcrumbs: [mockComponent({ key: 'foo' })],
+        key: 'foo',
+        needIssueSync: true,
+      }),
+    });
+
+    await user.click(await ui.overallCodeButton.find());
+
+    expect(await ui.softwareImpactMeasureCard(SoftwareQuality.Security).find()).toBeInTheDocument();
+
+    ui.expectSoftwareImpactMeasureCard(
+      SoftwareQuality.Security,
+      'B',
+      {
+        total: 1,
+        [SoftwareImpactSeverity.High]: 0,
+        [SoftwareImpactSeverity.Medium]: 1,
+        [SoftwareImpactSeverity.Low]: 0,
+      },
+      [false, true, false],
+    );
+
+    await expect(
+      byRole('link', {
+        name: `overview.measures.software_impact.see_list_of_x_open_issues.${1}.software_quality.${
+          SoftwareQuality.Security
+        }`,
+      }).get(),
+    ).toHaveATooltipWithContent('indexation.in_progress');
+  });
 });
 
 describe('application overview', () => {
index de80ee18c20630dbafe51c25e5aaecad42d4193c..68c6174487bd2eab164923b52a721516f8791531 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import styled from '@emotion/styled';
-import { Badge, Card, ContentLink, themeBorder, themeColor } from 'design-system';
+import { Badge, Card, ContentLink, Tooltip, themeBorder, themeColor } from 'design-system';
 import * as React from 'react';
 import { To } from 'react-router-dom';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
@@ -32,12 +32,14 @@ export interface MeasuresCardProps {
   label: string;
   failed?: boolean;
   icon?: React.ReactElement;
+  disabled?: boolean;
+  tooltip?: React.ReactNode | null;
 }
 
 export default function MeasuresCard(
   props: React.PropsWithChildren<MeasuresCardProps & React.HTMLAttributes<HTMLDivElement>>,
 ) {
-  const { failed, children, metric, icon, value, url, label, ...rest } = props;
+  const { failed, children, metric, icon, value, url, label, disabled, tooltip, ...rest } = props;
 
   return (
     <StyledCard className="sw-h-fit sw-p-6 sw-rounded-2 sw-text-base" {...rest}>
@@ -49,17 +51,20 @@ export default function MeasuresCard(
       )}
       <div className="sw-flex sw-items-center sw-mt-1 sw-justify-between sw-font-semibold">
         {value ? (
-          <ContentLink
-            aria-label={translateWithParameters(
-              'overview.see_more_details_on_x_of_y',
-              value,
-              localizeMetric(metric),
-            )}
-            className="it__overview-measures-value sw-text-lg"
-            to={url}
-          >
-            {value}
-          </ContentLink>
+          <Tooltip overlay={tooltip}>
+            <ContentLink
+              aria-label={translateWithParameters(
+                'overview.see_more_details_on_x_of_y',
+                value,
+                localizeMetric(metric),
+              )}
+              className="it__overview-measures-value sw-text-lg"
+              to={url}
+              disabled={disabled}
+            >
+              {value}
+            </ContentLink>
+          </Tooltip>
         ) : (
           <ColorBold> — </ColorBold>
         )}