aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorViktor Vorona <viktor.vorona@sonarsource.com>2024-08-14 14:03:11 +0200
committersonartech <sonartech@sonarsource.com>2024-08-26 20:03:07 +0000
commitac8fc4c3d9ff6d8b366f13c159e8fad51d58843c (patch)
tree602b6b8305e2f5cb28f2655f0a10d81246a09109 /server
parentfc0739fd4eaf7a467de77773be76ddbb5f8040ef (diff)
downloadsonarqube-ac8fc4c3d9ff6d8b366f13c159e8fad51d58843c.tar.gz
sonarqube-ac8fc4c3d9ff6d8b366f13c159e8fad51d58843c.zip
SONAR-22710 Projects facet
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/design-system/src/components/__tests__/__snapshots__/HotspotRating-test.tsx.snap2
-rw-r--r--server/sonar-web/design-system/src/sonar-aligned/components/MetricsRatingBadge.tsx16
-rw-r--r--server/sonar-web/design-system/src/theme/light.ts9
-rw-r--r--server/sonar-web/src/main/js/app/components/metrics/RatingComponent.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts59
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx25
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx65
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx34
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx34
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx34
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/RangeFacetBase.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/RatingFacet.tsx45
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/RatingFilter.tsx (renamed from server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx)39
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx35
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx35
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/SecurityReviewFilter.tsx94
-rw-r--r--server/sonar-web/src/main/js/apps/projects/query.ts49
-rw-r--r--server/sonar-web/src/main/js/apps/projects/utils.ts117
-rw-r--r--server/sonar-web/src/main/js/queries/settings.ts4
-rw-r--r--server/sonar-web/src/main/js/types/settings.ts1
23 files changed, 343 insertions, 402 deletions
diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/HotspotRating-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/HotspotRating-test.tsx.snap
index 741beaababd..900f1f226d9 100644
--- a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/HotspotRating-test.tsx.snap
+++ b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/HotspotRating-test.tsx.snap
@@ -74,7 +74,7 @@ exports[`should render HotspotRating with MEDIUM rating 1`] = `
<circle
cx="8"
cy="8"
- fill="rgb(255,214,175)"
+ fill="rgb(254,205,202)"
r="7"
/>
<path
diff --git a/server/sonar-web/design-system/src/sonar-aligned/components/MetricsRatingBadge.tsx b/server/sonar-web/design-system/src/sonar-aligned/components/MetricsRatingBadge.tsx
index bf1555b1b93..9276c9b286a 100644
--- a/server/sonar-web/design-system/src/sonar-aligned/components/MetricsRatingBadge.tsx
+++ b/server/sonar-web/design-system/src/sonar-aligned/components/MetricsRatingBadge.tsx
@@ -26,6 +26,7 @@ import { RatingLabel } from '../types/measures';
type sizeType = keyof typeof SIZE_MAPPING;
interface Props extends React.AriaAttributes {
className?: string;
+ isLegacy?: boolean;
label?: string;
rating?: RatingLabel;
size?: sizeType;
@@ -40,7 +41,10 @@ const SIZE_MAPPING = {
};
export const MetricsRatingBadge = forwardRef<HTMLDivElement, Props>(
- ({ className, size = 'sm', label, rating, ...ariaAttrs }: Readonly<Props>, ref) => {
+ (
+ { className, size = 'sm', isLegacy = true, label, rating, ...ariaAttrs }: Readonly<Props>,
+ ref,
+ ) => {
if (!rating) {
return (
<StyledNoRatingBadge
@@ -58,6 +62,7 @@ export const MetricsRatingBadge = forwardRef<HTMLDivElement, Props>(
<MetricsRatingBadgeStyled
aria-label={label}
className={className}
+ isLegacy={isLegacy}
rating={rating}
ref={ref}
size={SIZE_MAPPING[size]}
@@ -91,12 +96,17 @@ const getFontSize = (size: string) => {
}
};
-const MetricsRatingBadgeStyled = styled.div<{ rating: RatingLabel; size: string }>`
+const MetricsRatingBadgeStyled = styled.div<{
+ isLegacy: boolean;
+ rating: RatingLabel;
+ size: string;
+}>`
width: ${getProp('size')};
height: ${getProp('size')};
color: ${({ rating }) => themeContrast(`rating.${rating}`)};
font-size: ${({ size }) => getFontSize(size)};
- background-color: ${({ rating }) => themeColor(`rating.${rating}`)};
+ background-color: ${({ rating, isLegacy }) =>
+ themeColor(`rating.${isLegacy ? 'legacy.' : ''}${rating}`)};
user-select: none;
display: inline-flex;
diff --git a/server/sonar-web/design-system/src/theme/light.ts b/server/sonar-web/design-system/src/theme/light.ts
index 098ca5ce763..dce350b51b6 100644
--- a/server/sonar-web/design-system/src/theme/light.ts
+++ b/server/sonar-web/design-system/src/theme/light.ts
@@ -465,10 +465,17 @@ export const lightTheme = {
sizeIndicator: COLORS.blue[500],
// rating colors
+ 'rating.legacy.A': COLORS.green[200],
+ 'rating.legacy.B': COLORS.yellowGreen[200],
+ 'rating.legacy.C': COLORS.yellow[200],
+ 'rating.legacy.D': COLORS.orange[200],
+ 'rating.legacy.E': COLORS.red[200],
+
+ // rating colors
'rating.A': COLORS.green[200],
'rating.B': COLORS.yellowGreen[200],
'rating.C': COLORS.yellow[200],
- 'rating.D': COLORS.orange[200],
+ 'rating.D': COLORS.red[200],
'rating.E': COLORS.red[200],
// rating donut outside circle indicators
diff --git a/server/sonar-web/src/main/js/app/components/metrics/RatingComponent.tsx b/server/sonar-web/src/main/js/app/components/metrics/RatingComponent.tsx
index d5e2a61644a..03de2c241b6 100644
--- a/server/sonar-web/src/main/js/app/components/metrics/RatingComponent.tsx
+++ b/server/sonar-web/src/main/js/app/components/metrics/RatingComponent.tsx
@@ -29,6 +29,8 @@ import { useMeasureQuery } from '../../../queries/measures';
import { useIsLegacyCCTMode } from '../../../queries/settings';
import { BranchLike } from '../../../types/branch-like';
+type SizeType = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+
interface Props {
branchLike?: BranchLike;
className?: string;
@@ -36,7 +38,7 @@ interface Props {
getLabel?: (rating: RatingEnum) => string;
getTooltip?: (rating: RatingEnum) => React.ReactNode;
ratingMetric: MetricKey;
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+ size?: SizeType;
}
type RatingMetricKeys =
@@ -88,6 +90,7 @@ export default function RatingComponent(props: Readonly<Props>) {
const badge = (
<MetricsRatingBadge
label={getLabel ? getLabel(rating) : value ?? '—'}
+ isLegacy={measure?.metric ? !isNewRatingMetric(measure.metric as MetricKey) : false}
rating={rating}
size={size}
className={className}
diff --git a/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts
index 9728540e2e8..f245ee977fe 100644
--- a/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts
+++ b/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts
@@ -84,7 +84,39 @@ describe('formatDuration', () => {
describe('fetchProjects', () => {
it('correctly converts the passed arguments to the desired query format', async () => {
- await utils.fetchProjects({ isFavorite: true, query: {} });
+ await utils.fetchProjects({ isFavorite: true, query: {}, isLegacy: true });
+
+ expect(searchProjects).toHaveBeenCalledWith({
+ f: 'analysisDate,leakPeriodDate',
+ facets: utils.LEGACY_FACETS.join(),
+ filter: 'isFavorite',
+ p: undefined,
+ ps: 50,
+ });
+
+ await utils.fetchProjects({
+ isFavorite: false,
+ pageIndex: 3,
+ query: {
+ view: 'leak',
+ new_reliability: 6,
+ incorrect_property: 'should not appear in post data',
+ search: 'foo',
+ },
+ isLegacy: true,
+ });
+
+ expect(searchProjects).toHaveBeenCalledWith({
+ f: 'analysisDate,leakPeriodDate',
+ facets: utils.LEGACY_LEAK_FACETS.join(),
+ filter: 'new_reliability_rating = 6 and query = "foo"',
+ p: 3,
+ ps: 50,
+ });
+ });
+
+ it('correctly converts the passed arguments to the desired query format for non legacy', async () => {
+ await utils.fetchProjects({ isFavorite: true, query: {}, isLegacy: false });
expect(searchProjects).toHaveBeenCalledWith({
f: 'analysisDate,leakPeriodDate',
@@ -103,12 +135,13 @@ describe('fetchProjects', () => {
incorrect_property: 'should not appear in post data',
search: 'foo',
},
+ isLegacy: false,
});
expect(searchProjects).toHaveBeenCalledWith({
f: 'analysisDate,leakPeriodDate',
facets: utils.LEAK_FACETS.join(),
- filter: 'new_reliability_rating = 6 and query = "foo"',
+ filter: 'new_software_quality_reliability_rating = 6 and query = "foo"',
p: 3,
ps: 50,
});
@@ -132,7 +165,7 @@ describe('fetchProjects', () => {
paging: { total: 2 },
});
- await utils.fetchProjects({ isFavorite: true, query: {} }).then((r) => {
+ await utils.fetchProjects({ isFavorite: true, query: {}, isLegacy: true }).then((r) => {
expect(r).toEqual({
facets: {
new_coverage: { NO_DATA: 0 },
@@ -166,8 +199,22 @@ describe('defineMetrics', () => {
describe('convertToSorting', () => {
it('handles asc and desc sort', () => {
- expect(utils.convertToSorting({ sort: '-size' })).toStrictEqual({ asc: false, s: 'ncloc' });
- expect(utils.convertToSorting({})).toStrictEqual({ s: undefined });
- expect(utils.convertToSorting({ sort: 'search' })).toStrictEqual({ s: 'query' });
+ expect(utils.convertToSorting({ sort: '-size' }, true)).toStrictEqual({
+ asc: false,
+ s: 'ncloc',
+ });
+ expect(utils.convertToSorting({}, true)).toStrictEqual({ s: undefined });
+ expect(utils.convertToSorting({ sort: 'search' }, true)).toStrictEqual({ s: 'query' });
+ });
+
+ it('handles sort for legacy and non legacy queries', () => {
+ expect(utils.convertToSorting({ sort: '-reliability' }, true)).toStrictEqual({
+ asc: false,
+ s: 'reliability_rating',
+ });
+ expect(utils.convertToSorting({ sort: '-reliability' }, false)).toStrictEqual({
+ asc: false,
+ s: 'software_quality_reliability_rating',
+ });
});
});
diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
index f7fe5e311bd..b366a9ca956 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
@@ -44,6 +44,7 @@ import handleRequiredAuthentication from '../../../helpers/handleRequiredAuthent
import { translate } from '../../../helpers/l10n';
import { get, save } from '../../../helpers/storage';
import { isDefined } from '../../../helpers/types';
+import { useIsLegacyCCTMode } from '../../../queries/settings';
import { AppState } from '../../../types/appstate';
import { CurrentUser, isLoggedIn } from '../../../types/users';
import { Query, hasFilterParams, parseUrlQuery } from '../query';
@@ -58,6 +59,7 @@ interface Props {
appState: AppState;
currentUser: CurrentUser;
isFavorite: boolean;
+ isLegacy: boolean;
location: Location;
router: Router;
}
@@ -104,13 +106,13 @@ export class AllProjects extends React.PureComponent<Props, State> {
}
fetchMoreProjects = () => {
- const { isFavorite } = this.props;
+ const { isFavorite, isLegacy } = this.props;
const { pageIndex, projects, query } = this.state;
if (pageIndex && projects && Object.keys(query).length !== 0) {
this.setState({ loading: true });
- fetchProjects({ isFavorite, query, pageIndex: pageIndex + 1 }).then((response) => {
+ fetchProjects({ isFavorite, query, pageIndex: pageIndex + 1, isLegacy }).then((response) => {
if (this.mounted) {
this.setState({
loading: false,
@@ -173,14 +175,14 @@ export class AllProjects extends React.PureComponent<Props, State> {
};
handleQueryChange() {
- const { isFavorite } = this.props;
+ const { isFavorite, isLegacy } = this.props;
const queryRaw = this.props.location.query;
const query = parseUrlQuery(queryRaw);
this.setState({ loading: true, query });
- fetchProjects({ isFavorite, query }).then((response) => {
+ fetchProjects({ isFavorite, query, isLegacy }).then((response) => {
// We ignore the request if the query changed since the time it was initiated
// If that happened, another query will be initiated anyway
if (this.mounted && queryRaw === this.props.location.query) {
@@ -202,10 +204,10 @@ export class AllProjects extends React.PureComponent<Props, State> {
};
loadSearchResultCount = (property: string, values: string[]) => {
- const { isFavorite } = this.props;
+ const { isFavorite, isLegacy } = this.props;
const { query = {} } = this.state;
- const data = convertToQueryData({ ...query, [property]: values }, isFavorite, {
+ const data = convertToQueryData({ ...query, [property]: values }, isFavorite, isLegacy, {
ps: 1,
facets: property,
});
@@ -347,9 +349,10 @@ function getStorageOptions() {
return options;
}
-function SetSearchParamsWrapper(props: Readonly<Props>) {
+function AllProjectsWrapper(props: Readonly<Omit<Props, 'isLegacy'>>) {
const [searchParams, setSearchParams] = useSearchParams();
const savedOptions = getStorageOptions();
+ const { data: isLegacy, isLoading } = useIsLegacyCCTMode();
React.useEffect(
() => {
@@ -366,10 +369,14 @@ function SetSearchParamsWrapper(props: Readonly<Props>) {
],
);
- return <AllProjects {...props} />;
+ return (
+ <Spinner isLoading={isLoading}>
+ <AllProjects {...props} isLegacy={isLegacy ?? false} />
+ </Spinner>
+ );
}
-export default withRouter(withCurrentUserContext(withAppStateContext(SetSearchParamsWrapper)));
+export default withRouter(withCurrentUserContext(withAppStateContext(AllProjectsWrapper)));
const StyledWrapper = styled.div`
background-color: ${themeColor('backgroundPrimary')};
diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx b/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx
index 2aed6d9bfae..3398f06718f 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx
@@ -27,18 +27,12 @@ import { Dict } from '../../../types/types';
import CoverageFilter from '../filters/CoverageFilter';
import DuplicationsFilter from '../filters/DuplicationsFilter';
import LanguagesFilter from '../filters/LanguagesFilter';
-import MaintainabilityFilter from '../filters/MaintainabilityFilter';
import NewCoverageFilter from '../filters/NewCoverageFilter';
import NewDuplicationsFilter from '../filters/NewDuplicationsFilter';
import NewLinesFilter from '../filters/NewLinesFilter';
-import NewMaintainabilityFilter from '../filters/NewMaintainabilityFilter';
-import NewReliabilityFilter from '../filters/NewReliabilityFilter';
-import NewSecurityFilter from '../filters/NewSecurityFilter';
import QualifierFacet from '../filters/QualifierFilter';
import QualityGateFacet from '../filters/QualityGateFilter';
-import ReliabilityFilter from '../filters/ReliabilityFilter';
-import SecurityFilter from '../filters/SecurityFilter';
-import SecurityReviewFilter from '../filters/SecurityReviewFilter';
+import RatingFilter from '../filters/RatingFilter';
import SizeFilter from '../filters/SizeFilter';
import TagsFacet from '../filters/TagsFilter';
import { hasFilterParams } from '../query';
@@ -107,34 +101,38 @@ export default function PageSidebar(props: PageSidebarProps) {
{!isLeakView && (
<>
- <ReliabilityFilter
+ <RatingFilter
{...facetProps}
- facet={getFacet(facets, 'reliability')}
- value={query.reliability}
+ facets={facets}
+ property="security"
+ value={query.security}
/>
<BasicSeparator className="sw-my-4" />
- <SecurityFilter
+ <RatingFilter
{...facetProps}
- facet={getFacet(facets, 'security')}
- value={query.security}
+ facets={facets}
+ property="reliability"
+ value={query.reliability}
/>
<BasicSeparator className="sw-my-4" />
- <SecurityReviewFilter
+ <RatingFilter
{...facetProps}
- facet={getFacet(facets, 'security_review')}
- value={query.security_review_rating}
+ facets={facets}
+ property="maintainability"
+ value={query.maintainability}
/>
<BasicSeparator className="sw-my-4" />
- <MaintainabilityFilter
+ <RatingFilter
{...facetProps}
- facet={getFacet(facets, 'maintainability')}
- value={query.maintainability}
+ facets={facets}
+ property="security_review"
+ value={query.security_review_rating}
/>
<BasicSeparator className="sw-my-4" />
@@ -160,35 +158,38 @@ export default function PageSidebar(props: PageSidebarProps) {
)}
{isLeakView && (
<>
- <NewReliabilityFilter
+ <RatingFilter
{...facetProps}
- facet={getFacet(facets, 'new_reliability')}
- value={query.new_reliability}
+ facets={facets}
+ property="new_security"
+ value={query.new_security}
/>
<BasicSeparator className="sw-my-4" />
- <NewSecurityFilter
+ <RatingFilter
{...facetProps}
- facet={getFacet(facets, 'new_security')}
- value={query.new_security}
+ facets={facets}
+ property="new_reliability"
+ value={query.new_reliability}
/>
<BasicSeparator className="sw-my-4" />
- <SecurityReviewFilter
+ <RatingFilter
{...facetProps}
- facet={getFacet(facets, 'new_security_review')}
- property="new_security_review"
- value={query.new_security_review_rating}
+ facets={facets}
+ property="new_maintainability"
+ value={query.new_maintainability}
/>
<BasicSeparator className="sw-my-4" />
- <NewMaintainabilityFilter
+ <RatingFilter
{...facetProps}
- facet={getFacet(facets, 'new_maintainability')}
- value={query.new_maintainability}
+ facets={facets}
+ property="security_review"
+ value={query.new_security_review_rating}
/>
<BasicSeparator className="sw-my-4" />
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
index df733296b25..698bc1abb13 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
@@ -81,7 +81,7 @@ it('changes sort and perspective', async () => {
const user = userEvent.setup();
renderProjects();
- await user.click(ui.sortSelect.get());
+ await user.click(await ui.sortSelect.find());
await user.click(screen.getByText('projects.sorting.size'));
const projects = ui.projects.getAll();
@@ -108,7 +108,7 @@ it('handles showing favorite projects on load', async () => {
const user = userEvent.setup();
renderProjects(`${BASE_PATH}/favorite`);
- expect(ui.myFavoritesToggleOption.get()).toHaveAttribute('aria-current', 'true');
+ expect(await ui.myFavoritesToggleOption.find()).toHaveAttribute('aria-current', 'true');
expect(await ui.projects.findAll()).toHaveLength(2);
await user.click(ui.allToggleOption.get());
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx
index 1ed4cde499b..63fe3f135e9 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx
@@ -20,12 +20,20 @@
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as React from 'react';
+import SettingsServiceMock from '../../../../api/mocks/SettingsServiceMock';
import { CurrentUserContext } from '../../../../app/components/current-user/CurrentUserContext';
import { mockCurrentUser } from '../../../../helpers/testMocks';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { SettingsKey } from '../../../../types/settings';
import { CurrentUser } from '../../../../types/users';
import PageSidebar, { PageSidebarProps } from '../PageSidebar';
+const settingsHandler = new SettingsServiceMock();
+
+beforeEach(() => {
+ settingsHandler.reset();
+});
+
it('should render the right facets for overview', () => {
renderPageSidebar({
query: { size: '3' },
@@ -76,6 +84,26 @@ it('should allow to clear all filters', async () => {
expect(screen.getByRole('heading', { level: 2, name: 'filters' })).toHaveFocus();
});
+it('should show legacy filters', async () => {
+ settingsHandler.set(SettingsKey.LegacyMode, 'true');
+ renderPageSidebar();
+
+ expect(await screen.findAllByText('E')).toHaveLength(4);
+ expect(screen.queryByText(/projects.facets.rating_option/)).not.toBeInTheDocument();
+ expect(screen.queryByText('projects.facets.maintainability.description')).not.toBeInTheDocument();
+ expect(screen.queryByText('projects.facets.security_review.description')).not.toBeInTheDocument();
+});
+
+it('should show non legacy filters', async () => {
+ settingsHandler.set(SettingsKey.LegacyMode, 'false');
+ renderPageSidebar();
+
+ expect(await screen.findAllByText(/projects.facets.rating_option/)).toHaveLength(16);
+ expect(screen.queryAllByText('E')).toHaveLength(0);
+ expect(screen.getByText('projects.facets.maintainability.description')).toBeInTheDocument();
+ expect(screen.getByText('projects.facets.security_review.description')).toBeInTheDocument();
+});
+
function renderPageSidebar(overrides: Partial<PageSidebarProps> = {}, currentUser?: CurrentUser) {
return renderComponent(
<CurrentUserContext.Provider
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 26d8ae6558d..e062e396102 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
@@ -27,6 +27,7 @@ import SettingsServiceMock from '../../../../../api/mocks/SettingsServiceMock';
import { mockComponent } from '../../../../../helpers/mocks/component';
import { mockCurrentUser, mockLoggedInUser, mockMeasure } from '../../../../../helpers/testMocks';
import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
+import { SettingsKey } from '../../../../../types/settings';
import { CurrentUser } from '../../../../../types/users';
import { Project } from '../../../types';
import ProjectCard from '../ProjectCard';
@@ -282,7 +283,7 @@ describe('upgrade scenario (awaiting scan)', () => {
});
it('should not display awaiting analysis badge if legacy mode is enabled', async () => {
- settingsHandler.set('sonar.legacy.ratings.mode.enabled', 'true');
+ settingsHandler.set(SettingsKey.LegacyMode, 'true');
renderProjectCard({
...PROJECT,
measures: {
@@ -300,7 +301,7 @@ describe('upgrade scenario (awaiting scan)', () => {
});
it('should not display new values if legacy mode is enabled', async () => {
- settingsHandler.set('sonar.legacy.ratings.mode.enabled', 'true');
+ settingsHandler.set(SettingsKey.LegacyMode, 'true');
measuresHandler.registerComponentMeasures({
[PROJECT.key]: {
...newRatings,
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx
deleted file mode 100644
index 7ecdbd101ab..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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 * as React from 'react';
-import { RawQuery } from '~sonar-aligned/types/router';
-import { Facet } from '../types';
-import RatingFacet from './RatingFacet';
-
-interface Props {
- facet?: Facet;
- maxFacetValue?: number;
- onQueryChange: (change: RawQuery) => void;
- value?: any;
-}
-
-export default function NewMaintainabilityFilter(props: Props) {
- return <RatingFacet {...props} name="Maintainability" property="new_maintainability" />;
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx
deleted file mode 100644
index ab38d55005c..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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 * as React from 'react';
-import { RawQuery } from '~sonar-aligned/types/router';
-import { Facet } from '../types';
-import RatingFacet from './RatingFacet';
-
-interface Props {
- facet?: Facet;
- maxFacetValue?: number;
- onQueryChange: (change: RawQuery) => void;
- value?: any;
-}
-
-export default function NewReliabilityFilter(props: Props) {
- return <RatingFacet {...props} name="Reliability" property="new_reliability" />;
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx
deleted file mode 100644
index 43bded56cec..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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 * as React from 'react';
-import { RawQuery } from '~sonar-aligned/types/router';
-import { Facet } from '../types';
-import RatingFacet from './RatingFacet';
-
-interface Props {
- facet?: Facet;
- maxFacetValue?: number;
- onQueryChange: (change: RawQuery) => void;
- value?: any;
-}
-
-export default function NewSecurityFilter(props: Props) {
- return <RatingFacet {...props} name="Security" property="new_security" />;
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/RangeFacetBase.tsx b/server/sonar-web/src/main/js/apps/projects/filters/RangeFacetBase.tsx
index b80678c4350..63ebbd13062 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/RangeFacetBase.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/RangeFacetBase.tsx
@@ -31,6 +31,7 @@ export type Option = string | number;
interface Props {
className?: string;
+ description?: string;
facet?: Facet;
getFacetValueForOption?: (facet: Facet, option: Option) => number;
header: string;
@@ -153,10 +154,13 @@ export default class RangeFacetBase extends React.PureComponent<Props> {
};
render() {
- const { className, header, property } = this.props;
+ const { className, header, property, description } = this.props;
return (
<FacetBox className={className} name={header} data-key={property} open>
+ {description && (
+ <LightLabel className="sw-mb-4 sw--mt-2 sw-block">{description}</LightLabel>
+ )}
{this.renderOptions()}
</FacetBox>
);
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/RatingFacet.tsx b/server/sonar-web/src/main/js/apps/projects/filters/RatingFacet.tsx
index 9b8df67e781..5dbbfc52654 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/RatingFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/RatingFacet.tsx
@@ -19,10 +19,12 @@
*/
import { MetricsRatingBadge, RatingEnum } from 'design-system';
import * as React from 'react';
+import { useIntl } from 'react-intl';
import { formatMeasure } from '~sonar-aligned/helpers/measures';
import { MetricType } from '~sonar-aligned/types/metrics';
import { RawQuery } from '~sonar-aligned/types/router';
import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { useIsLegacyCCTMode } from '../../../queries/settings';
import { Facet } from '../types';
import RangeFacetBase from './RangeFacetBase';
@@ -37,6 +39,7 @@ interface Props {
export default function RatingFacet(props: Props) {
const { facet, maxFacetValue, name, property, value } = props;
+ const { data: isLegacy } = useIsLegacyCCTMode();
const renderAccessibleLabel = React.useCallback(
(option: number) => {
@@ -61,22 +64,54 @@ export default function RatingFacet(props: Props) {
<RangeFacetBase
facet={facet}
header={translate('metric_domain', name)}
+ description={
+ !isLegacy && hasDescription(property)
+ ? translate(`projects.facets.${property.replace('new_', '')}.description`)
+ : undefined
+ }
highlightUnder={1}
maxFacetValue={maxFacetValue}
onQueryChange={props.onQueryChange}
- options={[1, 2, 3, 4, 5]}
+ options={isLegacy ? [1, 2, 3, 4, 5] : [1, 2, 3, 4]}
property={property}
renderAccessibleLabel={renderAccessibleLabel}
- renderOption={renderOption}
+ renderOption={(option) => renderOption(option, property)}
value={value}
/>
);
}
-function renderOption(option: number) {
- const ratingFormatted = formatMeasure(option, MetricType.Rating);
+const hasDescription = (property: string) => {
+ return ['maintainability', 'new_maintainability', 'security_review'].includes(property);
+};
+
+function renderOption(option: string | number, property: string) {
+ return <RatingOption option={option} property={property} />;
+}
+
+function RatingOption({
+ option,
+ property,
+}: Readonly<{ option: string | number; property: string }>) {
+ const { data: isLegacy } = useIsLegacyCCTMode();
+ const intl = useIntl();
+ const ratingFormatted = formatMeasure(option, MetricType.Rating);
return (
- <MetricsRatingBadge label={ratingFormatted} rating={ratingFormatted as RatingEnum} size="xs" />
+ <>
+ <MetricsRatingBadge
+ label={ratingFormatted}
+ rating={ratingFormatted as RatingEnum}
+ isLegacy={isLegacy}
+ size="xs"
+ />
+ {!isLegacy && (
+ <span className="sw-ml-2">
+ {intl.formatMessage({
+ id: `projects.facets.rating_option.${property.replace('new_', '')}.${option}`,
+ })}
+ </span>
+ )}
+ </>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/RatingFilter.tsx
index b383a368fd3..15d2d5aa270 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/RatingFilter.tsx
@@ -19,18 +19,47 @@
*/
import * as React from 'react';
import { RawQuery } from '~sonar-aligned/types/router';
-import { Facet } from '../types';
+import { Facets } from '../types';
import RatingFacet from './RatingFacet';
interface Props {
- className?: string;
- facet?: Facet;
+ facets?: Facets;
headerDetail?: React.ReactNode;
maxFacetValue?: number;
onQueryChange: (change: RawQuery) => void;
+ property: string;
value?: any;
}
-export default function MaintainabilityFilter(props: Props) {
- return <RatingFacet {...props} name="Maintainability" property="maintainability" />;
+export default function RatingFilter({ facets, ...props }: Readonly<Props>) {
+ return (
+ <RatingFacet
+ {...props}
+ facet={getFacet(facets, props.property)}
+ name={getFacetName(props.property)}
+ />
+ );
+}
+
+function getFacetName(property: string) {
+ switch (property) {
+ case 'new_security':
+ case 'security':
+ return 'Security';
+ case 'new_maintainability':
+ case 'maintainability':
+ return 'Maintainability';
+ case 'new_reliability':
+ case 'reliability':
+ return 'Reliability';
+ case 'new_security_review':
+ case 'security_review':
+ return 'SecurityReview';
+ default:
+ return property;
+ }
+}
+
+function getFacet(facets: Facets | undefined, name: string) {
+ return facets && facets[name];
}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx
deleted file mode 100644
index 82f29a5f837..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 * as React from 'react';
-import { RawQuery } from '~sonar-aligned/types/router';
-import { Facet } from '../types';
-import RatingFacet from './RatingFacet';
-
-interface Props {
- facet?: Facet;
- headerDetail?: React.ReactNode;
- maxFacetValue?: number;
- onQueryChange: (change: RawQuery) => void;
- value?: any;
-}
-
-export default function ReliabilityFilter(props: Props) {
- return <RatingFacet {...props} name="Reliability" property="reliability" />;
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx
deleted file mode 100644
index 1adb18ec05d..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 * as React from 'react';
-import { RawQuery } from '~sonar-aligned/types/router';
-import { Facet } from '../types';
-import RatingFacet from './RatingFacet';
-
-interface Props {
- facet?: Facet;
- headerDetail?: React.ReactNode;
- maxFacetValue?: number;
- onQueryChange: (change: RawQuery) => void;
- value?: any;
-}
-
-export default function SecurityFilter(props: Props) {
- return <RatingFacet {...props} name="Security" property="security" />;
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SecurityReviewFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/SecurityReviewFilter.tsx
deleted file mode 100644
index dde1c3b2707..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/filters/SecurityReviewFilter.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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 { MetricsRatingBadge, RatingEnum } from 'design-system';
-import * as React from 'react';
-import { formatMeasure } from '~sonar-aligned/helpers/measures';
-import { MetricType } from '~sonar-aligned/types/metrics';
-import { RawQuery } from '~sonar-aligned/types/router';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { Dict } from '../../../types/types';
-import { Facet } from '../types';
-import RangeFacetBase from './RangeFacetBase';
-
-export interface Props {
- facet?: Facet;
- maxFacetValue?: number;
- onQueryChange: (change: RawQuery) => void;
- property?: string;
- value?: any;
-}
-
-const labels: Dict<string> = {
- 1: '≥ 80%',
- 2: '70% - 80%',
- 3: '50% - 70%',
- 4: '30% - 50%',
- 5: '< 30%',
-};
-
-export default function SecurityReviewFilter(props: Props) {
- const { facet, maxFacetValue, property = 'security_review', value } = props;
-
- return (
- <RangeFacetBase
- facet={facet}
- header={translate('metric_domain.SecurityReview')}
- highlightUnder={1}
- maxFacetValue={maxFacetValue}
- onQueryChange={props.onQueryChange}
- options={[1, 2, 3, 4, 5]}
- property={property}
- renderAccessibleLabel={renderAccessibleLabel}
- renderOption={renderOption}
- value={value}
- />
- );
-}
-
-function renderAccessibleLabel(option: number) {
- if (option === 1) {
- return translateWithParameters(
- 'projects.facets.rating_label_single_x',
- translate('metric_domain.SecurityReview'),
- formatMeasure(option, MetricType.Rating),
- );
- }
-
- return translateWithParameters(
- 'projects.facets.rating_label_multi_x',
- translate('metric_domain.SecurityReview'),
- formatMeasure(option, MetricType.Rating),
- );
-}
-
-function renderOption(option: number) {
- const ratingFormatted = formatMeasure(option, MetricType.Rating);
-
- return (
- <div className="sw-flex sw-items-center">
- <MetricsRatingBadge
- label={ratingFormatted}
- rating={ratingFormatted as RatingEnum}
- size="xs"
- />
- <span className="sw-ml-2">{labels[option]}</span>
- </div>
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/query.ts b/server/sonar-web/src/main/js/apps/projects/query.ts
index 3ccf4a0d176..5dd5b964204 100644
--- a/server/sonar-web/src/main/js/apps/projects/query.ts
+++ b/server/sonar-web/src/main/js/apps/projects/query.ts
@@ -19,7 +19,7 @@
*/
import { ComponentQualifier } from '~sonar-aligned/types/component';
import { RawQuery } from '~sonar-aligned/types/router';
-import { Dict } from '../../types/types';
+import { propertyToMetricMap, propertyToMetricMapLegacy } from './utils';
type Level = 'ERROR' | 'WARN' | 'OK';
@@ -74,7 +74,7 @@ export function parseUrlQuery(urlQuery: RawQuery): Query {
};
}
-export function convertToFilter(query: Query, isFavorite: boolean): string {
+export function convertToFilter(query: Query, isFavorite: boolean, isLegacy: boolean): string {
const conditions: string[] = [];
if (isFavorite) {
@@ -82,19 +82,19 @@ export function convertToFilter(query: Query, isFavorite: boolean): string {
}
if (query['gate'] != null) {
- conditions.push(mapPropertyToMetric('gate') + ' = ' + query['gate']);
+ conditions.push(mapPropertyToMetric('gate', isLegacy) + ' = ' + query['gate']);
}
['coverage', 'new_coverage'].forEach((property) =>
- pushMetricToArray(query, property, conditions, convertCoverage),
+ pushMetricToArray(query, property, conditions, convertCoverage, isLegacy),
);
['duplications', 'new_duplications'].forEach((property) =>
- pushMetricToArray(query, property, conditions, convertDuplications),
+ pushMetricToArray(query, property, conditions, convertDuplications, isLegacy),
);
['size', 'new_lines'].forEach((property) =>
- pushMetricToArray(query, property, conditions, convertSize),
+ pushMetricToArray(query, property, conditions, convertSize, isLegacy),
);
[
@@ -106,14 +106,16 @@ export function convertToFilter(query: Query, isFavorite: boolean): string {
'new_security',
'new_security_review_rating',
'new_maintainability',
- ].forEach((property) => pushMetricToArray(query, property, conditions, convertIssuesRating));
+ ].forEach((property) =>
+ pushMetricToArray(query, property, conditions, convertIssuesRating, isLegacy),
+ );
['languages', 'tags', 'qualifier'].forEach((property) =>
- pushMetricToArray(query, property, conditions, convertArrayMetric),
+ pushMetricToArray(query, property, conditions, convertArrayMetric, isLegacy),
);
if (query['search'] != null) {
- conditions.push(`${mapPropertyToMetric('search')} = "${query['search']}"`);
+ conditions.push(`${mapPropertyToMetric('search', isLegacy)} = "${query['search']}"`);
}
return conditions.join(' and ');
@@ -233,30 +235,8 @@ function convertSize(metric: string, size: number): string {
}
}
-function mapPropertyToMetric(property?: string): string | undefined {
- const map: Dict<string> = {
- analysis_date: 'analysisDate',
- reliability: 'reliability_rating',
- new_reliability: 'new_reliability_rating',
- security: 'security_rating',
- new_security: 'new_security_rating',
- security_review_rating: 'security_review_rating',
- new_security_review_rating: 'new_security_review_rating',
- maintainability: 'sqale_rating',
- new_maintainability: 'new_maintainability_rating',
- coverage: 'coverage',
- new_coverage: 'new_coverage',
- duplications: 'duplicated_lines_density',
- new_duplications: 'new_duplicated_lines_density',
- size: 'ncloc',
- new_lines: 'new_lines',
- gate: 'alert_status',
- languages: 'languages',
- tags: 'tags',
- search: 'query',
- qualifier: 'qualifier',
- };
- return property && map[property];
+function mapPropertyToMetric(property?: string, isLegacy: boolean = false): string | undefined {
+ return property && (isLegacy ? propertyToMetricMapLegacy : propertyToMetricMap)[property];
}
function pushMetricToArray(
@@ -264,8 +244,9 @@ function pushMetricToArray(
property: string,
conditionsArray: string[],
convertFunction: (metric: string, value: Query[string]) => string,
+ isLegacy: boolean,
): void {
- const metric = mapPropertyToMetric(property);
+ const metric = mapPropertyToMetric(property, isLegacy);
if (query[property] !== undefined && metric !== undefined) {
conditionsArray.push(convertFunction(metric, query[property]));
}
diff --git a/server/sonar-web/src/main/js/apps/projects/utils.ts b/server/sonar-web/src/main/js/apps/projects/utils.ts
index 426e9195529..21ec392bfbe 100644
--- a/server/sonar-web/src/main/js/apps/projects/utils.ts
+++ b/server/sonar-web/src/main/js/apps/projects/utils.ts
@@ -121,29 +121,57 @@ export const LEAK_METRICS = [
MetricKey.projects,
];
+export const LEGACY_FACETS = [
+ MetricKey.reliability_rating,
+ MetricKey.security_rating,
+ MetricKey.security_review_rating,
+ MetricKey.sqale_rating,
+ MetricKey.coverage,
+ MetricKey.duplicated_lines_density,
+ MetricKey.ncloc,
+ MetricKey.alert_status,
+ 'languages',
+ 'tags',
+ 'qualifier',
+];
+
export const FACETS = [
- 'reliability_rating',
- 'security_rating',
- 'security_review_rating',
- 'sqale_rating',
- 'coverage',
- 'duplicated_lines_density',
- 'ncloc',
- 'alert_status',
+ MetricKey.software_quality_reliability_rating,
+ MetricKey.software_quality_security_rating,
+ MetricKey.software_quality_security_review_rating,
+ MetricKey.software_quality_maintainability_rating,
+ MetricKey.coverage,
+ MetricKey.duplicated_lines_density,
+ MetricKey.ncloc,
+ MetricKey.alert_status,
+ 'languages',
+ 'tags',
+ 'qualifier',
+];
+
+export const LEGACY_LEAK_FACETS = [
+ MetricKey.new_reliability_rating,
+ MetricKey.new_security_rating,
+ MetricKey.new_security_review_rating,
+ MetricKey.new_maintainability_rating,
+ MetricKey.new_coverage,
+ MetricKey.new_duplicated_lines_density,
+ MetricKey.new_lines,
+ MetricKey.alert_status,
'languages',
'tags',
'qualifier',
];
export const LEAK_FACETS = [
- 'new_reliability_rating',
- 'new_security_rating',
- 'new_security_review_rating',
- 'new_maintainability_rating',
- 'new_coverage',
- 'new_duplicated_lines_density',
- 'new_lines',
- 'alert_status',
+ MetricKey.new_software_quality_reliability_rating,
+ MetricKey.new_software_quality_security_rating,
+ MetricKey.new_software_quality_security_review_rating,
+ MetricKey.new_software_quality_maintainability_rating,
+ MetricKey.new_coverage,
+ MetricKey.new_duplicated_lines_density,
+ MetricKey.new_lines,
+ MetricKey.alert_status,
'languages',
'tags',
'qualifier',
@@ -179,17 +207,19 @@ export function fetchProjects({
isFavorite,
query,
pageIndex = 1,
+ isLegacy,
}: {
isFavorite: boolean;
+ isLegacy: boolean;
pageIndex?: number;
query: Query;
}) {
const ps = PAGE_SIZE;
- const data = convertToQueryData(query, isFavorite, {
+ const data = convertToQueryData(query, isFavorite, isLegacy, {
p: pageIndex > 1 ? pageIndex : undefined,
ps,
- facets: defineFacets(query).join(),
+ facets: defineFacets(query, isLegacy).join(),
f: 'analysisDate,leakPeriodDate',
});
@@ -197,7 +227,7 @@ export function fetchProjects({
.then((response) => Promise.all([Promise.resolve(response), fetchScannableProjects()]))
.then(([{ components, facets, paging }, { scannableProjects }]) => {
return {
- facets: getFacetsMap(facets),
+ facets: getFacetsMap(facets, isLegacy),
projects: components.map((component) => ({
...component,
isScannable: scannableProjects.find((p) => p.key === component.key) !== undefined,
@@ -215,18 +245,23 @@ export function defineMetrics(query: Query): string[] {
return METRICS;
}
-function defineFacets(query: Query): string[] {
+function defineFacets(query: Query, isLegacy: boolean): string[] {
if (query.view === 'leak') {
- return LEAK_FACETS;
+ return isLegacy ? LEGACY_LEAK_FACETS : LEAK_FACETS;
}
- return FACETS;
+ return isLegacy ? LEGACY_FACETS : FACETS;
}
-export function convertToQueryData(query: Query, isFavorite: boolean, defaultData = {}) {
+export function convertToQueryData(
+ query: Query,
+ isFavorite: boolean,
+ isLegacy: boolean,
+ defaultData = {},
+) {
const data: RequestData = { ...defaultData };
- const filter = convertToFilter(query, isFavorite);
- const sort = convertToSorting(query);
+ const filter = convertToFilter(query, isFavorite, isLegacy);
+ const sort = convertToSorting(query, isLegacy);
if (filter) {
data.filter = filter;
@@ -253,7 +288,7 @@ function mapFacetValues(values: Array<{ count: number; val: string }>) {
return map;
}
-const propertyToMetricMap: Dict<string | undefined> = {
+export const propertyToMetricMapLegacy: Dict<string | undefined> = {
analysis_date: 'analysisDate',
reliability: 'reliability_rating',
new_reliability: 'new_reliability_rating',
@@ -277,13 +312,25 @@ const propertyToMetricMap: Dict<string | undefined> = {
creation_date: 'creationDate',
};
-const metricToPropertyMap = invert(propertyToMetricMap);
+export const propertyToMetricMap: Dict<string | undefined> = {
+ ...propertyToMetricMapLegacy,
+ reliability: 'software_quality_reliability_rating',
+ new_reliability: 'new_software_quality_reliability_rating',
+ security: 'software_quality_security_rating',
+ new_security: 'new_software_quality_security_rating',
+ security_review: 'software_quality_security_review_rating',
+ new_security_review: 'new_software_quality_security_review_rating',
+ maintainability: 'software_quality_maintainability_rating',
+ new_maintainability: 'new_software_quality_maintainability_rating',
+};
-function getFacetsMap(facets: Facet[]) {
+function getFacetsMap(facets: Facet[], isLegacy: boolean) {
const map: Dict<Dict<number>> = {};
facets.forEach((facet) => {
- const property = metricToPropertyMap[facet.property];
+ const property = invert(isLegacy ? propertyToMetricMapLegacy : propertyToMetricMap)[
+ facet.property
+ ];
const { values } = facet;
if (REVERSED_FACETS.includes(property)) {
@@ -296,12 +343,18 @@ function getFacetsMap(facets: Facet[]) {
return map;
}
-export function convertToSorting({ sort }: Query): { asc?: boolean; s?: string } {
+export function convertToSorting(
+ { sort }: Query,
+ isLegacy: boolean,
+): { asc?: boolean; s?: string } {
if (sort?.startsWith('-')) {
- return { s: propertyToMetricMap[sort.substring(1)], asc: false };
+ return {
+ s: (isLegacy ? propertyToMetricMapLegacy : propertyToMetricMap)[sort.substring(1)],
+ asc: false,
+ };
}
- return { s: propertyToMetricMap[sort ?? ''] };
+ return { s: (isLegacy ? propertyToMetricMapLegacy : propertyToMetricMap)[sort ?? ''] };
}
const ONE_MINUTE = 60000;
diff --git a/server/sonar-web/src/main/js/queries/settings.ts b/server/sonar-web/src/main/js/queries/settings.ts
index f6534ee40b8..2648e452844 100644
--- a/server/sonar-web/src/main/js/queries/settings.ts
+++ b/server/sonar-web/src/main/js/queries/settings.ts
@@ -21,7 +21,7 @@ import { queryOptions, useMutation, useQuery, useQueryClient } from '@tanstack/r
import { addGlobalSuccessMessage } from 'design-system';
import { getValue, getValues, resetSettingValue, setSettingValue } from '../api/settings';
import { translate } from '../helpers/l10n';
-import { ExtendedSettingDefinition } from '../types/settings';
+import { ExtendedSettingDefinition, SettingsKey } from '../types/settings';
import { createQueryHook } from './common';
type SettingValue = string | boolean | string[];
@@ -48,7 +48,7 @@ export const useGetValueQuery = createQueryHook(
export const useIsLegacyCCTMode = () => {
return useGetValueQuery(
- { key: 'sonar.legacy.ratings.mode.enabled' },
+ { key: SettingsKey.LegacyMode },
{ staleTime: Infinity, select: (data) => data?.value === 'true' },
);
};
diff --git a/server/sonar-web/src/main/js/types/settings.ts b/server/sonar-web/src/main/js/types/settings.ts
index 7722625a5ea..16f5083bd54 100644
--- a/server/sonar-web/src/main/js/types/settings.ts
+++ b/server/sonar-web/src/main/js/types/settings.ts
@@ -28,6 +28,7 @@ export const enum SettingsKey {
LicenceRemainingLocNotificationThreshold = 'sonar.license.notifications.remainingLocThreshold',
TokenMaxAllowedLifetime = 'sonar.auth.token.max.allowed.lifetime',
QPAdminCanDisableInheritedRules = 'sonar.qualityProfiles.allowDisableInheritedRules',
+ LegacyMode = 'sonar.legacy.ratings.mode.enabled',
}
export enum GlobalSettingKeys {