From: Ismail Cherri Date: Fri, 11 Oct 2024 15:20:35 +0000 (+0200) Subject: SONAR-23195 MQR mode for Code and Measures pages X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=239d7e3a64eac3ae6c0894171565edf9ad0a2909;p=sonarqube.git SONAR-23195 MQR mode for Code and Measures pages --- diff --git a/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts b/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts index 422930bdf77..27560be451e 100644 --- a/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts +++ b/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts @@ -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'), diff --git a/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx b/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx index 39188e22ddf..c180e6fe1a7 100644 --- a/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx @@ -17,15 +17,8 @@ * 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) { return ( - - {!canBrowseAllChildProjects && isPortfolio && ( {translate('code_viewer.not_all_measures_are_shown')} @@ -154,7 +145,7 @@ export default function CodeAppRenderer(props: Readonly) { className="sw-ml-2" overlay={translate('code_viewer.not_all_measures_are_shown.help')} > - + )} @@ -184,12 +175,12 @@ export default function CodeAppRenderer(props: Readonly) { {!hasComponents && sourceViewer === undefined && (
- + {translate( 'code_viewer.no_source_code_displayed_due_to_empty_analysis', component.qualifier, )} - +
)} diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx index 061b1946736..7f77c3dda31 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx @@ -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; diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.tsx index e50a6a6e898..b1ee5f40500 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.tsx @@ -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', diff --git a/server/sonar-web/src/main/js/apps/component-measures/__tests__/ComponentMeasures-it.tsx b/server/sonar-web/src/main/js/apps/component-measures/__tests__/ComponentMeasures-it.tsx index c3eecceb27b..1a0ccf0c4d1 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/__tests__/ComponentMeasures-it.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/__tests__/ComponentMeasures-it.tsx @@ -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', ); diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx index a2909d2340e..1271c05af55 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx @@ -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) { 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) { key={item.metric.key} componentKey={componentKey} measure={item} - name={getMetricSubnavigationName(item.metric, translateMetric)} + name={getMetricSubnavigationName(item.metric, translateMetric, false, isLegacy)} onChange={onChange} selected={selected} /> diff --git a/server/sonar-web/src/main/js/apps/component-measures/utils.ts b/server/sonar-web/src/main/js/apps/component-measures/utils.ts index e47f720209e..8a70d05b716 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/utils.ts +++ b/server/sonar-web/src/main/js/apps/component-measures/utils.ts @@ -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, diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx index 92bd714356c..2915ce50b29 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx @@ -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) { 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) { 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) { : { types: getIssueTypeBySoftwareQuality(quality) }), }); - const qualityTitle = intl.formatMessage({ id: `metric.${metric}.short_name` }); + const qualityTitle = intl.formatMessage({ + id: `metric.${isLegacy ? deprecatedMetric : metric}.short_name`, + }); return (
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx index d11faf063d4..bcc29f329b9 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx @@ -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 () => { diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx index f8edb8c7191..a7d77852d73 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx @@ -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 ; } 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]}`, diff --git a/server/sonar-webserver/src/test/java/org/sonar/server/platform/telemetry/TelemetryMQRModePropertyProviderTest.java b/server/sonar-webserver/src/test/java/org/sonar/server/platform/telemetry/TelemetryMQRModePropertyProviderTest.java index 66a93111d8c..df2c384af17 100644 --- a/server/sonar-webserver/src/test/java/org/sonar/server/platform/telemetry/TelemetryMQRModePropertyProviderTest.java +++ b/server/sonar-webserver/src/test/java/org/sonar/server/platform/telemetry/TelemetryMQRModePropertyProviderTest.java @@ -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) ); } diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index d58203f803b..7a2e6d94f73 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -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