]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23195 MQR mode for Code and Measures pages
authorIsmail Cherri <ismail.cherri@sonarsource.com>
Fri, 11 Oct 2024 15:20:35 +0000 (17:20 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 16 Oct 2024 20:03:00 +0000 (20:03 +0000)
12 files changed:
server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts
server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx
server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx
server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.tsx
server/sonar-web/src/main/js/apps/component-measures/__tests__/ComponentMeasures-it.tsx
server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx
server/sonar-web/src/main/js/apps/component-measures/utils.ts
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx
server/sonar-webserver/src/test/java/org/sonar/server/platform/telemetry/TelemetryMQRModePropertyProviderTest.java
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 422930bdf774c5861fb8a6c93b638c6241e08a91..27560be451ebc22800c24a2da0062f607bdfff10 100644 (file)
@@ -566,7 +566,7 @@ function getPageObject(user: UserEvent) {
     previewCode: byText('numpy', { exact: false }),
     previewIssueUnderline: byTestId('hljs-sonar-underline'),
     previewIssueIndicator: byRole('button', {
-      name: 'source_viewer.issues_on_line.multiple_issues_same_category.true.1.issue.clean_code_attribute_category.responsible',
+      name: 'source_viewer.issues_on_line.multiple_issues_same_category.true.1.issue.type.code_smell.issue.clean_code_attribute_category.responsible',
     }),
     issuesViewPage: byText('/project/issues?open=some-issue&id=foo'),
     previewMarkdown: byText('Learning a cosine with keras'),
index 39188e22ddf5cc2fea4bc1dbf861692f8d057313..c180e6fe1a7d408a6bd6e2f17084cf83f1ba57d9 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 { Spinner } from '@sonarsource/echoes-react';
-import {
-  Card,
-  FlagMessage,
-  HelperHintIcon,
-  KeyboardHint,
-  LargeCenteredLayout,
-  LightLabel,
-} from 'design-system';
+import { IconQuestionMark, Spinner, Text } from '@sonarsource/echoes-react';
+import { Card, FlagMessage, KeyboardHint, LargeCenteredLayout } from 'design-system';
 import { difference, intersection } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
@@ -144,9 +137,7 @@ export default function CodeAppRenderer(props: Readonly<Props>) {
   return (
     <LargeCenteredLayout className="sw-py-8 sw-typo-lg" id="code-page">
       <Helmet defer={false} title={sourceViewer !== undefined ? sourceViewer.name : defaultTitle} />
-
       <A11ySkipTarget anchor="code_main" />
-
       {!canBrowseAllChildProjects && isPortfolio && (
         <FlagMessage variant="warning" className="it__portfolio_warning sw-mb-4">
           {translate('code_viewer.not_all_measures_are_shown')}
@@ -154,7 +145,7 @@ export default function CodeAppRenderer(props: Readonly<Props>) {
             className="sw-ml-2"
             overlay={translate('code_viewer.not_all_measures_are_shown.help')}
           >
-            <HelperHintIcon />
+            <IconQuestionMark />
           </HelpTooltip>
         </FlagMessage>
       )}
@@ -184,12 +175,12 @@ export default function CodeAppRenderer(props: Readonly<Props>) {
 
             {!hasComponents && sourceViewer === undefined && (
               <div className="sw-flex sw-align-center sw-flex-col sw-fixed sw-top-1/2">
-                <LightLabel>
+                <Text isSubdued>
                   {translate(
                     'code_viewer.no_source_code_displayed_due_to_empty_analysis',
                     component.qualifier,
                   )}
-                </LightLabel>
+                </Text>
               </div>
             )}
 
index 061b1946736111ac97ddbc2b8006967e99093b80..7f77c3dda31a563f3e65d190939d3757b25052eb 100644 (file)
@@ -34,6 +34,7 @@ import {
   areCCTMeasuresComputed as areCCTMeasuresComputedFn,
   isDiffMetric,
 } from '../../../helpers/measures';
+import { useIsLegacyCCTMode } from '../../../queries/settings';
 import { BranchLike } from '../../../types/branch-like';
 import { isApplication, isProject } from '../../../types/component';
 import { Metric, ComponentMeasure as TypeComponentMeasure } from '../../../types/types';
@@ -47,13 +48,14 @@ interface Props {
 export default function ComponentMeasure(props: Props) {
   const { component, metric, branchLike } = props;
   const isProjectLike = isProject(component.qualifier) || isApplication(component.qualifier);
+  const { data: isLegacy } = useIsLegacyCCTMode();
   const isReleasability = metric.key === MetricKey.releasability_rating;
 
   let finalMetricKey = isProjectLike && isReleasability ? MetricKey.alert_status : metric.key;
   const finalMetricType = isProjectLike && isReleasability ? MetricType.Level : metric.type;
 
-  const areCCTMeasasuresComputed = areCCTMeasuresComputedFn(component.measures);
-  finalMetricKey = areCCTMeasasuresComputed
+  const areCCTMeasuresComputed = !isLegacy && areCCTMeasuresComputedFn(component.measures);
+  finalMetricKey = areCCTMeasuresComputed
     ? (OLD_TO_NEW_TAXONOMY_METRICS_MAP[finalMetricKey as MetricKey] ?? finalMetricKey)
     : finalMetricKey;
 
index e50a6a6e8988070dd1fda19c4c5fbb756c181941..b1ee5f405003a88fac80ae6d4662d6ef8f5b4b12 100644 (file)
@@ -26,6 +26,7 @@ import {
   OLD_TO_NEW_TAXONOMY_METRICS_MAP,
 } from '../../../helpers/constants';
 import { translate } from '../../../helpers/l10n';
+import { useIsLegacyCCTMode } from '../../../queries/settings';
 import { ComponentMeasure } from '../../../types/types';
 
 interface ComponentsHeaderProps {
@@ -46,6 +47,7 @@ const SHORT_NAME_METRICS = [
 
 export default function ComponentsHeader(props: ComponentsHeaderProps) {
   const { baseComponent, canBePinned = true, metrics, rootComponent, showAnalysisDate } = props;
+  const { data: isLegacy = false } = useIsLegacyCCTMode();
   const isPortfolio = isPortfolioLike(rootComponent.qualifier);
   let columns: string[] = [];
   let Cell: typeof NumericalCell;
@@ -66,7 +68,7 @@ export default function ComponentsHeader(props: ComponentsHeaderProps) {
     Cell = RatingCell;
   } else {
     columns = metrics.map((m: MetricKey) => {
-      const metric = OLD_TO_NEW_TAXONOMY_METRICS_MAP[m] ?? m;
+      const metric = isLegacy ? m : (OLD_TO_NEW_TAXONOMY_METRICS_MAP[m] ?? m);
 
       return translate(
         'metric',
index c3eecceb27b3aa92f18cc8735c07b9b1aa55eb87..1a0ccf0c4d19025448583889caed6f715345b871 100644 (file)
@@ -164,11 +164,11 @@ describe('rendering', () => {
     // Check one of the domains.
     await user.click(ui.maintainabilityDomainBtn.get());
     [
-      'component_measures.metric.new_code_smells.name 9',
+      'component_measures.leak_awaiting_analysis.name 9',
       'Added Technical Debt work_duration.x_minutes.1',
       'Technical Debt Ratio on New Code 1.0%',
       'Maintainability Rating on New Code metric.has_rating_X.E metric.sqale_rating.tooltip.E.0.0%',
-      'component_measures.metric.code_smells.name 9',
+      'component_measures.awaiting_analysis.name 9',
       'Technical Debt work_duration.x_minutes.1',
       'Technical Debt Ratio 1.0%',
       'Maintainability Rating metric.has_rating_X.E metric.sqale_rating.tooltip.E.0.0%',
@@ -592,7 +592,7 @@ describe('redirects', () => {
     const { ui } = getPageObject();
     renderMeasuresApp('component_measures/metric/bugs?id=foo');
     await ui.appLoaded();
-    expect(ui.measureLink('component_measures.metric.bugs.name 0').get()).toHaveAttribute(
+    expect(ui.measureLink('component_measures.awaiting_analysis.name 0').get()).toHaveAttribute(
       'aria-current',
       'true',
     );
index a2909d2340ed19c57a68f114b5e9f6e74957963b..1271c05af55c06ba1e808eed5be976d209739708 100644 (file)
@@ -32,6 +32,7 @@ import {
   hasMessage,
   translate,
 } from '../../../helpers/l10n';
+import { useIsLegacyCCTMode } from '../../../queries/settings';
 import { MeasureEnhanced } from '../../../types/types';
 import { useBubbleChartMetrics } from '../hooks';
 import {
@@ -54,6 +55,7 @@ interface Props {
 
 export default function DomainSubnavigation(props: Readonly<Props>) {
   const { componentKey, domain, onChange, open, selected, showFullMeasures, measures } = props;
+  const { data: isLegacy = false } = useIsLegacyCCTMode();
   const helperMessageKey = `component_measures.domain_subnavigation.${domain.name}.help`;
   const helper = hasMessage(helperMessageKey) ? translate(helperMessageKey) : undefined;
   const items = addMeasureCategories(domain.name, domain.measures);
@@ -112,7 +114,7 @@ export default function DomainSubnavigation(props: Readonly<Props>) {
             key={item.metric.key}
             componentKey={componentKey}
             measure={item}
-            name={getMetricSubnavigationName(item.metric, translateMetric)}
+            name={getMetricSubnavigationName(item.metric, translateMetric, false, isLegacy)}
             onChange={onChange}
             selected={selected}
           />
index e47f720209e2e2b767eb59174dfb11e98189b344..8a70d05b716abd732dc1e6dc05eb8cb12c467d3c 100644 (file)
@@ -159,7 +159,16 @@ export function getMetricSubnavigationName(
   metric: Metric,
   translateFn: (metric: Metric) => string,
   isDiff = false,
+  isLegacy = false,
 ) {
+  // MQR mode and old taxonomy metrics, we return "Issues" for them anyway
+  if (!isLegacy && OLD_TAXONOMY_METRICS.includes(metric.key as MetricKey)) {
+    return translate('component_measures.awaiting_analysis.name');
+  }
+  if (!isLegacy && LEAK_OLD_TAXONOMY_METRICS.includes(metric.key as MetricKey)) {
+    return translate('component_measures.leak_awaiting_analysis.name');
+  }
+
   if (
     [
       ...LEAK_CCT_SOFTWARE_QUALITY_METRICS,
index 92bd714356c5ffbf4d111f86ad2a48ebf7e76782..2915ce50b294950f8754575909465b5da4078d9d 100644 (file)
@@ -58,6 +58,7 @@ import { omitNil } from '../../helpers/request';
 import { getBaseUrl } from '../../helpers/system';
 import { isDefined } from '../../helpers/types';
 import { getBranchLikeUrl, getCodeUrl } from '../../helpers/urls';
+import { useIsLegacyCCTMode } from '../../queries/settings';
 import type { BranchLike } from '../../types/branch-like';
 import { IssueType } from '../../types/issues';
 import type { Measure, SourceViewerFile } from '../../types/types';
@@ -75,6 +76,7 @@ interface Props {
 
 export default function SourceViewerHeader(props: Readonly<Props>) {
   const intl = useIntl();
+  const { data: isLegacy = false } = useIsLegacyCCTMode();
 
   const { showMeasures, branchLike, hidePinOption, openComponent, componentMeasures } = props;
   const { key, measures, path, project, projectName, q } = props.sourceViewerFile;
@@ -85,7 +87,7 @@ export default function SourceViewerHeader(props: Readonly<Props>) {
   const rawSourcesLink = `${getBaseUrl()}/api/sources/raw?${query}`;
 
   const renderIssueMeasures = () => {
-    const areCCTMeasuresComputed = areCCTMeasuresComputedFn(componentMeasures);
+    const areCCTMeasuresComputed = !isLegacy && areCCTMeasuresComputedFn(componentMeasures);
 
     return (
       componentMeasures &&
@@ -112,7 +114,9 @@ export default function SourceViewerHeader(props: Readonly<Props>) {
                   : { types: getIssueTypeBySoftwareQuality(quality) }),
               });
 
-              const qualityTitle = intl.formatMessage({ id: `metric.${metric}.short_name` });
+              const qualityTitle = intl.formatMessage({
+                id: `metric.${isLegacy ? deprecatedMetric : metric}.short_name`,
+              });
 
               return (
                 <div className="sw-flex sw-flex-col sw-gap-1" key={quality}>
index d11faf063d4c063bebaed2d6ad0c854cee36ac95..bcc29f329b98c482536a924ebb1054c851d24c60 100644 (file)
@@ -31,6 +31,7 @@ import { isDiffMetric } from '../../../helpers/measures';
 import { HttpStatus } from '../../../helpers/request';
 import { mockIssue, mockLoggedInUser, mockMeasure } from '../../../helpers/testMocks';
 import { renderComponent } from '../../../helpers/testReactTestingUtils';
+import { SettingsKey } from '../../../types/settings';
 import { RestUserDetailed } from '../../../types/users';
 import SourceViewer, { Props } from '../SourceViewer';
 import loadIssues from '../helpers/loadIssues';
@@ -223,7 +224,11 @@ it('should show SCM information', async () => {
   expect(within(row).queryByRole('button')).not.toBeInTheDocument();
 });
 
-it('should show issue indicator', async () => {
+it.each([
+  ['MQR mode', 'true', ''],
+  ['Legacy mode', 'false', '.legacy'],
+])('should show issue indicator in %s', async (_, mode, translationKey) => {
+  settingsHandler.set(SettingsKey.MQRMode, mode);
   jest.mocked(loadIssues).mockResolvedValueOnce([
     mockIssue(false, {
       key: 'first-issue',
@@ -251,11 +256,12 @@ it('should show issue indicator', async () => {
   const issueRow = within(row);
   expect(issueRow.getByText('2')).toBeInTheDocument();
 
-  await user.click(
-    issueRow.getByRole('button', {
-      name: 'source_viewer.issues_on_line.multiple_issues_same_category.true.2.issue.clean_code_attribute_category.responsible',
-    }),
-  );
+  const issueIndicator = await issueRow.findByRole('button', {
+    name: `source_viewer.issues_on_line.multiple_issues_same_category${translationKey}.true.2.issue.type.bug.plural.issue.clean_code_attribute_category.responsible`,
+  });
+  await user.click(issueIndicator);
+
+  expect(await screen.findByRole('tooltip')).toBeInTheDocument();
 });
 
 it('should show coverage information', async () => {
index f8edb8c71913a0f3c70c2c94de12536162db23cf..a7d77852d736c810bf767e2b8cbbc13b87bd77a9 100644 (file)
@@ -22,6 +22,7 @@ import { uniq } from 'lodash';
 import * as React from 'react';
 import { useIntl } from 'react-intl';
 import Tooltip from '../../../components/controls/Tooltip';
+import { useIsLegacyCCTMode } from '../../../queries/settings';
 import { Issue, SourceLine } from '../../../types/types';
 
 const MOUSE_LEAVE_DELAY = 0.25;
@@ -38,25 +39,34 @@ export function LineIssuesIndicator(props: LineIssuesIndicatorProps) {
   const { issues, issuesOpen, line, as = 'td' } = props;
   const hasIssues = issues.length > 0;
   const intl = useIntl();
+  const { data: isLegacy } = useIsLegacyCCTMode();
 
   if (!hasIssues) {
     return <LineMeta />;
   }
 
   const issueAttributeCategories = uniq(issues.map((issue) => issue.cleanCodeAttributeCategory));
+  const issueTypes = uniq(issues.map((issue) => issue.type));
   let tooltipContent;
 
-  if (issueAttributeCategories.length > 1) {
+  if (isLegacy ? issueTypes.length > 1 : issueAttributeCategories.length > 1) {
     tooltipContent = intl.formatMessage(
       { id: 'source_viewer.issues_on_line.multiple_issues' },
       { show: !issuesOpen },
     );
   } else {
     tooltipContent = intl.formatMessage(
-      { id: 'source_viewer.issues_on_line.multiple_issues_same_category' },
+      {
+        id: `source_viewer.issues_on_line.multiple_issues_same_category${isLegacy ? '.legacy' : ''}`,
+      },
       {
         show: !issuesOpen,
         count: issues.length,
+        issueType: intl
+          .formatMessage({
+            id: `issue.type.${issueTypes[0]}${issues.length > 1 ? '.plural' : ''}`,
+          })
+          .toLowerCase(),
         category: intl
           .formatMessage({
             id: `issue.clean_code_attribute_category.${issueAttributeCategories[0]}`,
index 66a93111d8c9a6dacc246eef34affdcc8366f7c4..df2c384af17f95b9a44b82a7c62eba2b0b0d41d8 100644 (file)
@@ -64,7 +64,7 @@ class TelemetryMQRModePropertyProviderTest {
     return Stream.of(
       Arguments.of(true, true),
       Arguments.of(false, false),
-      Arguments.of(null, false)
+      Arguments.of(null, true)
     );
   }
 
index d58203f803b2bc88cb43088553d712fa2eb2be7a..7a2e6d94f73c0c117a70bba62c936f0f1fff2517 100644 (file)
@@ -3096,6 +3096,7 @@ metric.branch_coverage.description=Condition coverage
 metric.branch_coverage.name=Condition Coverage
 metric.bugs.description=Bugs
 metric.bugs.name=Bugs
+metric.bugs.short_name=Bugs
 metric.ca.description=Afferent couplings
 metric.ca.name=Afferent Couplings
 metric.ce.description=Efferent couplings
@@ -3108,6 +3109,7 @@ metric.class_complexity_distribution.description=Classes distribution /complexit
 metric.class_complexity_distribution.name=Class Distribution / Complexity
 metric.code_smells.description=Code Smells
 metric.code_smells.name=Code Smells
+metric.code_smells.short_name=Code Smells
 metric.cognitive_complexity.description=Cognitive complexity
 metric.cognitive_complexity.name=Cognitive Complexity
 metric.commented_out_code_lines.description=Commented lines of code
@@ -3594,6 +3596,7 @@ metric.violations.name=Issues
 metric.violations.short_name=Issues
 metric.vulnerabilities.description=Vulnerabilities
 metric.vulnerabilities.name=Vulnerabilities
+metric.vulnerabilities.short_name=Vulnerabilities
 metric.wont_fix_issues.description=Won't fix issues
 metric.wont_fix_issues.name=Won't Fix Issues
 metric.accepted_issues.description=Accepted issues
@@ -3893,6 +3896,7 @@ source_viewer.tooltip.scm.revision=Revision
 
 source_viewer.issues_on_line.multiple_issues={show, select, true {Show} other {Hide}} multiple issues on this line
 source_viewer.issues_on_line.multiple_issues_same_category={show, select, true {Show} other {Hide}} {count} {category} {count, plural, one {issue} other {issues}} on this line
+source_viewer.issues_on_line.multiple_issues_same_category.legacy={show, select, true {Show} other {Hide}} {count} {issueType} on this line
 
 source_viewer.load_more_code=Load More Code
 source_viewer.loading_more_code=Loading More Code...
@@ -4506,6 +4510,8 @@ component_measures.navigation=Measures navigation
 component_measures.skip_to_navigation=Skip to measure navigation
 component_measures.see_metric_history=See history
 component_measures.leak_legend.new_code=New Code:
+component_measures.awaiting_analysis.name=Issues
+component_measures.leak_awaiting_analysis.name=Issues
 
 component_measures.overview.project_overview.subnavigation=Project Overview
 component_measures.overview.project_overview.title=Risk
@@ -4534,31 +4540,30 @@ component_measures.not_all_measures_are_shown.help=You do not have access to all
 
 component_measures.metric.new_security_issues.name=Issues
 component_measures.metric.new_security_issues.detailed_name=New Issues
-component_measures.metric.new_vulnerabilities.name=Issues
-component_measures.metric.new_vulnerabilities.detailed_name=New Issues
+component_measures.metric.new_vulnerabilities.name=Vulnerabilities
+component_measures.metric.new_vulnerabilities.detailed_name=New Vulnerabilities
 component_measures.metric.new_reliability_issues.name=Issues
 component_measures.metric.new_reliability_issues.detailed_name=New Issues
 component_measures.metric.new_maintainability_issues.name=Issues
 component_measures.metric.new_maintainability_issues.detailed_name=New Issues
-component_measures.metric.new_code_smells.name=Issues
-component_measures.metric.new_code_smells.detailed_name=New Issues
+component_measures.metric.new_code_smells.name=Code Smells
+component_measures.metric.new_code_smells.detailed_name=New Code Smells
 component_measures.metric.new_violations.name=Open Issues
 component_measures.metric.new_violations.detailed_name=New Open Issues
 component_measures.metric.new_accepted_issues.name=Accepted Issues
 component_measures.metric.new_accepted_issues.detailed_name=New Accepted Issues
-component_measures.metric.new_bugs.name=Issues
-component_measures.metric.new_bugs.detailed_name=New Issues
+component_measures.metric.new_bugs.name=Bugs
+component_measures.metric.new_bugs.detailed_name=New Bugs
 component_measures.metric.security_issues.name=Issues
-component_measures.metric.vulnerabilities.name=Issues
+component_measures.metric.vulnerabilities.name=Vulnerabilities
 component_measures.metric.reliability_issues.name=Issues
-component_measures.metric.bugs.name=Issues
+component_measures.metric.bugs.name=Bugs
 component_measures.metric.maintainability_issues.name=Issues
-component_measures.metric.code_smells.name=Issues
+component_measures.metric.code_smells.name=Code Smells
 component_measures.metric.violations.name=Open Issues
 component_measures.metric.accepted_issues.name=Accepted Issues
 component_measures.metric.confirmed_issues.name=Confirmed Issues
 component_measures.metric.false_positive_issues.name=False Positive Issues
-
 #------------------------------------------------------------------------------
 #
 # DOCS