From: Ismail Cherri Date: Wed, 9 Oct 2024 13:20:46 +0000 (+0200) Subject: SONAR-23194 Project overview adopts MQR mode X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=3fedddcd21bfff1bff8e2eb25f34f110b803aa48;p=sonarqube.git SONAR-23194 Project overview adopts MQR mode --- diff --git a/server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx index 9e6b76baf78..92c9bd59815 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx @@ -17,7 +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 { BasicSeparator, Card, Spinner } from 'design-system'; +import { Spinner } from '@sonarsource/echoes-react'; +import { BasicSeparator, Card } from 'design-system'; import * as React from 'react'; import { MetricKey } from '~sonar-aligned/types/metrics'; import GraphsHeader from '../../../components/activity-graph/GraphsHeader'; @@ -32,6 +33,7 @@ import ActivityLink from '../../../components/common/ActivityLink'; import { parseDate } from '../../../helpers/dates'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { localizeMetric } from '../../../helpers/measures'; +import { useIsLegacyCCTMode } from '../../../queries/settings'; import { BranchLike } from '../../../types/branch-like'; import { Analysis as AnalysisType, @@ -70,7 +72,8 @@ export function ActivityPanel(props: ActivityPanelProps) { metrics, } = props; - const displayedMetrics = getDisplayedHistoryMetrics(graph, []); + const { data: isLegacy = false } = useIsLegacyCCTMode(); + const displayedMetrics = getDisplayedHistoryMetrics(graph, [], isLegacy); const series = generateSeries(measuresHistory, graph, metrics, displayedMetrics); const graphs = splitSeriesInGraphs(series, MAX_GRAPH_NB, MAX_SERIES_PER_GRAPH); let shownLeakPeriodDate; @@ -134,7 +137,7 @@ export function ActivityPanel(props: ActivityPanelProps) { - + {displayedAnalyses.length === 0 ? (

{translate('no_results')}

) : ( diff --git a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx index e7834509ce4..3a159842e5f 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx @@ -45,6 +45,7 @@ import { useApplicationQualityGateStatus, useProjectQualityGateStatus, } from '../../../queries/quality-gates'; +import { useIsLegacyCCTMode } from '../../../queries/settings'; import { ApplicationPeriod } from '../../../types/application'; import { Branch, BranchLike } from '../../../types/branch-like'; import { Analysis, GraphType, MeasureHistory } from '../../../types/project-activity'; @@ -68,6 +69,7 @@ const FROM_DATE = toISO8601WithOffsetString(new Date().setFullYear(new Date().ge export default function BranchOverview(props: Readonly) { const { component, branch, branchesEnabled } = props; + const { data: isLegacy = false } = useIsLegacyCCTMode(); const { graph: initialGraph } = getActivityGraph( BRANCH_OVERVIEW_ACTIVITY_GRAPH, props.component.key, @@ -281,7 +283,7 @@ export default function BranchOverview(props: Readonly) { }; const loadHistoryMeasures = React.useCallback(() => { - const graphMetrics = getHistoryMetrics(graph, []); + const graphMetrics = getHistoryMetrics(graph, [], isLegacy); const metrics = uniq([...HISTORY_METRICS_LIST, ...graphMetrics]); return getAllTimeMachineData({ @@ -375,11 +377,10 @@ export default function BranchOverview(props: Readonly) { }, [branch, loadHistory, loadStatus]); const projectIsEmpty = - loadingStatus === false && - (measures === undefined || - measures.find((measure) => - ([MetricKey.lines, MetricKey.new_lines] as string[]).includes(measure.metric.key), - ) === undefined); + !loadingStatus && + measures?.find((measure) => + ([MetricKey.lines, MetricKey.new_lines] as string[]).includes(measure.metric.key), + ) === undefined; return ( ) { const { analyses, appLeak, diff --git a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx index 0f4f9c97e80..3d381c9a414 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx @@ -18,8 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import styled from '@emotion/styled'; -import { LinkHighlight, LinkStandalone, Tooltip } from '@sonarsource/echoes-react'; -import { Badge, TextBold, TextSubdued, themeColor } from 'design-system'; +import { LinkHighlight, LinkStandalone, Text, Tooltip } from '@sonarsource/echoes-react'; +import { Badge, themeColor } from 'design-system'; import * as React from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { formatMeasure } from '~sonar-aligned/helpers/measures'; @@ -87,7 +87,8 @@ export function SoftwareImpactMeasureCard(props: Readonly
- {intl.formatMessage({ id: `software_quality.${softwareQuality}` })} + {!isLegacy && intl.formatMessage({ id: `software_quality.${softwareQuality}` })} + {alternativeMeasure && isLegacy && alternativeMeasure.metric.name} {failed && ( @@ -99,7 +100,7 @@ export function SoftwareImpactMeasureCard(props: Readonly
{count ? ( - + ) : ( - + - )} - + {intl.formatMessage({ id: 'overview.measures.software_impact.total_open_issues' })} - +
@@ -142,7 +143,7 @@ export function SoftwareImpactMeasureCard(props: Readonly) { const { ratingMetricKey, componentKey, softwareQuality, branch } = props; + const { data: isLegacy = false } = useIsLegacyCCTMode(); const intl = useIntl(); const getSoftwareImpactRatingTooltip = useCallback( - (rating: RatingEnum) => { + (rating: RatingEnum, value: string | undefined) => { if (rating === undefined) { return null; } + if (isLegacy && value !== undefined) { + return ; + } + function ratingToWorseSeverity(rating: string): SoftwareImpactSeverity { return ( { diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/ActivityPanel-it.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/ActivityPanel-it.tsx index 8b49e156574..b250935267b 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/ActivityPanel-it.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/ActivityPanel-it.tsx @@ -18,7 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { screen } from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup'; import * as React from 'react'; +import SettingsServiceMock from '../../../../api/mocks/SettingsServiceMock'; import { mockComponent } from '../../../../helpers/mocks/component'; import { mockAnalysis, @@ -38,10 +40,16 @@ import { DefinitionChangeType, ProjectAnalysisEventCategory, } from '../../../../types/project-activity'; +import { SettingsKey } from '../../../../types/settings'; import ActivityPanel, { ActivityPanelProps } from '../ActivityPanel'; -it('should render correctly', async () => { - const user = userEvent.setup(); +const settingsHandler = new SettingsServiceMock(); + +afterEach(() => { + settingsHandler.reset(); +}); + +async function expectGraphs(user: UserEvent) { renderActivityPanel(); expect(await screen.findAllByText('metric.level.ERROR')).toHaveLength(2); @@ -52,7 +60,7 @@ it('should render correctly', async () => { expect(screen.getByText('event.sqUpgrade10.2')).toBeInTheDocument(); // Checking measures variations - expect(screen.getAllByText(/% project_activity\.graphs\.coverage$/)).toHaveLength(3); + expect(await screen.findAllByText(/% project_activity\.graphs\.coverage$/)).toHaveLength(3); expect(screen.getAllByText(/% project_activity\.graphs\.duplications$/)).toHaveLength(3); // Analysis 1 (latest) expect(screen.getByText(/^-5 project_activity\.graphs\.issues$/)).toBeInTheDocument(); @@ -76,6 +84,10 @@ it('should render correctly', async () => { ), ).toBeInTheDocument(); + return user; +} + +async function expectQualityGate(user: UserEvent) { await user.click( screen.getByRole('link', { name: 'quality_profiles.page_title_changelog_x.QP-test: 1 new rule, 2 modified rules, and 3 removed rules', @@ -83,10 +95,60 @@ it('should render correctly', async () => { ); expect(await screen.findByText('QP-test java')).toBeInTheDocument(); +} + +it('should render correctly', async () => { + const user = userEvent.setup(); + await expectGraphs(user); + + expect(screen.queryByText('metric.code_smells.name')).not.toBeInTheDocument(); + expect(screen.queryByText('metric.vulnerabilities.name')).not.toBeInTheDocument(); + expect(screen.queryByText('metric.bugs.name')).not.toBeInTheDocument(); + + await expectQualityGate(user); +}); + +it('should render correctly for legacy mode', async () => { + settingsHandler.set(SettingsKey.MQRMode, 'false'); + const user = userEvent.setup(); + await expectGraphs(user); + + expect(screen.getByText('metric.code_smells.name')).toBeInTheDocument(); + expect(screen.getByText('metric.vulnerabilities.name')).toBeInTheDocument(); + expect(screen.getByText('metric.bugs.name')).toBeInTheDocument(); + + await expectQualityGate(user); }); function renderActivityPanel() { const mockedMeasureHistory = [ + mockMeasureHistory({ + metric: MetricKey.vulnerabilities, + history: [ + mockHistoryItem({ date: parseDate('2018-10-27T10:21:15+0200'), value: '200' }), + mockHistoryItem({ date: parseDate('2018-10-27T12:21:15+0200'), value: '200' }), + mockHistoryItem({ date: parseDate('2020-10-27T16:33:50+0200'), value: '100' }), + mockHistoryItem({ date: parseDate('2020-10-27T18:33:50+0200'), value: '95' }), + ], + }), + mockMeasureHistory({ + metric: MetricKey.code_smells, + history: [ + mockHistoryItem({ date: parseDate('2018-10-27T10:21:15+0200'), value: '200' }), + mockHistoryItem({ date: parseDate('2018-10-27T12:21:15+0200'), value: '200' }), + mockHistoryItem({ date: parseDate('2020-10-27T16:33:50+0200'), value: '100' }), + mockHistoryItem({ date: parseDate('2020-10-27T18:33:50+0200'), value: '95' }), + ], + }), + mockMeasureHistory({ + metric: MetricKey.bugs, + history: [ + mockHistoryItem({ date: parseDate('2018-10-27T10:21:15+0200'), value: '200' }), + mockHistoryItem({ date: parseDate('2018-10-27T12:21:15+0200'), value: '200' }), + mockHistoryItem({ date: parseDate('2020-10-27T16:33:50+0200'), value: '100' }), + mockHistoryItem({ date: parseDate('2020-10-27T18:33:50+0200'), value: '95' }), + ], + }), mockMeasureHistory({ metric: MetricKey.violations, history: [ diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx index 78d22c7c9ec..35d1150b9cd 100644 --- a/server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx @@ -37,7 +37,7 @@ interface Props { selectedMetrics?: string[]; } -export default function GraphsHeader(props: Props) { +export default function GraphsHeader(props: Readonly) { const { className, graph, diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/utils-test.ts b/server/sonar-web/src/main/js/components/activity-graph/__tests__/utils-test.ts index 4cfdf423674..a289f2bae52 100644 --- a/server/sonar-web/src/main/js/components/activity-graph/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/utils-test.ts @@ -105,6 +105,13 @@ describe('getDisplayedHistoryMetrics', () => { customMetrics, ); }); + it('should return Legacy graphs', () => { + expect(utils.getDisplayedHistoryMetrics(GraphType.issues, [], true)).toEqual([ + MetricKey.bugs, + MetricKey.code_smells, + MetricKey.vulnerabilities, + ]); + }); }); describe('getHistoryMetrics', () => { @@ -123,6 +130,16 @@ describe('getHistoryMetrics', () => { ]); expect(utils.getHistoryMetrics(GraphType.custom, customMetrics)).toEqual(customMetrics); }); + it('should return legacy metrics', () => { + expect(utils.getHistoryMetrics(utils.DEFAULT_GRAPH, [], true)).toEqual([ + MetricKey.bugs, + MetricKey.code_smells, + MetricKey.vulnerabilities, + MetricKey.reliability_rating, + MetricKey.security_rating, + MetricKey.sqale_rating, + ]); + }); }); describe('hasHistoryData', () => { diff --git a/server/sonar-web/src/main/js/components/activity-graph/utils.ts b/server/sonar-web/src/main/js/components/activity-graph/utils.ts index 54dbc17e42c..b1768446382 100644 --- a/server/sonar-web/src/main/js/components/activity-graph/utils.ts +++ b/server/sonar-web/src/main/js/components/activity-graph/utils.ts @@ -37,6 +37,11 @@ const GRAPHS_METRICS_DISPLAYED: Dict = { [GraphType.duplications]: [MetricKey.ncloc, MetricKey.duplicated_lines], }; +const LEGACY_GRAPHS_METRICS_DISPLAYED: Dict = { + ...GRAPHS_METRICS_DISPLAYED, + [GraphType.issues]: [MetricKey.bugs, MetricKey.code_smells, MetricKey.vulnerabilities], +}; + const GRAPHS_METRICS: Dict = { [GraphType.issues]: GRAPHS_METRICS_DISPLAYED[GraphType.issues].concat([ MetricKey.reliability_rating, @@ -50,6 +55,15 @@ const GRAPHS_METRICS: Dict = { ], }; +const LEGACY_GRAPHS_METRICS: Dict = { + ...GRAPHS_METRICS, + [GraphType.issues]: LEGACY_GRAPHS_METRICS_DISPLAYED[GraphType.issues].concat([ + MetricKey.reliability_rating, + MetricKey.security_rating, + MetricKey.sqale_rating, + ]), +}; + export const LINE_CHART_DASHES = [0, 3, 7]; export function isCustomGraph(graph: GraphType) { @@ -74,12 +88,23 @@ export function getSeriesMetricType(series: Serie[]) { return series.length > 0 ? series[0].type : MetricType.Integer; } -export function getDisplayedHistoryMetrics(graph: GraphType, customMetrics: string[]) { - return isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS_DISPLAYED[graph]; +export function getDisplayedHistoryMetrics( + graph: GraphType, + customMetrics: string[], + isLegacy = false, +) { + if (isCustomGraph(graph)) { + return customMetrics; + } + + return isLegacy ? LEGACY_GRAPHS_METRICS_DISPLAYED[graph] : GRAPHS_METRICS_DISPLAYED[graph]; } -export function getHistoryMetrics(graph: GraphType, customMetrics: string[]) { - return isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS[graph]; +export function getHistoryMetrics(graph: GraphType, customMetrics: string[], isLegacy = false) { + if (isCustomGraph(graph)) { + return customMetrics; + } + return isLegacy ? LEGACY_GRAPHS_METRICS[graph] : GRAPHS_METRICS[graph]; } export function hasHistoryDataValue(series: Serie[]) { diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/telemetry/TelemetryMQRModePropertyProvider.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/telemetry/TelemetryMQRModePropertyProvider.java index ed1e6ab34fe..b827a02f278 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/telemetry/TelemetryMQRModePropertyProvider.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/telemetry/TelemetryMQRModePropertyProvider.java @@ -62,6 +62,6 @@ public class TelemetryMQRModePropertyProvider implements TelemetryDataProvider getValue() { PropertyDto property = dbClient.propertiesDao().selectGlobalProperty(MULTI_QUALITY_MODE_ENABLED); - return Optional.of(property != null && Boolean.parseBoolean(property.getValue())); + return property == null ? Optional.of(true) : Optional.of(Boolean.valueOf(property.getValue())); } } diff --git a/sonar-core/src/main/java/org/sonar/core/config/MQRModeProperties.java b/sonar-core/src/main/java/org/sonar/core/config/MQRModeProperties.java index aee71806cf8..536aeae5cbb 100644 --- a/sonar-core/src/main/java/org/sonar/core/config/MQRModeProperties.java +++ b/sonar-core/src/main/java/org/sonar/core/config/MQRModeProperties.java @@ -36,7 +36,7 @@ public final class MQRModeProperties { public static List all() { return Collections.singletonList( PropertyDefinition.builder(MULTI_QUALITY_MODE_ENABLED) - .defaultValue(Boolean.FALSE.toString()) + .defaultValue(Boolean.TRUE.toString()) .name("Enable Multi-Quality Rule Mode") .description("Aims to more accurately represent the impact software has on all software qualities. " + "It does this by mapping rules to every software quality they can impact, not just the one " +