<circle
cx="8"
cy="8"
- fill="rgb(255,214,175)"
+ fill="rgb(254,205,202)"
r="7"
/>
<path
type sizeType = keyof typeof SIZE_MAPPING;
interface Props extends React.AriaAttributes {
className?: string;
+ isLegacy?: boolean;
label?: string;
rating?: RatingLabel;
size?: sizeType;
};
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
<MetricsRatingBadgeStyled
aria-label={label}
className={className}
+ isLegacy={isLegacy}
rating={rating}
ref={ref}
size={SIZE_MAPPING[size]}
}
};
-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;
// size indicators
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
import { useIsLegacyCCTMode } from '../../../queries/settings';
import { BranchLike } from '../../../types/branch-like';
+type SizeType = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+
interface Props {
branchLike?: BranchLike;
className?: string;
getLabel?: (rating: RatingEnum) => string;
getTooltip?: (rating: RatingEnum) => React.ReactNode;
ratingMetric: MetricKey;
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+ size?: SizeType;
}
type RatingMetricKeys =
const badge = (
<MetricsRatingBadge
label={getLabel ? getLabel(rating) : value ?? '—'}
+ isLegacy={measure?.metric ? !isNewRatingMetric(measure.metric as MetricKey) : false}
rating={rating}
size={size}
className={className}
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',
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,
});
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 },
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',
+ });
});
});
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';
appState: AppState;
currentUser: CurrentUser;
isFavorite: boolean;
+ isLegacy: boolean;
location: Location;
router: Router;
}
}
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,
};
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) {
};
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,
});
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(
() => {
],
);
- 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')};
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';
{!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" />
)}
{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" />
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();
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());
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' },
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
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';
});
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: {
});
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,
+++ /dev/null
-/*
- * 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 {
- className?: string;
- facet?: Facet;
- headerDetail?: React.ReactNode;
- maxFacetValue?: number;
- onQueryChange: (change: RawQuery) => void;
- value?: any;
-}
-
-export default function MaintainabilityFilter(props: Props) {
- return <RatingFacet {...props} name="Maintainability" property="maintainability" />;
-}
+++ /dev/null
-/*
- * 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" />;
-}
+++ /dev/null
-/*
- * 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" />;
-}
+++ /dev/null
-/*
- * 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" />;
-}
interface Props {
className?: string;
+ description?: string;
facet?: Facet;
getFacetValueForOption?: (facet: Facet, option: Option) => number;
header: string;
};
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>
);
*/
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';
export default function RatingFacet(props: Props) {
const { facet, maxFacetValue, name, property, value } = props;
+ const { data: isLegacy } = useIsLegacyCCTMode();
const renderAccessibleLabel = React.useCallback(
(option: number) => {
<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>
+ )}
+ </>
);
}
--- /dev/null
+/*
+ * 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 { Facets } from '../types';
+import RatingFacet from './RatingFacet';
+
+interface Props {
+ facets?: Facets;
+ headerDetail?: React.ReactNode;
+ maxFacetValue?: number;
+ onQueryChange: (change: RawQuery) => void;
+ property: string;
+ value?: any;
+}
+
+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];
+}
+++ /dev/null
-/*
- * 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" />;
-}
+++ /dev/null
-/*
- * 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" />;
-}
+++ /dev/null
-/*
- * 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>
- );
-}
*/
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';
};
}
-export function convertToFilter(query: Query, isFavorite: boolean): string {
+export function convertToFilter(query: Query, isFavorite: boolean, isLegacy: boolean): string {
const conditions: string[] = [];
if (isFavorite) {
}
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),
);
[
'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 ');
}
}
-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(
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]));
}
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',
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',
});
.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,
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;
return map;
}
-const propertyToMetricMap: Dict<string | undefined> = {
+export const propertyToMetricMapLegacy: Dict<string | undefined> = {
analysis_date: 'analysisDate',
reliability: 'reliability_rating',
new_reliability: 'new_reliability_rating',
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)) {
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;
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[];
export const useIsLegacyCCTMode = () => {
return useGetValueQuery(
- { key: 'sonar.legacy.ratings.mode.enabled' },
+ { key: SettingsKey.LegacyMode },
{ staleTime: Infinity, select: (data) => data?.value === 'true' },
);
};
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 {
projects.facets.quality_gate=Quality Gate
projects.facets.quality_gate.warning_help=Warning status is deprecated. This filter will disappear when no Warning Quality Gate remains.
projects.facets.rating_x={0} rating
+projects.facets.rating_option.reliability.1=0 issues
+projects.facets.rating_option.reliability.2=≥ 1 low issue
+projects.facets.rating_option.reliability.3=≥ 1 medium issue
+projects.facets.rating_option.reliability.4=≥ 1 high issue
+projects.facets.rating_option.security.1=0 issues
+projects.facets.rating_option.security.2=≥ 1 low issue
+projects.facets.rating_option.security.3=≥ 1 medium issue
+projects.facets.rating_option.security.4=≥ 1 high issue
+projects.facets.rating_option.maintainability.1=≤ 5% to 0%
+projects.facets.rating_option.maintainability.2=≥ 5% to <10%
+projects.facets.rating_option.maintainability.3=≥ 10% to <20%
+projects.facets.rating_option.maintainability.4=≥ 20%
+projects.facets.rating_option.security_review.1== 100%
+projects.facets.rating_option.security_review.2=≥ 70% to <100%
+projects.facets.rating_option.security_review.3=≥ 50% to <70%
+projects.facets.rating_option.security_review.4=< 50%
+projects.facets.security_review.description=The percentage of reviewed (fixed or safe) security hotspots
+projects.facets.maintainability.description=Ratio of the size of the project to the estimated time needed to fix all outstanding maintainability issues
projects.facets.languages=Languages
projects.facets.search.languages=Search for languages
projects.facets.new_lines=New Lines