From: Viktor Vorona Date: Fri, 9 Aug 2024 17:20:24 +0000 (+0200) Subject: SONAR-22717 Change in calculation badge in projects list X-Git-Tag: 10.7.0.96327~197 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=497b7e3d8826e495865b36860ead63a2a4a5b69b;p=sonarqube.git SONAR-22717 Change in calculation badge in projects list --- diff --git a/server/sonar-web/design-system/src/components/Pill.tsx b/server/sonar-web/design-system/src/components/Pill.tsx index 79d68589126..acd24669bda 100644 --- a/server/sonar-web/design-system/src/components/Pill.tsx +++ b/server/sonar-web/design-system/src/components/Pill.tsx @@ -17,8 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { css } from '@emotion/react'; import styled from '@emotion/styled'; -import { ReactNode } from 'react'; +import { forwardRef, ReactNode } from 'react'; import tw from 'twin.macro'; import { themeColor, themeContrast } from '../helpers/theme'; import { ThemeColors } from '../types/theme'; @@ -43,34 +44,59 @@ interface PillProps { ['aria-label']?: string; children: ReactNode; className?: string; + onClick?: () => void; variant: PillVariant; } -export function Pill({ children, variant, ...rest }: Readonly) { - return ( - - {children} - - ); -} +// eslint-disable-next-line react/display-name +export const Pill = forwardRef>( + ({ children, variant, onClick, ...rest }, ref) => { + return onClick ? ( + + {children} + + ) : ( + + {children} + + ); + }, +); -const StyledPill = styled.span<{ - variant: PillVariant; -}>` +const reusedStyles = css` ${tw`sw-body-xs`}; ${tw`sw-w-fit`}; ${tw`sw-inline-block`}; ${tw`sw-whitespace-nowrap`}; ${tw`sw-px-[8px] sw-py-[2px]`}; ${tw`sw-rounded-pill`}; + border-width: 1px; + + &:empty { + ${tw`sw-hidden`} + } +`; + +const StyledPill = styled.span<{ + variant: PillVariant; +}>` + ${reusedStyles}; background-color: ${({ variant }) => themeColor(variantThemeColors[variant])}; color: ${({ variant }) => themeContrast(variantThemeColors[variant])}; border-style: ${({ variant }) => (variant === 'accent' ? 'hidden' : 'solid')}; border-color: ${({ variant }) => themeColor(variantThemeBorderColors[variant])}; - border-width: 1px; +`; - &:empty { - ${tw`sw-hidden`} - } +const StyledPillButton = styled.button<{ + variant: PillVariant; +}>` + ${reusedStyles}; + + background-color: ${({ variant }) => themeColor(variantThemeColors[variant])}; + color: ${({ variant }) => themeContrast(variantThemeColors[variant])}; + border-style: ${({ variant }) => (variant === 'accent' ? 'hidden' : 'solid')}; + border-color: ${({ variant }) => themeColor(variantThemeBorderColors[variant])}; + + cursor: pointer; `; diff --git a/server/sonar-web/src/main/js/app/components/ChangeInCalculationPill.tsx b/server/sonar-web/src/main/js/app/components/ChangeInCalculationPill.tsx new file mode 100644 index 00000000000..bdf31ea2a25 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/ChangeInCalculationPill.tsx @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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 { Popover } from '@sonarsource/echoes-react'; +import { Pill } from 'design-system'; +import * as React from 'react'; +import DocumentationLink from '../../components/common/DocumentationLink'; +import { DocLink } from '../../helpers/doc-links'; +import { translate } from '../../helpers/l10n'; +import { useIsLegacyCCTMode } from '../../queries/settings'; +import { ComponentQualifier } from '../../sonar-aligned/types/component'; + +interface Props { + qualifier: ComponentQualifier; +} + +export default function ChangeInCalculation({ qualifier }: Readonly) { + const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); + const { data: isLegacy, isLoading } = useIsLegacyCCTMode(); + + if (isLegacy || isLoading) { + return null; + } + + return ( + + {translate('learn_more')} + + } + > + setIsPopoverOpen(!isPopoverOpen)}> + {translate('projects.awaiting_scan')} + + + ); +} diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx index 0c19a7436ee..8bfe5f83eca 100644 --- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx @@ -34,6 +34,7 @@ import { translateWithParameters } from '../../helpers/l10n'; import { HttpStatus } from '../../helpers/request'; import { getPortfolioUrl, getProjectUrl, getPullRequestUrl } from '../../helpers/urls'; import { useBranchesQuery } from '../../queries/branch'; +import { useIsLegacyCCTMode } from '../../queries/settings'; import { ProjectAlmBindingConfigurationErrors } from '../../types/alm-settings'; import { Branch } from '../../types/branch-like'; import { isFile } from '../../types/component'; @@ -72,6 +73,9 @@ function ComponentContainer({ hasFeature }: Readonly fixedInPullRequest ? component : undefined, ); + //prefetch isLegacyCCTMode + useIsLegacyCCTMode(); + const isInTutorials = pathname.includes('tutorials'); const fetchComponent = React.useCallback( diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx index 8a87dad61c3..4f31c39eeb9 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx @@ -51,6 +51,10 @@ jest.mock('../../../api/components', () => ({ .mockResolvedValue({ component: { name: 'component name', analysisDate: '2018-07-30' } }), })); +jest.mock('../../../queries/settings', () => ({ + useIsLegacyCCTMode: jest.fn(), +})); + jest.mock('../../../api/navigation', () => ({ getComponentNavigation: jest.fn().mockResolvedValue({ breadcrumbs: [{ key: 'portfolioKey', name: 'portfolio', qualifier: 'VW' }], diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx index d0ff2f6e32f..2ee4a4d6881 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx @@ -42,6 +42,7 @@ import { formatMeasure } from '~sonar-aligned/helpers/measures'; import { Status } from '~sonar-aligned/types/common'; import { ComponentQualifier } from '~sonar-aligned/types/component'; import { MetricKey, MetricType } from '~sonar-aligned/types/metrics'; +import ChangeInCalculation from '../../../../app/components/ChangeInCalculationPill'; import Favorite from '../../../../components/controls/Favorite'; import Tooltip from '../../../../components/controls/Tooltip'; import DateFromNow from '../../../../components/intl/DateFromNow'; @@ -67,12 +68,18 @@ function renderFirstLine( isNewCode: boolean, ) { const { analysisDate, isFavorite, key, measures, name, qualifier, tags, visibility } = project; + const noSoftwareQualityMetrics = [ + MetricKey.reliability_issues, + MetricKey.maintainability_issues, + MetricKey.security_issues, + ].every((key) => measures[key] === undefined); + const noRatingMetrics = [ + MetricKey.reliability_rating_new, + MetricKey.sqale_rating_new, + MetricKey.security_rating_new, + ].every((key) => measures[key] === undefined); const awaitingScan = - [ - MetricKey.reliability_issues, - MetricKey.maintainability_issues, - MetricKey.security_issues, - ].every((key) => measures[key] === undefined) && + (noSoftwareQualityMetrics || noRatingMetrics) && !isNewCode && !isEmpty(analysisDate) && measures.ncloc !== undefined; @@ -124,13 +131,7 @@ function renderFirstLine( {awaitingScan && !isNewCode && !isEmpty(analysisDate) && measures.ncloc !== undefined && ( - - - - {translate('projects.awaiting_scan')} - - - + )} diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx index f444465e33b..0f0b31ef799 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx @@ -31,6 +31,7 @@ import RatingComponent from '../../../../app/components/metrics/RatingComponent' import { duplicationRatingConverter } from '../../../../components/measure/utils'; import { translate } from '../../../../helpers/l10n'; import { isDefined } from '../../../../helpers/types'; +import { useIsLegacyCCTMode } from '../../../../queries/settings'; import { Dict } from '../../../../types/types'; import ProjectCardMeasure from './ProjectCardMeasure'; @@ -115,7 +116,7 @@ function renderDuplication(props: ProjectCardMeasuresProps) { ); } -function renderRatings(props: ProjectCardMeasuresProps) { +function renderRatings(props: ProjectCardMeasuresProps, isLegacy: boolean) { const { isNewCode, measures, componentKey } = props; const measuresByCodeLeak = isNewCode @@ -125,27 +126,27 @@ function renderRatings(props: ProjectCardMeasuresProps) { iconLabel: translate(`metric.${MetricKey.security_issues}.short_name`), noShrink: true, metricKey: - measures[MetricKey.security_issues] !== undefined - ? MetricKey.security_issues - : MetricKey.vulnerabilities, + isLegacy || measures[MetricKey.security_issues] === undefined + ? MetricKey.vulnerabilities + : MetricKey.security_issues, metricRatingKey: MetricKey.security_rating, metricType: MetricType.ShortInteger, }, { iconLabel: translate(`metric.${MetricKey.reliability_issues}.short_name`), metricKey: - measures[MetricKey.reliability_issues] !== undefined - ? MetricKey.reliability_issues - : MetricKey.bugs, + isLegacy || measures[MetricKey.reliability_issues] === undefined + ? MetricKey.bugs + : MetricKey.reliability_issues, metricRatingKey: MetricKey.reliability_rating, metricType: MetricType.ShortInteger, }, { iconLabel: translate(`metric.${MetricKey.maintainability_issues}.short_name`), metricKey: - measures[MetricKey.maintainability_issues] !== undefined - ? MetricKey.maintainability_issues - : MetricKey.code_smells, + isLegacy || measures[MetricKey.maintainability_issues] === undefined + ? MetricKey.code_smells + : MetricKey.maintainability_issues, metricRatingKey: MetricKey.sqale_rating, metricType: MetricType.ShortInteger, }, @@ -195,6 +196,7 @@ function renderRatings(props: ProjectCardMeasuresProps) { export default function ProjectCardMeasures(props: ProjectCardMeasuresProps) { const { isNewCode, measures, componentQualifier } = props; + const { data: isLegacy } = useIsLegacyCCTMode(); const { ncloc } = measures; @@ -210,7 +212,7 @@ export default function ProjectCardMeasures(props: ProjectCardMeasuresProps) { const measureList = [ renderNewIssues(props), - ...renderRatings(props), + ...renderRatings(props, !!isLegacy), renderCoverage(props), renderDuplication(props), ].filter(isDefined); diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx index 3e22f1b4f35..3fb06f240d5 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx @@ -17,13 +17,15 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { screen } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React from 'react'; import { ComponentQualifier, Visibility } from '~sonar-aligned/types/component'; import { MetricKey } from '~sonar-aligned/types/metrics'; import { MeasuresServiceMock } from '../../../../../api/mocks/MeasuresServiceMock'; import SettingsServiceMock from '../../../../../api/mocks/SettingsServiceMock'; -import { mockCurrentUser, mockLoggedInUser } from '../../../../../helpers/testMocks'; +import { mockComponent } from '../../../../../helpers/mocks/component'; +import { mockCurrentUser, mockLoggedInUser, mockMeasure } from '../../../../../helpers/testMocks'; import { renderComponent } from '../../../../../helpers/testReactTestingUtils'; import { CurrentUser } from '../../../../../types/users'; import { Project } from '../../../types'; @@ -35,6 +37,7 @@ const MEASURES = { [MetricKey.reliability_rating]: '1.0', [MetricKey.security_rating]: '1.0', [MetricKey.sqale_rating]: '1.0', + [MetricKey.security_review_rating]: '1.0', [MetricKey.new_bugs]: '12', }; @@ -94,97 +97,247 @@ it('should display applications', () => { expect(screen.getAllByText('qualifier.APP')).toHaveLength(2); }); -it('should not display awaiting analysis badge and do not display old measures', () => { +it('should display 3 projects', () => { renderProjectCard({ ...PROJECT, - measures: { - ...MEASURES, - [MetricKey.security_issues]: JSON.stringify({ LOW: 0, MEDIUM: 0, HIGH: 1, total: 1 }), - [MetricKey.reliability_issues]: JSON.stringify({ LOW: 0, MEDIUM: 2, HIGH: 0, total: 2 }), - [MetricKey.maintainability_issues]: JSON.stringify({ LOW: 3, MEDIUM: 0, HIGH: 0, total: 3 }), - [MetricKey.code_smells]: '4', - [MetricKey.bugs]: '5', - [MetricKey.vulnerabilities]: '6', - }, + qualifier: ComponentQualifier.Application, + measures: { ...MEASURES, projects: '3' }, }); - expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument(); - expect(screen.getByText('1')).toBeInTheDocument(); - expect(screen.getByText('2')).toBeInTheDocument(); - expect(screen.getByText('3')).toBeInTheDocument(); - expect(screen.queryByText('4')).not.toBeInTheDocument(); - expect(screen.queryByText('5')).not.toBeInTheDocument(); - expect(screen.queryByText('6')).not.toBeInTheDocument(); + expect(screen.getByText(/x_projects_.3/)).toBeInTheDocument(); }); -it('should display awaiting analysis badge and show the old measures', async () => { - renderProjectCard({ - ...PROJECT, - measures: { - ...MEASURES, - [MetricKey.code_smells]: '4', - [MetricKey.bugs]: '5', - [MetricKey.vulnerabilities]: '6', - }, +describe('upgrade scenario (awaiting scan)', () => { + const oldRatings = { + [MetricKey.reliability_rating]: mockMeasure({ + metric: MetricKey.reliability_rating, + value: '1', + }), + [MetricKey.sqale_rating]: mockMeasure({ + metric: MetricKey.sqale_rating, + value: '1', + }), + [MetricKey.security_rating]: mockMeasure({ + metric: MetricKey.security_rating, + value: '1', + }), + [MetricKey.security_review_rating]: mockMeasure({ + metric: MetricKey.security_review_rating, + value: '1', + }), + }; + + const newRatings = { + [MetricKey.reliability_rating_new]: mockMeasure({ + metric: MetricKey.reliability_rating_new, + value: '2', + }), + [MetricKey.sqale_rating_new]: mockMeasure({ + metric: MetricKey.sqale_rating_new, + value: '2', + }), + [MetricKey.security_rating_new]: mockMeasure({ + metric: MetricKey.security_rating_new, + value: '2', + }), + [MetricKey.security_review_rating_new]: mockMeasure({ + metric: MetricKey.security_review_rating_new, + value: '2', + }), + }; + beforeEach(() => { + measuresHandler.setComponents({ + component: mockComponent({ key: PROJECT.key }), + ancestors: [], + children: [], + }); + measuresHandler.registerComponentMeasures({ + [PROJECT.key]: oldRatings, + }); + }); + it('should not display awaiting analysis badge and do not display old measures', async () => { + measuresHandler.registerComponentMeasures({ + [PROJECT.key]: newRatings, + }); + renderProjectCard({ + ...PROJECT, + measures: { + ...MEASURES, + [MetricKey.security_issues]: JSON.stringify({ LOW: 0, MEDIUM: 0, HIGH: 1, total: 1 }), + [MetricKey.reliability_issues]: JSON.stringify({ LOW: 0, MEDIUM: 2, HIGH: 0, total: 2 }), + [MetricKey.maintainability_issues]: JSON.stringify({ + LOW: 3, + MEDIUM: 0, + HIGH: 0, + total: 3, + }), + [MetricKey.sqale_rating_new]: '2', + [MetricKey.reliability_rating_new]: '2', + [MetricKey.security_rating_new]: '2', + [MetricKey.security_review_rating_new]: '2', + [MetricKey.code_smells]: '4', + [MetricKey.bugs]: '5', + [MetricKey.vulnerabilities]: '6', + }, + }); + expect(screen.getByText('1')).toBeInTheDocument(); + expect(screen.getByText('2')).toBeInTheDocument(); + expect(screen.getByText('3')).toBeInTheDocument(); + await waitFor(() => expect(screen.getAllByText('B')).toHaveLength(4)); + expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument(); + expect(screen.queryByText('4')).not.toBeInTheDocument(); + expect(screen.queryByText('5')).not.toBeInTheDocument(); + expect(screen.queryByText('6')).not.toBeInTheDocument(); + expect(screen.queryByText('A')).not.toBeInTheDocument(); }); - expect(screen.getByText('projects.awaiting_scan')).toBeInTheDocument(); - await expect(screen.getByText('projects.awaiting_scan')).toHaveATooltipWithContent( - 'projects.awaiting_scan.description.TRK', - ); - expect(screen.getByText('4')).toBeInTheDocument(); - expect(screen.getByText('5')).toBeInTheDocument(); - expect(screen.getByText('6')).toBeInTheDocument(); -}); -it('should display awaiting analysis badge and show the old measures for Application', async () => { - renderProjectCard({ - ...PROJECT, - qualifier: ComponentQualifier.Application, - measures: { - ...MEASURES, - [MetricKey.code_smells]: '4', - [MetricKey.bugs]: '5', - [MetricKey.vulnerabilities]: '6', - }, + it('should display awaiting analysis badge and show the old measures', async () => { + const user = userEvent.setup(); + renderProjectCard({ + ...PROJECT, + measures: { + ...MEASURES, + [MetricKey.code_smells]: '4', + [MetricKey.bugs]: '5', + [MetricKey.vulnerabilities]: '6', + }, + }); + expect(await screen.findByText('projects.awaiting_scan')).toBeInTheDocument(); + await user.click(screen.getByText('projects.awaiting_scan')); + await expect(screen.getByText('projects.awaiting_scan.description.TRK')).toBeInTheDocument(); + expect(screen.getByText('4')).toBeInTheDocument(); + expect(screen.getByText('5')).toBeInTheDocument(); + expect(screen.getByText('6')).toBeInTheDocument(); + expect(screen.getAllByText('A')).toHaveLength(4); }); - expect(screen.getByText('projects.awaiting_scan')).toBeInTheDocument(); - await expect(screen.getByText('projects.awaiting_scan')).toHaveATooltipWithContent( - 'projects.awaiting_scan.description.APP', - ); - expect(screen.getByText('4')).toBeInTheDocument(); - expect(screen.getByText('5')).toBeInTheDocument(); - expect(screen.getByText('6')).toBeInTheDocument(); -}); -it('should not display awaiting analysis badge if project is not analyzed', () => { - renderProjectCard({ - ...PROJECT, - analysisDate: undefined, + it('should display awaiting analysis badge, show new software qualities, but old ratings', async () => { + renderProjectCard({ + ...PROJECT, + measures: { + ...MEASURES, + [MetricKey.security_issues]: JSON.stringify({ LOW: 0, MEDIUM: 0, HIGH: 1, total: 1 }), + [MetricKey.reliability_issues]: JSON.stringify({ LOW: 0, MEDIUM: 2, HIGH: 0, total: 2 }), + [MetricKey.maintainability_issues]: JSON.stringify({ + LOW: 3, + MEDIUM: 0, + HIGH: 0, + total: 3, + }), + [MetricKey.code_smells]: '4', + [MetricKey.bugs]: '5', + [MetricKey.vulnerabilities]: '6', + }, + }); + expect(await screen.findByText('projects.awaiting_scan')).toBeInTheDocument(); + expect(screen.getByText('1')).toBeInTheDocument(); + expect(screen.getByText('2')).toBeInTheDocument(); + expect(screen.getByText('3')).toBeInTheDocument(); + expect(screen.queryByText('4')).not.toBeInTheDocument(); + expect(screen.queryByText('5')).not.toBeInTheDocument(); + expect(screen.queryByText('6')).not.toBeInTheDocument(); + await waitFor(() => expect(screen.getAllByText('A')).toHaveLength(4)); }); - expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument(); -}); -it('should not display awaiting analysis badge if project does not have lines of code', () => { - renderProjectCard({ - ...PROJECT, - measures: { - ...(({ [MetricKey.ncloc]: _, ...rest }) => rest)(MEASURES), - }, + it('should display awaiting analysis badge and show the old measures for Application', async () => { + const user = userEvent.setup(); + renderProjectCard({ + ...PROJECT, + qualifier: ComponentQualifier.Application, + measures: { + ...MEASURES, + [MetricKey.code_smells]: '4', + [MetricKey.bugs]: '5', + [MetricKey.vulnerabilities]: '6', + }, + }); + expect(await screen.findByText('projects.awaiting_scan')).toBeInTheDocument(); + await user.click(screen.getByText('projects.awaiting_scan')); + await expect(screen.getByText('projects.awaiting_scan.description.APP')).toBeInTheDocument(); + expect(screen.getByText('4')).toBeInTheDocument(); + expect(screen.getByText('5')).toBeInTheDocument(); + expect(screen.getByText('6')).toBeInTheDocument(); }); - expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument(); -}); -it('should not display awaiting analysis badge if it is a new code filter', () => { - renderProjectCard(PROJECT, undefined, 'leak'); - expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument(); -}); + it('should not display awaiting analysis badge if project is not analyzed', () => { + renderProjectCard({ + ...PROJECT, + analysisDate: undefined, + }); + expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument(); + }); -it('should display 3 aplication', () => { - renderProjectCard({ - ...PROJECT, - qualifier: ComponentQualifier.Application, - measures: { ...MEASURES, projects: '3' }, + it('should not display awaiting analysis badge if project does not have lines of code', () => { + renderProjectCard({ + ...PROJECT, + measures: { + ...(({ [MetricKey.ncloc]: _, ...rest }) => rest)(MEASURES), + }, + }); + expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument(); + }); + + it('should not display awaiting analysis badge if it is a new code filter', () => { + renderProjectCard(PROJECT, undefined, 'leak'); + expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument(); + }); + + it('should not display awaiting analysis badge if legacy mode is enabled', async () => { + settingsHandler.set('sonar.legacy.ratings.mode.enabled', 'true'); + renderProjectCard({ + ...PROJECT, + measures: { + ...MEASURES, + [MetricKey.code_smells]: '4', + [MetricKey.bugs]: '5', + [MetricKey.vulnerabilities]: '6', + }, + }); + expect(screen.getByText('4')).toBeInTheDocument(); + expect(screen.getByText('5')).toBeInTheDocument(); + expect(screen.getByText('6')).toBeInTheDocument(); + await waitFor(() => expect(screen.getAllByText('A')).toHaveLength(4)); + expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument(); + }); + + it('should not display new values if legacy mode is enabled', async () => { + settingsHandler.set('sonar.legacy.ratings.mode.enabled', 'true'); + measuresHandler.registerComponentMeasures({ + [PROJECT.key]: { + ...newRatings, + ...oldRatings, + }, + }); + renderProjectCard({ + ...PROJECT, + measures: { + ...MEASURES, + [MetricKey.security_issues]: JSON.stringify({ LOW: 0, MEDIUM: 0, HIGH: 1, total: 1 }), + [MetricKey.reliability_issues]: JSON.stringify({ LOW: 0, MEDIUM: 2, HIGH: 0, total: 2 }), + [MetricKey.maintainability_issues]: JSON.stringify({ + LOW: 3, + MEDIUM: 0, + HIGH: 0, + total: 3, + }), + [MetricKey.sqale_rating_new]: '2', + [MetricKey.reliability_rating_new]: '2', + [MetricKey.security_rating_new]: '2', + [MetricKey.security_review_rating_new]: '2', + [MetricKey.code_smells]: '4', + [MetricKey.bugs]: '5', + [MetricKey.vulnerabilities]: '6', + }, + }); + expect(await screen.findByText('4')).toBeInTheDocument(); + expect(screen.getByText('5')).toBeInTheDocument(); + expect(screen.getByText('6')).toBeInTheDocument(); + expect(screen.queryByText('1')).not.toBeInTheDocument(); + expect(screen.queryByText('2')).not.toBeInTheDocument(); + expect(screen.queryByText('3')).not.toBeInTheDocument(); + await waitFor(() => expect(screen.getAllByText('A')).toHaveLength(4)); + expect(screen.queryByText('B')).not.toBeInTheDocument(); + expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument(); }); - expect(screen.getByText(/x_projects_.3/)).toBeInTheDocument(); }); function renderProjectCard(project: Project, user: CurrentUser = USER_LOGGED_OUT, type?: string) { diff --git a/server/sonar-web/src/main/js/queries/settings.ts b/server/sonar-web/src/main/js/queries/settings.ts index 2afb4624194..f6534ee40b8 100644 --- a/server/sonar-web/src/main/js/queries/settings.ts +++ b/server/sonar-web/src/main/js/queries/settings.ts @@ -49,7 +49,7 @@ export const useGetValueQuery = createQueryHook( export const useIsLegacyCCTMode = () => { return useGetValueQuery( { key: 'sonar.legacy.ratings.mode.enabled' }, - { staleTime: Infinity, select: (data) => !!data }, + { staleTime: Infinity, select: (data) => data?.value === '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 84d4aaab267..feb5feb18eb 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1357,8 +1357,10 @@ projects.sort.-size=by size (biggest first) projects.show_more=Show more projects projects.security_hotspots_reviewed=Hotspots Reviewed projects.awaiting_scan=Change in Calculation -projects.awaiting_scan.description.TRK=The way Security, Reliability, and Maintainability counts are calculated has changed. The values currently displayed may change after the next analysis. -projects.awaiting_scan.description.APP=The way Security, Reliability, and Maintainability counts are calculated has changed. The values currently displayed may change after all projects in this application have been analyzed. +projects.awaiting_scan.title=Values will change after the next analysis +projects.awaiting_scan.description.TRK=The way in which security, reliability, maintainability, and security review counts and ratings are calculated has changed. The values currently displayed will change after the next analysis. +projects.awaiting_scan.description.APP=The way in which security, reliability, maintainability, and security review counts and ratings are calculated has changed. The values currently displayed will change after all projects in this application have been analyzed. +projects.awaiting_scan.description.VW=The way in which security, reliability, maintainability, and security review counts and ratings are calculated has changed. The values currently displayed will change after all projects in this portfolio have been analyzed. #------------------------------------------------------------------------------