]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21280 Show no data information when new accepted issues measure is missing
author7PH <benjamin.raymond@sonarsource.com>
Thu, 25 Jan 2024 15:38:23 +0000 (16:38 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 31 Jan 2024 20:03:36 +0000 (20:03 +0000)
server/sonar-web/design-system/src/theme/colors.ts
server/sonar-web/design-system/src/theme/light.ts
server/sonar-web/src/main/js/apps/overview/branches/AcceptedIssuesPanel.tsx
server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx
server/sonar-web/src/main/js/apps/overview/components/IssueMeasuresCardInner.tsx
server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx

index 04f4b075785adfc07bd48b5fcd5c7413d444c454..18584103ff6ead0f0b211b71690c399c6ca73a27 100644 (file)
@@ -24,6 +24,7 @@ export default {
   grey: { 50: [235, 235, 235], 100: [221, 221, 221] },
   blueGrey: {
     25: [252, 252, 253],
+    35: [247, 249, 252],
     50: [239, 242, 249],
     100: [225, 230, 243],
     200: [197, 205, 223],
index e20f3b914cf659d453ee64f6bbf59f3af7aac4f4..d6ba2162d3b86575943e0598d575f1e0660afc7a 100644 (file)
@@ -522,7 +522,7 @@ export const lightTheme = {
     // project
     projectCardBackground: COLORS.white,
     projectCardBorder: COLORS.blueGrey[100],
-    projectLightGreyCardBorder: COLORS.grey[50],
+    projectCardInfo: COLORS.blueGrey[35],
 
     // overview
     overviewCardDefaultIcon: secondary.light,
index 80bc780870ed02aa6b4dffa5edeaeb526a5e3e81..a4dc0e11d1710bf8c7c7a09f28c6075766b07baa 100644 (file)
@@ -23,6 +23,7 @@ import {
   Card,
   HighImpactCircleIcon,
   LightLabel,
+  NoDataIcon,
   PageTitle,
   SnoozeCircleIcon,
   Spinner,
@@ -90,9 +91,8 @@ function AcceptedIssuesPanel(props: Readonly<AcceptedIssuesPanelProps>) {
         >
           <Card className="sw-flex sw-gap-4">
             <IssueMeasuresCardInner
-              linkDisabled={component.needIssueSync}
+              disabled={component.needIssueSync}
               className={classNames({ 'sw-w-1/2': !isNewCode, 'sw-w-full': isNewCode })}
-              noDataIconClassName="sw--translate-y-3"
               metric={MetricKey.accepted_issues}
               value={formatMeasure(acceptedCount, MetricType.ShortInteger)}
               header={intl.formatMessage({
@@ -112,16 +112,21 @@ function AcceptedIssuesPanel(props: Readonly<AcceptedIssuesPanelProps>) {
               <>
                 <StyledCardSeparator />
                 <IssueMeasuresCardInner
-                  linkDisabled={component.needIssueSync}
+                  disabled={Boolean(component.needIssueSync) || !acceptedWithHighImpactCount}
                   className="sw-w-1/2"
-                  noDataIconClassName="sw--translate-y-3"
                   metric={MetricKey.high_impact_accepted_issues}
                   value={formatMeasure(acceptedWithHighImpactCount, MetricType.ShortInteger)}
                   header={intl.formatMessage({
                     id: 'overview.high_impact_accepted_issues',
                   })}
                   url={acceptedIssuesWithHighImpactUrl}
-                  icon={<HighImpactCircleIcon className="sw--translate-y-3" />}
+                  icon={
+                    acceptedWithHighImpactCount ? (
+                      <HighImpactCircleIcon className="sw--translate-y-3" />
+                    ) : (
+                      <NoDataIcon className="sw--translate-y-3" width={36} height={36} />
+                    )
+                  }
                 />
               </>
             )}
index 61e5ae9854e10fee1cfb365e91b3695bc075ac94..00a35b001d6a87071e5801992331476a75766022 100644 (file)
@@ -92,12 +92,32 @@ export default function NewCodeMeasuresPanel(props: Readonly<Props>) {
     );
   }
 
+  let acceptedIssuesFooter = null;
+  if (!newAcceptedIssues) {
+    acceptedIssuesFooter = (
+      <StyledInfoMessage className="sw-rounded-2 sw-text-xs sw-p-4 sw-flex sw-gap-1 sw-flex-wrap">
+        <span>{intl.formatMessage({ id: 'overview.project.no_data' })}</span>
+        <span>
+          {intl.formatMessage({
+            id: `overview.run_analysis_to_compute.${component.qualifier}`,
+          })}
+        </span>
+      </StyledInfoMessage>
+    );
+  } else {
+    acceptedIssuesFooter = (
+      <TextSubdued className="sw-body-xs">
+        {intl.formatMessage({ id: 'overview.accepted_issues.help' })}
+      </TextSubdued>
+    );
+  }
+
   return (
     <div className="sw-mt-6" id={getTabPanelId(MeasuresTabs.New)}>
       <LightGreyCard className="sw-flex sw-rounded-2 sw-gap-4">
         <IssueMeasuresCardInner
           data-test="overview__measures-new_issues"
-          linkDisabled={component.needIssueSync}
+          disabled={component.needIssueSync}
           className="sw-w-1/2"
           metric={MetricKey.new_violations}
           value={formatMeasure(newIssues, MetricType.ShortInteger)}
@@ -116,7 +136,7 @@ export default function NewCodeMeasuresPanel(props: Readonly<Props>) {
         <StyledCardSeparator />
         <IssueMeasuresCardInner
           data-test="overview__measures-accepted_issues"
-          linkDisabled={component.needIssueSync}
+          disabled={Boolean(component.needIssueSync) || !newAcceptedIssues}
           className="sw-w-1/2"
           metric={MetricKey.new_accepted_issues}
           value={formatMeasure(newAcceptedIssues, MetricType.ShortInteger)}
@@ -128,11 +148,7 @@ export default function NewCodeMeasuresPanel(props: Readonly<Props>) {
             issueStatuses: IssueStatus.Accepted,
             inNewCodePeriod: 'true',
           })}
-          footer={
-            <TextSubdued className="sw-body-xs">
-              {intl.formatMessage({ id: 'overview.accepted_issues.help' })}
-            </TextSubdued>
-          }
+          footer={acceptedIssuesFooter}
           icon={
             <SnoozeCircleIcon
               color={
@@ -204,3 +220,7 @@ const StyledCardSeparator = styled.div`
   width: 1px;
   background-color: ${themeColor('projectCardBorder')};
 `;
+
+const StyledInfoMessage = styled.div`
+  background-color: ${themeColor('projectCardInfo')};
+`;
index 4121771f465dc95dc1d2e7d050ff38a56a1a1aa4..8600556c2fa900e21f65e533fa6e01ef23dc87ee 100644 (file)
@@ -19,7 +19,7 @@
  */
 import styled from '@emotion/styled';
 import classNames from 'classnames';
-import { Badge, ContentLink, LightLabel, NoDataIcon, themeColor } from 'design-system';
+import { Badge, ContentLink, themeColor } from 'design-system';
 import * as React from 'react';
 import { Path } from 'react-router-dom';
 import Tooltip from '../../../components/controls/Tooltip';
@@ -28,8 +28,6 @@ import { localizeMetric } from '../../../helpers/measures';
 import { MetricKey } from '../../../types/metrics';
 import { OverviewDisabledLinkTooltip } from './OverviewDisabledLinkTooltip';
 
-const NO_DATA_ICON_SIZE = 36;
-
 interface IssueMeasuresCardInnerProps extends React.HTMLAttributes<HTMLDivElement> {
   metric: MetricKey;
   value?: string;
@@ -37,29 +35,20 @@ interface IssueMeasuresCardInnerProps extends React.HTMLAttributes<HTMLDivElemen
   url: Path;
   failed?: boolean;
   icon?: React.ReactNode;
-  linkDisabled?: boolean;
+  disabled?: boolean;
   footer?: React.ReactNode;
-  noDataIconClassName?: string;
 }
 
 export function IssueMeasuresCardInner(props: Readonly<IssueMeasuresCardInnerProps>) {
-  const {
-    header,
-    metric,
-    icon,
-    value,
-    url,
-    failed,
-    footer,
-    className,
-    noDataIconClassName,
-    linkDisabled,
-    ...rest
-  } = props;
+  const { header, metric, icon, value, url, failed, footer, className, disabled, ...rest } = props;
 
   return (
     <div className={classNames('sw-flex sw-flex-col sw-gap-3', className)} {...rest}>
-      <div className="sw-flex sw-flex-col sw-gap-2 sw-font-semibold">
+      <div
+        className={classNames('sw-flex sw-flex-col sw-gap-2 sw-font-semibold', {
+          'sw-opacity-60': disabled,
+        })}
+      >
         <ColorBold className="sw-flex sw-items-center sw-gap-2 sw-body-sm-highlight">
           {header}
 
@@ -71,39 +60,29 @@ export function IssueMeasuresCardInner(props: Readonly<IssueMeasuresCardInnerPro
         </ColorBold>
         <div className="sw-flex sw-justify-between sw-items-center sw-h-9">
           <div className="sw-h-fit">
-            {value ? (
-              <Tooltip
-                classNameSpace={linkDisabled ? 'tooltip' : 'sw-hidden'}
-                overlay={<OverviewDisabledLinkTooltip />}
+            <Tooltip
+              classNameSpace={disabled ? 'tooltip' : 'sw-hidden'}
+              overlay={value && <OverviewDisabledLinkTooltip />}
+            >
+              <ContentLink
+                disabled={disabled || !value}
+                aria-label={
+                  value
+                    ? translateWithParameters(
+                        'overview.see_more_details_on_x_of_y',
+                        value,
+                        localizeMetric(metric),
+                      )
+                    : translate('no_data')
+                }
+                className="it__overview-measures-value sw-w-fit sw-text-lg"
+                to={url}
               >
-                <ContentLink
-                  disabled={linkDisabled}
-                  aria-label={translateWithParameters(
-                    'overview.see_more_details_on_x_of_y',
-                    value,
-                    localizeMetric(metric),
-                  )}
-                  className="it__overview-measures-value sw-w-fit sw-text-lg"
-                  to={url}
-                >
-                  {value}
-                </ContentLink>
-              </Tooltip>
-            ) : (
-              <Tooltip overlay={translate('no_data')}>
-                <LightLabel aria-label={translate('no_data')}> — </LightLabel>
-              </Tooltip>
-            )}
+                {value ? value : '-'}
+              </ContentLink>
+            </Tooltip>
           </div>
-          {value ? (
-            icon
-          ) : (
-            <NoDataIcon
-              className={noDataIconClassName}
-              width={NO_DATA_ICON_SIZE}
-              height={NO_DATA_ICON_SIZE}
-            />
-          )}
+          {icon}
         </div>
       </div>
       {footer}
index e4017a68a953c741f90518a1e1fd67fb961bbb2c..2386b41d1a8ffe82235b612fabfbca091081fa00 100644 (file)
@@ -111,12 +111,14 @@ export default function IssueMeasuresCard(
         data-test={`overview__measures-${MetricKey.new_accepted_issues}`}
         metric={MetricKey.new_accepted_issues}
         value={formatMeasure(acceptedCount, MetricType.ShortInteger)}
-        linkDisabled={component.needIssueSync}
+        disabled={component.needIssueSync}
         url={acceptedUrl}
         icon={
-          <SnoozeCircleIcon
-            color={acceptedCount === '0' ? 'overviewCardDefaultIcon' : 'overviewCardWarningIcon'}
-          />
+          acceptedCount && (
+            <SnoozeCircleIcon
+              color={acceptedCount === '0' ? 'overviewCardDefaultIcon' : 'overviewCardWarningIcon'}
+            />
+          )
         }
         footer={
           <TextSubdued className="sw-body-xs">
@@ -150,9 +152,9 @@ export default function IssueMeasuresCard(
         data-test={`overview__measures-${MetricKey.pull_request_fixed_issues}`}
         metric={MetricKey.pull_request_fixed_issues}
         value={formatMeasure(fixedCount, MetricType.ShortInteger)}
-        linkDisabled={component.needIssueSync}
+        disabled={component.needIssueSync}
         url={fixedUrl}
-        icon={fixedCount !== '0' && <TrendDownCircleIcon />}
+        icon={fixedCount && fixedCount !== '0' && <TrendDownCircleIcon />}
         footer={
           <TextSubdued className="sw-body-xs">
             {intl.formatMessage({ id: 'overview.pull_request.fixed_issues.help' })}