]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19752 New UX for App overview while re-indexing
authorDavid Cho-Lerat <david.cho-lerat@sonarsource.com>
Tue, 18 Jul 2023 14:46:46 +0000 (16:46 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 19 Jul 2023 20:03:05 +0000 (20:03 +0000)
server/sonar-web/design-system/src/components/Link.tsx
server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx
server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap
server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx
server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx
server/sonar-web/src/main/js/apps/overview/components/OverviewDisabledLinkTooltip.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueLabel-test.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 7ec8470de210c07c5139e75c1bb2737270c8a478..02889e25c5faaf6d0b2232b975b92ad4494dfb76 100644 (file)
@@ -148,9 +148,13 @@ export const DrilldownLink = styled(StyledBaseLink)`
   ${tw`sw-tracking-tight`}
   ${tw`sw-whitespace-nowrap`}
 
-  --active: ${themeColor('linkActive')};
-  --border: ${themeBorder('default', 'drilldownBorder')};
-  --borderActive: ${themeBorder('default', 'linkActive')};
+  ${({ disabled, theme }) =>
+    disabled
+      ? tw`sw-cursor-default`
+      : `--active: ${themeColor('linkActive')({ theme })};
+         --border: ${themeBorder('default', 'drilldownBorder')({ theme })};
+         --borderActive: ${themeBorder('default', 'linkActive')({ theme })};`};
+
   --color: ${themeColor('drilldown')};
 `;
 
index defc97b75270b5cca1739c620a6addd00123f91e..924963867ae213398cc8f360b0fed60587548af6 100644 (file)
@@ -95,7 +95,9 @@ function renderInProgressBanner(props: IndexationNotificationRendererProps) {
 
   return (
     <>
-      <span className="spacer-right">{translate('indexation.in_progress')}</span>
+      <span className="spacer-right">{`${translate('indexation.in_progress')} ${translate(
+        'indexation.projects_unavailable'
+      )}`}</span>
       <i className="spinner spacer-right" />
       <span className="spacer-right">
         {translateWithParameters('indexation.progression', percentCompleted)}
@@ -120,7 +122,9 @@ function renderInProgressWithFailureBanner(props: IndexationNotificationRenderer
 
   return (
     <>
-      <span className="spacer-right">{translate('indexation.in_progress')}</span>
+      <span className="spacer-right">{`${translate('indexation.in_progress')} ${translate(
+        'indexation.projects_unavailable'
+      )}`}</span>
       <i className="spinner spacer-right" />
       <span className="spacer-right">
         <FormattedMessage
index edcf985e28b89e9e01ed79ec5f33dc5a7ff0b46e..6b8c3972439c047c84ef7eeb5d7614d06fed32b5 100644 (file)
@@ -133,7 +133,7 @@ exports[`should render correctly for type="InProgress" & isSystemAdmin=false 1`]
       <span
         className="spacer-right"
       >
-        indexation.in_progress
+        indexation.in_progress indexation.projects_unavailable
       </span>
       <i
         className="spinner spacer-right"
@@ -164,7 +164,7 @@ exports[`should render correctly for type="InProgress" & isSystemAdmin=true 1`]
       <span
         className="spacer-right"
       >
-        indexation.in_progress
+        indexation.in_progress indexation.projects_unavailable
       </span>
       <i
         className="spinner spacer-right"
@@ -217,7 +217,7 @@ exports[`should render correctly for type="InProgressWithFailure" & isSystemAdmi
       <span
         className="spacer-right"
       >
-        indexation.in_progress
+        indexation.in_progress indexation.projects_unavailable
       </span>
       <i
         className="spinner spacer-right"
@@ -256,7 +256,7 @@ exports[`should render correctly for type="InProgressWithFailure" & isSystemAdmi
       <span
         className="spacer-right"
       >
-        indexation.in_progress
+        indexation.in_progress indexation.projects_unavailable
       </span>
       <i
         className="spinner spacer-right"
index f8b0e30662d9e0f59dab6464606859778d6c0331..4461066dd24634061e03755caf6b6cc54e59bcca 100644 (file)
@@ -22,11 +22,13 @@ import {
   CoverageIndicator,
   DeferredSpinner,
   DuplicationsIndicator,
+  FlagMessage,
   LightLabel,
   PageTitle,
   ToggleButton,
 } from 'design-system';
 import * as React from 'react';
+import DocLink from '../../../components/common/DocLink';
 import ComponentReportActions from '../../../components/controls/ComponentReportActions';
 import { Location, withRouter } from '../../../components/hoc/withRouter';
 import { duplicationRatingConverter } from '../../../components/measure/utils';
@@ -137,7 +139,6 @@ export function MeasuresPanel(props: MeasuresPanelProps) {
               </LightLabel>
             )}
           </div>
-
           {tab === MeasuresPanelTabs.New && leakPeriod ? (
             <LightLabel className="sw-body-sm sw-flex sw-items-center sw-mt-4">
               <span className="sw-mr-1">{translate('overview.new_code')}:</span>
@@ -147,6 +148,22 @@ export function MeasuresPanel(props: MeasuresPanelProps) {
             <div className="sw-h-4 sw-pt-1 sw-mt-4" />
           )}
 
+          {component.qualifier === ComponentQualifier.Application && component.needIssueSync && (
+            <FlagMessage className="sw-mt-4" variant="info">
+              <span>
+                {`${translate('indexation.in_progress')} ${translate(
+                  'indexation.details_unavailable'
+                )}`}
+                <DocLink
+                  className="sw-ml-1 sw-whitespace-nowrap"
+                  to="/instance-administration/reindexing/"
+                >
+                  {translate('learn_more')}
+                </DocLink>
+              </span>
+            </FlagMessage>
+          )}
+
           {!hasDiffMeasures && isNewCodeTab ? (
             <MeasuresPanelNoNewCode branch={branch} component={component} period={period} />
           ) : (
index 511cf069cb275073e46d77afce6bc7e8e861aa7e..baf7b512cdeeb324a8a350ec253e2148d65413ea 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 { DrilldownLink, HelperHintIcon, LightLabel } from 'design-system';
 import * as React from 'react';
 import HelpTooltip from '../../../components/controls/HelpTooltip';
+import Tooltip from '../../../components/controls/Tooltip';
 import { getLeakValue } from '../../../components/measure/utils';
 import { getBranchLikeQuery } from '../../../helpers/branch-like';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { findMeasure, formatMeasure, localizeMetric } from '../../../helpers/measures';
 import { getComponentIssuesUrl, getComponentSecurityHotspotsUrl } from '../../../helpers/urls';
 import { BranchLike } from '../../../types/branch-like';
+import { ComponentQualifier } from '../../../types/component';
 import { IssueType } from '../../../types/issues';
 import { MetricType } from '../../../types/metrics';
 import { Component, MeasureEnhanced } from '../../../types/types';
 import { getIssueMetricKey } from '../utils';
+import { OverviewDisabledLinkTooltip } from './OverviewDisabledLinkTooltip';
 
 export interface IssueLabelProps {
   branchLike?: BranchLike;
@@ -46,15 +50,16 @@ export function IssueLabel(props: IssueLabelProps) {
   const measure = findMeasure(measures, metricKey);
 
   let value;
+
   if (measure) {
     value = useDiffMetric ? getLeakValue(measure) : measure.value;
   }
 
   const params = {
     ...getBranchLikeQuery(branchLike),
+    inNewCodePeriod: useDiffMetric ? 'true' : 'false',
     resolved: 'false',
     types: type,
-    inNewCodePeriod: useDiffMetric ? 'true' : 'false',
   };
 
   const url =
@@ -62,24 +67,37 @@ export function IssueLabel(props: IssueLabelProps) {
       ? getComponentSecurityHotspotsUrl(component.key, params)
       : getComponentIssuesUrl(component.key, params);
 
+  const disabled =
+    component.qualifier === ComponentQualifier.Application && component.needIssueSync;
+
+  const drilldownLinkProps = disabled
+    ? { disabled, to: '' }
+    : {
+        'aria-label': translateWithParameters(
+          'overview.see_list_of_x_y_issues',
+          value as string,
+          localizeMetric(metricKey)
+        ),
+        to: url,
+      };
+
   return (
     <div className="sw-body-md sw-flex sw-items-center">
       {value === undefined ? (
         <LightLabel aria-label={translate('no_data')}> — </LightLabel>
       ) : (
-        <DrilldownLink
-          aria-label={translateWithParameters(
-            'overview.see_list_of_x_y_issues',
-            value,
-            localizeMetric(metricKey)
-          )}
-          className="it__overview-measures-value"
-          to={url}
+        <Tooltip
+          classNameSpace={disabled ? 'tooltip' : 'sw-hidden'}
+          overlay={<OverviewDisabledLinkTooltip />}
         >
-          {formatMeasure(value, MetricType.ShortInteger)}
-        </DrilldownLink>
+          <DrilldownLink className="it__overview-measures-value" {...drilldownLinkProps}>
+            {formatMeasure(value, MetricType.ShortInteger)}
+          </DrilldownLink>
+        </Tooltip>
       )}
+
       <LightLabel className="sw-mx-2">{localizeMetric(metricKey)}</LightLabel>
+
       {helpTooltip && (
         <HelpTooltip overlay={helpTooltip}>
           <HelperHintIcon aria-label={helpTooltip} />
diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewDisabledLinkTooltip.tsx b/server/sonar-web/src/main/js/apps/overview/components/OverviewDisabledLinkTooltip.tsx
new file mode 100644 (file)
index 0000000..1242d47
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import DocLink from '../../../components/common/DocLink';
+import { translate } from '../../../helpers/l10n';
+
+export function OverviewDisabledLinkTooltip() {
+  return (
+    <div className="sw-body-sm sw-w-[280px]">
+      {translate('indexation.in_progress')}
+
+      <br />
+
+      {translate('indexation.link_unavailable')}
+
+      <hr className="sw-mx-0 sw-my-3 sw-p-0 sw-w-full" />
+
+      <span className="sw-body-sm-highlight">{translate('indexation.learn_more')}</span>
+
+      <DocLink className="sw-ml-1" to="/instance-administration/reindexing/">
+        {translate('indexation.reindexing')}
+      </DocLink>
+    </div>
+  );
+}
index 6d2de23747f56b2a7c2bf3f0428b9d28fd708eab..5b2746e36212706ce41a2ca41687c15d26cd718b 100644 (file)
@@ -23,6 +23,7 @@ import { mockPullRequest } from '../../../../helpers/mocks/branch-like';
 import { mockComponent } from '../../../../helpers/mocks/component';
 import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks';
 import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { ComponentQualifier } from '../../../../types/component';
 import { IssueType } from '../../../../types/issues';
 import { MetricKey } from '../../../../types/metrics';
 import { IssueLabel, IssueLabelProps } from '../IssueLabel';
@@ -74,6 +75,26 @@ it('should render correctly for hotspots with tooltip', async () => {
   expect(screen.getByText('tooltip text')).toBeInTheDocument();
 });
 
+it('should render correctly for a re-indexing Application', () => {
+  const type = IssueType.SecurityHotspot;
+  const measures = [
+    mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.security_hotspots }) }),
+    mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_security_hotspots }) }),
+  ];
+
+  renderIssueLabel({
+    component: mockComponent({ needIssueSync: true, qualifier: ComponentQualifier.Application }),
+    measures,
+    type,
+  });
+
+  expect(
+    screen.queryByRole('link', {
+      name: 'overview.see_list_of_x_y_issues.1.0.metric.security_hotspots.name',
+    })
+  ).not.toBeInTheDocument();
+});
+
 function renderIssueLabel(props: Partial<IssueLabelProps> = {}) {
   return renderComponent(
     <IssueLabel
index 736bdf495c95bc658e1dd23ebe7c16e06930a377..2066a81cdd85aa6a8b23b8d1a2c4bb5c711a1f4f 100644 (file)
@@ -4722,7 +4722,10 @@ maintenance.sonarqube_is_offline.text=The connection to SonarQube is lost. Pleas
 # INDEXATION
 #
 #------------------------------------------------------------------------------
-indexation.in_progress=SonarQube is reloading project data. Some projects will be unavailable until this process is complete.
+indexation.in_progress=SonarQube is reindexing project data.
+indexation.details_unavailable=Details are unavailable until this process is complete.
+indexation.link_unavailable=The link to these results is unavailable until this process is complete.
+indexation.projects_unavailable=Some projects will be unavailable until this process is complete.
 indexation.progression={0}% complete.
 indexation.progression_with_error={0}% complete with some {link}.
 indexation.progression_with_error.link=tasks failing