From 764e818569f3328a3d39470ec54837b52117dea5 Mon Sep 17 00:00:00 2001 From: Viktor Vorona Date: Wed, 7 Aug 2024 10:42:24 +0200 Subject: [PATCH] SONAR-22697 RatingsComponent POC --- .../components/metrics/RatingComponent.tsx | 71 +++++++++++++++ .../apps/code/components/ComponentMeasure.tsx | 22 ++--- .../components/ComponentMeasuresApp.tsx | 3 +- .../components/MeasureHeader.tsx | 1 + .../drilldown/MeasureCell.tsx | 8 +- .../sidebar/DomainSubnavigation.tsx | 4 +- .../sidebar/DomainSubnavigationItem.tsx | 4 +- .../component-measures/sidebar/Sidebar.tsx | 4 +- .../sidebar/SubnavigationMeasureValue.tsx | 4 +- .../branches/NewCodeMeasuresPanel.tsx | 10 +-- .../branches/OverallCodeMeasuresPanel.tsx | 17 ++-- .../branches/QualityGateCondition.tsx | 3 +- .../branches/SoftwareImpactMeasureCard.tsx | 6 +- .../branches/SoftwareImpactMeasureRating.tsx | 57 ++++++------ .../apps/projects/components/AllProjects.tsx | 2 +- .../apps/projects/components/ProjectsList.tsx | 90 ++++++++++++------- .../components/project-card/ProjectCard.tsx | 3 + .../project-card/ProjectCardMeasures.tsx | 21 +++-- .../__tests__/ProjectCardMeasures-test.tsx | 1 + .../src/main/js/apps/projects/utils.ts | 44 ++------- .../compare/ComparisonContainer.tsx | 6 +- .../components/HotspotSidebarHeader.tsx | 23 ++--- .../apps/settings/components/Definition.tsx | 5 +- .../AutoProvisionningConsent.tsx | 2 +- .../BitbucketAuthenticationTab.tsx | 8 +- .../components/measure/MeasureIndicator.tsx | 1 + .../__tests__/MeasureIndicator-test.tsx | 3 + .../sonar-web/src/main/js/queries/measures.ts | 37 +++++++- .../sonar-web/src/main/js/queries/settings.ts | 28 ++++-- .../components/measure/Measure.tsx | 38 ++++---- .../main/js/sonar-aligned/types/metrics.ts | 19 ++++ 31 files changed, 349 insertions(+), 196 deletions(-) create mode 100644 server/sonar-web/src/main/js/app/components/metrics/RatingComponent.tsx 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 new file mode 100644 index 00000000000..ddf105008d2 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/metrics/RatingComponent.tsx @@ -0,0 +1,71 @@ +/* + * 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 { Spinner } from '@sonarsource/echoes-react'; +import { MetricsRatingBadge, RatingEnum } from 'design-system'; +import * as React from 'react'; +import { formatMeasure } from '~sonar-aligned/helpers/measures'; +import { MetricKey, MetricType } from '~sonar-aligned/types/metrics'; +import { getLeakValue } from '../../../components/measure/utils'; +import { isDiffMetric } from '../../../helpers/measures'; +import { useMeasureQuery } from '../../../queries/measures'; +import { useIsLegacyCCTMode } from '../../../queries/settings'; + +interface Props { + className?: string; + componentKey: string; + ratingMetric: MetricKey; + size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'; +} + +type RatingMetricKeys = + | MetricKey.reliability_rating + | MetricKey.sqale_rating + | MetricKey.security_rating + | MetricKey.security_review_rating + | MetricKey.releasability_rating; + +const ALLOW_NEW_METRICS = false; + +const useGetMetricKeyForRating = (ratingMetric: RatingMetricKeys): MetricKey | null => { + const { data: isLegacy, isLoading } = useIsLegacyCCTMode(); + if (isLoading) { + return null; + } + return isLegacy || !ALLOW_NEW_METRICS ? ratingMetric : ((ratingMetric + '_new') as MetricKey); +}; + +export default function RatingComponent({ componentKey, ratingMetric, size, className }: Props) { + const metricKey = useGetMetricKeyForRating(ratingMetric as RatingMetricKeys); + const { data: measure, isLoading } = useMeasureQuery( + { componentKey, metricKey: metricKey ?? '' }, + { enabled: !!metricKey }, + ); + const value = isDiffMetric(metricKey ?? '') ? getLeakValue(measure) : measure?.value; + return ( + + + + ); +} diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx index d9d9b3251f3..2e76c81919d 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx @@ -17,19 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { - ContentCell, - MetricsRatingBadge, - NumericalCell, - QualityGateIndicator, - RatingCell, - RatingEnum, -} from 'design-system'; +import { ContentCell, NumericalCell, QualityGateIndicator, RatingCell } from 'design-system'; import * as React from 'react'; import Measure from '~sonar-aligned/components/measure/Measure'; import { formatMeasure } from '~sonar-aligned/helpers/measures'; import { Status } from '~sonar-aligned/types/common'; import { MetricKey, MetricType } from '~sonar-aligned/types/metrics'; +import RatingComponent from '../../../app/components/metrics/RatingComponent'; import { getLeakValue } from '../../../components/measure/utils'; import { CCT_SOFTWARE_QUALITY_METRICS, @@ -95,16 +89,18 @@ export default function ComponentMeasure(props: Props) { case MetricType.Rating: return ( - + ); default: return ( - + ); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx index 5e77f0dabde..b5c6128f0e7 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx @@ -279,7 +279,7 @@ class ComponentMeasuresApp extends React.PureComponent { render() { const { branchLike } = this.props; const { measures } = this.state; - const { canBrowseAllChildProjects, qualifier } = this.props.component; + const { canBrowseAllChildProjects, qualifier, key } = this.props.component; const query = parseQuery(this.props.location.query); const showFullMeasures = hasFullMeasures(branchLike); const displayOverview = hasBubbleChart(query.metric); @@ -295,6 +295,7 @@ class ComponentMeasuresApp extends React.PureComponent { {measures.length > 0 ? (
) {
- + ); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx index ae2fd362713..a91187072a0 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx @@ -42,6 +42,7 @@ import { import DomainSubnavigationItem from './DomainSubnavigationItem'; interface Props { + componentKey: string; domain: { measures: MeasureEnhanced[]; name: string }; onChange: (metric: string) => void; open: boolean; @@ -50,7 +51,7 @@ interface Props { } export default function DomainSubnavigation(props: Readonly) { - const { domain, onChange, open, selected, showFullMeasures } = props; + const { componentKey, domain, onChange, open, selected, showFullMeasures } = props; const helperMessageKey = `component_measures.domain_subnavigation.${domain.name}.help`; const helper = hasMessage(helperMessageKey) ? translate(helperMessageKey) : undefined; const items = addMeasureCategories(domain.name, domain.measures); @@ -106,6 +107,7 @@ export default function DomainSubnavigation(props: Readonly) { ) : ( void; @@ -30,6 +31,7 @@ interface Props { } export default function DomainSubnavigationItem({ + componentKey, measure, name, onChange, @@ -47,7 +49,7 @@ export default function DomainSubnavigationItem({ id={`measure-${key}-name`} > {name} - + ); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx index 2370040305b..9a3cf5fb6b8 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx @@ -38,6 +38,7 @@ import { PROJECT_OVERVEW, Query, isProjectOverview, populateDomainsFromMeasures import DomainSubnavigation from './DomainSubnavigation'; interface Props { + componentKey: string; measures: MeasureEnhanced[]; selectedMetric: string; showFullMeasures: boolean; @@ -45,7 +46,7 @@ interface Props { } export default function Sidebar(props: Readonly) { - const { showFullMeasures, updateQuery, selectedMetric, measures } = props; + const { showFullMeasures, updateQuery, componentKey, selectedMetric, measures } = props; const { top: topScroll, scrolledOnce } = useFollowScroll(); const domains = populateDomainsFromMeasures(measures); @@ -99,6 +100,7 @@ export default function Sidebar(props: Readonly) { {domains.map((domain: Domain) => ( ) { +export default function SubnavigationMeasureValue({ measure, componentKey }: Readonly) { const isDiff = isDiffMetric(measure.metric.key); const value = isDiff ? measure.leak : measure.value; @@ -37,6 +38,7 @@ export default function SubnavigationMeasureValue({ measure }: Readonly) id={`measure-${measure.metric.key}-${isDiff ? 'leak' : 'value'}`} > ) { showRequired={!isApp} icon={ newSecurityReviewRating ? ( - ) : ( diff --git a/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx index fa5c11cd111..a669f5ce1be 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx @@ -18,13 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import classNames from 'classnames'; -import { - MetricsRatingBadge, - NoDataIcon, - SnoozeCircleIcon, - TextSubdued, - getTabPanelId, -} from 'design-system'; +import { NoDataIcon, SnoozeCircleIcon, TextSubdued, getTabPanelId } from 'design-system'; import * as React from 'react'; import { useIntl } from 'react-intl'; import { getBranchLikeQuery } from '~sonar-aligned/helpers/branch-like'; @@ -34,7 +28,8 @@ import { getComponentSecurityHotspotsUrl, } from '~sonar-aligned/helpers/urls'; import { MetricKey, MetricType } from '~sonar-aligned/types/metrics'; -import { findMeasure, formatRating, isDiffMetric } from '../../../helpers/measures'; +import RatingComponent from '../../../app/components/metrics/RatingComponent'; +import { findMeasure, isDiffMetric } from '../../../helpers/measures'; import { CodeScope, getComponentDrilldownUrl } from '../../../helpers/urls'; import { Branch } from '../../../types/branch-like'; import { SoftwareQuality } from '../../../types/clean-code-taxonomy'; @@ -199,9 +194,9 @@ export default function OverallCodeMeasuresPanel(props: Readonly ) : ( diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx index 9cad5268f98..3563e8d3865 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx @@ -145,7 +145,7 @@ export class QualityGateCondition extends React.PureComponent { }; render() { - const { condition } = this.props; + const { condition, component } = this.props; const { measure } = condition; const { metric } = measure; @@ -159,6 +159,7 @@ export class QualityGateCondition extends React.PureComponent { m.metric.key === SOFTWARE_QUALITIES_METRIC_KEYS_MAP[softwareQuality].deprecatedMetric, ); - // Find rating measure - const ratingMeasure = measures.find((m) => m.metric.key === ratingMetricKey); - const count = formatMeasure(measure?.total ?? alternativeMeasure?.value, MetricType.ShortInteger); const totalLinkHref = getComponentIssuesUrl(component.key, { @@ -140,7 +137,8 @@ export function SoftwareImpactMeasureCard(props: Readonly
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureRating.tsx b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureRating.tsx index 7fe77dd89a3..cc1790b9f95 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureRating.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureRating.tsx @@ -17,50 +17,47 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Tooltip } from '@sonarsource/echoes-react'; -import { MetricsRatingBadge } from 'design-system'; import * as React from 'react'; -import { useIntl } from 'react-intl'; -import { formatRating } from '../../../helpers/measures'; +import RatingComponent from '../../../app/components/metrics/RatingComponent'; +import { MetricKey } from '../../../sonar-aligned/types/metrics'; import { SoftwareQuality } from '../../../types/clean-code-taxonomy'; -import SoftwareImpactRatingTooltipContent from './SoftwareImpactRatingTooltip'; export interface SoftwareImpactMeasureRatingProps { + componentKey: string; + ratingMetricKey: MetricKey; softwareQuality: SoftwareQuality; - value?: string; } export function SoftwareImpactMeasureRating(props: Readonly) { - const { softwareQuality, value } = props; + const { ratingMetricKey, componentKey } = props; - const intl = useIntl(); + // const intl = useIntl(); - const rating = formatRating(value); - - const additionalInfo = ( - - ); + // const additionalInfo = ( + // + // ); return ( <> - - - + {/* */} + + {/* */} {/* The badge is not interactive, so show the tooltip content for screen-readers only */} - {additionalInfo} + {/* {additionalInfo} */} ); } 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 e43f16359de..f7fe5e311bd 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 @@ -66,7 +66,7 @@ interface State { facets?: Facets; loading: boolean; pageIndex?: number; - projects?: Project[]; + projects?: Omit[]; query: Query; total?: number; } diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx index 3b680c306f1..4b255c17b3c 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx @@ -18,17 +18,20 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Spinner } from '@sonarsource/echoes-react'; import classNames from 'classnames'; -import { Spinner } from 'design-system'; import * as React from 'react'; import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'; import { List, ListRowProps } from 'react-virtualized/dist/commonjs/List'; import EmptySearch from '../../../components/common/EmptySearch'; import ListFooter from '../../../components/controls/ListFooter'; import { translate } from '../../../helpers/l10n'; +import { isDiffMetric } from '../../../helpers/measures'; +import { useMeasuresForProjectsQuery } from '../../../queries/measures'; import { CurrentUser } from '../../../types/users'; import { Query } from '../query'; import { Project } from '../types'; +import { defineMetrics } from '../utils'; import EmptyFavoriteSearch from './EmptyFavoriteSearch'; import EmptyInstance from './EmptyInstance'; import NoFavoriteProjects from './NoFavoriteProjects'; @@ -46,29 +49,40 @@ interface Props { isFiltered: boolean; loadMore: () => void; loading: boolean; - projects: Project[]; + projects: Omit[]; query: Query; total?: number; } -export default class ProjectsList extends React.PureComponent { - renderNoProjects() { - const { currentUser, isFavorite, isFiltered, query } = this.props; - if (isFiltered) { - return isFavorite ? : ; - } - return isFavorite ? : ; - } +export default function ProjectsList(props: Readonly) { + const { + currentUser, + isFavorite, + handleFavorite, + cardType, + isFiltered, + query, + loading, + projects, + total, + loadMore, + } = props; + const { data: measures, isLoading: measuresLoading } = useMeasuresForProjectsQuery( + { + projectKeys: projects.map((p) => p.key), + metricKeys: defineMetrics(query), + }, + { enabled: projects.length > 0 }, + ); - renderRow = ({ index, key, style }: ListRowProps) => { - const { loading, projects, total } = this.props; + const renderRow = ({ index, key, style }: ListRowProps) => { if (index === projects.length) { return (
{ } const project = projects[index]; + const componentMeasures = + measures + ?.filter((measure) => measure.component === project.key) + .reduce( + (acc, measure) => { + const value = isDiffMetric(measure.metric) ? measure.period?.value : measure.value; + if (value !== undefined) { + acc[measure.metric] = value; + } + return acc; + }, + {} as Record, + ) ?? {}; return (
{ >
); }; - renderList() { - return this.props.loading ? ( - - ) : ( + if (projects.length === 0) { + if (isFiltered) { + return isFavorite ? : ; + } + return isFavorite ? : ; + } + + return ( + {({ height, width }) => ( { if (index === 0) { // first card, double top and bottom margin return PROJECT_CARD_HEIGHT + PROJECT_CARD_MARGIN * 2; } - if (index === this.props.projects.length) { + if (index === projects.length) { // Footer card, no margin return PROJECT_LIST_FOOTER_HEIGHT; } // all other cards, only bottom margin return PROJECT_CARD_HEIGHT + PROJECT_CARD_MARGIN; }} - rowRenderer={this.renderRow} + rowRenderer={renderRow} style={{ outline: 'none' }} tabIndex={-1} width={width} /> )} - ); - } - - render() { - const { projects } = this.props; - - return projects.length > 0 ? this.renderList() : this.renderNoProjects(); - } + + ); } diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx index d2e64fd1476..d0ff2f6e32f 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx @@ -172,6 +172,7 @@ function renderFirstLine(
); diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx index e7cfe3abf6b..f444465e33b 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCardMeasures.tsx @@ -20,30 +20,30 @@ import { CoverageIndicator, DuplicationsIndicator, - MetricsRatingBadge, Note, PageContentFontWrapper, - RatingLabel, } from 'design-system'; import * as React from 'react'; import Measure from '~sonar-aligned/components/measure/Measure'; import { ComponentQualifier } from '~sonar-aligned/types/component'; import { MetricKey, MetricType } from '~sonar-aligned/types/metrics'; +import RatingComponent from '../../../../app/components/metrics/RatingComponent'; import { duplicationRatingConverter } from '../../../../components/measure/utils'; import { translate } from '../../../../helpers/l10n'; -import { formatRating } from '../../../../helpers/measures'; import { isDefined } from '../../../../helpers/types'; import { Dict } from '../../../../types/types'; import ProjectCardMeasure from './ProjectCardMeasure'; export interface ProjectCardMeasuresProps { + // eslint-disable-next-line react/no-unused-prop-types + componentKey: string; componentQualifier: ComponentQualifier; isNewCode: boolean; measures: Dict; } function renderNewIssues(props: ProjectCardMeasuresProps) { - const { measures, isNewCode } = props; + const { measures, isNewCode, componentKey } = props; if (!isNewCode) { return null; @@ -55,6 +55,7 @@ function renderNewIssues(props: ProjectCardMeasuresProps) { label={translate(`metric.${MetricKey.new_violations}.description`)} > {measures[coverageMetric] && } {measures[duplicationMetric] != null && } { const { iconLabel, metricKey, metricRatingKey, metricType } = measure; - const value = formatRating(measures[metricRatingKey]); const measureValue = [ @@ -178,8 +180,9 @@ function renderRatings(props: ProjectCardMeasuresProps) { return ( - + - Promise.all([ - fetchProjectMeasures(response.components, query), - Promise.resolve(response), - fetchScannableProjects(), - ]), - ) - .then(([measures, { components, facets, paging }, { scannableProjects }]) => { + .then((response) => Promise.all([Promise.resolve(response), fetchScannableProjects()])) + .then(([{ components, facets, paging }, { scannableProjects }]) => { return { facets: getFacetsMap(facets), - projects: components.map((component) => { - const componentMeasures: Dict = {}; - measures - .filter((measure) => measure.component === component.key) - .forEach((measure) => { - const value = isDiffMetric(measure.metric) ? measure.period?.value : measure.value; - if (value !== undefined) { - componentMeasures[measure.metric] = value; - } - }); - - return { - ...component, - measures: componentMeasures, - isScannable: scannableProjects.find((p) => p.key === component.key) !== undefined, - }; - }), + projects: components.map((component) => ({ + ...component, + isScannable: scannableProjects.find((p) => p.key === component.key) !== undefined, + })), total: paging.total, }; }); @@ -260,17 +239,6 @@ export function convertToQueryData(query: Query, isFavorite: boolean, defaultDat return data; } -export function fetchProjectMeasures(projects: Array<{ key: string }>, query: Query) { - if (!projects.length) { - return Promise.resolve([]); - } - - const projectKeys = projects.map((project) => project.key); - const metrics = defineMetrics(query); - - return getMeasuresForProjects(projectKeys, metrics); -} - function mapFacetValues(values: Array<{ count: number; val: string }>) { const map: Dict = {}; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx index aed914a746c..6825ad39b45 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx @@ -38,9 +38,9 @@ export function ComparisonContainer(props: Readonly) { const { profile, profiles } = props; const location = useLocation(); const router = useRouter(); - const { data: inheritRulesSetting } = useGetValueQuery( - SettingsKey.QPAdminCanDisableInheritedRules, - ); + const { data: inheritRulesSetting } = useGetValueQuery({ + key: SettingsKey.QPAdminCanDisableInheritedRules, + }); const canDeactivateInheritedRules = inheritRulesSetting?.value === 'true'; const { withKey } = location.query; diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSidebarHeader.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSidebarHeader.tsx index b640a000212..ef2941c81d3 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSidebarHeader.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSidebarHeader.tsx @@ -81,16 +81,19 @@ function HotspotSidebarHeader(props: SecurityHotspotsAppRendererProps) { )} - + {component && ( + + )} diff --git a/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx b/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx index d8379e27c81..c8180e203fc 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx @@ -57,7 +57,10 @@ export default function Definition(props: Readonly) { const [success, setSuccess] = React.useState(false); const [changedValue, setChangedValue] = React.useState(); const [validationMessage, setValidationMessage] = React.useState(); - const { data: loadedSettingValue, isLoading } = useGetValueQuery(definition.key, component?.key); + const { data: loadedSettingValue, isLoading } = useGetValueQuery({ + key: definition.key, + component: component?.key, + }); const settingValue = isLoading ? initialSettingValue : loadedSettingValue ?? undefined; const { mutateAsync: resetSettingValue } = useResetSettingsMutation(); diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/AutoProvisionningConsent.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/AutoProvisionningConsent.tsx index f6c20eaa44b..85281797e08 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/AutoProvisionningConsent.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/AutoProvisionningConsent.tsx @@ -44,7 +44,7 @@ export default function AutoProvisioningConsent(props: Readonly) { const { mutate: updateGithubConfig } = useUpdateGitHubConfigurationMutation(); const { mutate: updateGitlabConfig } = useUpdateGitLabConfigurationMutation(); - const { data: userConsent } = useGetValueQuery(CONSENT_SETTING_KEY); + const { data: userConsent } = useGetValueQuery({ key: CONSENT_SETTING_KEY }); const { mutateAsync: resetSettingValue } = useResetSettingsMutation(); if ( diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/BitbucketAuthenticationTab.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/BitbucketAuthenticationTab.tsx index 138817182b1..06a2ed8703d 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/BitbucketAuthenticationTab.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/BitbucketAuthenticationTab.tsx @@ -37,10 +37,10 @@ interface Props { export default function BitbucketAuthenticationTab(props: Readonly) { const { definitions } = props; - const { data: allowToSignUpEnabled } = useGetValueQuery( - 'sonar.auth.bitbucket.allowUsersToSignUp', - ); - const { data: workspaces } = useGetValueQuery('sonar.auth.bitbucket.workspaces'); + const { data: allowToSignUpEnabled } = useGetValueQuery({ + key: 'sonar.auth.bitbucket.allowUsersToSignUp', + }); + const { data: workspaces } = useGetValueQuery({ key: 'sonar.auth.bitbucket.workspaces' }); const isConfigurationUnsafe = allowToSignUpEnabled?.value === 'true' && diff --git a/server/sonar-web/src/main/js/components/measure/MeasureIndicator.tsx b/server/sonar-web/src/main/js/components/measure/MeasureIndicator.tsx index 74b8e77154a..e6f9b7b1668 100644 --- a/server/sonar-web/src/main/js/components/measure/MeasureIndicator.tsx +++ b/server/sonar-web/src/main/js/components/measure/MeasureIndicator.tsx @@ -25,6 +25,7 @@ import { duplicationRatingConverter } from './utils'; interface Props { className?: string; + componentKey: string; decimals?: number; metricKey: string; metricType: string; diff --git a/server/sonar-web/src/main/js/components/measure/__tests__/MeasureIndicator-test.tsx b/server/sonar-web/src/main/js/components/measure/__tests__/MeasureIndicator-test.tsx index 766acf472e0..3b42b47f134 100644 --- a/server/sonar-web/src/main/js/components/measure/__tests__/MeasureIndicator-test.tsx +++ b/server/sonar-web/src/main/js/components/measure/__tests__/MeasureIndicator-test.tsx @@ -26,6 +26,7 @@ import MeasureIndicator from '../MeasureIndicator'; it('renders correctly for coverage', () => { render( { it('renders correctly for failed quality gate', () => { const wrapper = render( { it('renders correctly for passed quality gate', () => { const wrapper = render( { + const queryClient = useQueryClient(); + return queryOptions({ + queryKey: ['measures', 'list', 'projects', projectKeys, metricKeys], + queryFn: async () => { + const measures = await getMeasuresForProjects(projectKeys, metricKeys); + measures.forEach((measure) => { + queryClient.setQueryData( + ['measures', 'details', measure.component, measure.metric], + measure, + ); + }); + return measures; + }, + }); + }, +); + +export const useMeasureQuery = createQueryHook( + ({ componentKey, metricKey }: { componentKey: string; metricKey: string }) => { + return queryOptions({ + queryKey: ['measures', 'details', componentKey, metricKey], + queryFn: () => + getMeasures({ component: componentKey, metricKeys: metricKey }).then( + (measures) => measures[0], + ), + staleTime: Infinity, + }); + }, +); diff --git a/server/sonar-web/src/main/js/queries/settings.ts b/server/sonar-web/src/main/js/queries/settings.ts index 164ee2a950a..6d3b2c02d4f 100644 --- a/server/sonar-web/src/main/js/queries/settings.ts +++ b/server/sonar-web/src/main/js/queries/settings.ts @@ -17,11 +17,12 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { queryOptions, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { addGlobalSuccessMessage } from 'design-system'; import { getValue, getValues, resetSettingValue, setSettingValue } from '../api/settings'; import { translate } from '../helpers/l10n'; import { ExtendedSettingDefinition } from '../types/settings'; +import { createQueryHook } from './common'; type SettingValue = string | boolean | string[]; @@ -34,14 +35,23 @@ export function useGetValuesQuery(keys: string[]) { }); } -export function useGetValueQuery(key: string, component?: string) { - return useQuery({ - queryKey: ['settings', 'details', key] as const, - queryFn: ({ queryKey: [_a, _b, key] }) => { - return getValue({ key, component }).then((v) => v ?? null); - }, - }); -} +export const useGetValueQuery = createQueryHook( + ({ key, component }: { component?: string; key: string }) => { + return queryOptions({ + queryKey: ['settings', 'details', key] as const, + queryFn: ({ queryKey: [_a, _b, key] }) => { + return getValue({ key, component }).then((v) => v ?? null); + }, + }); + }, +); + +export const useIsLegacyCCTMode = () => { + return useGetValueQuery( + { key: 'sonar.old_world' }, + { staleTime: Infinity, select: (data) => !!data }, + ); +}; export function useResetSettingsMutation() { const queryClient = useQueryClient(); diff --git a/server/sonar-web/src/main/js/sonar-aligned/components/measure/Measure.tsx b/server/sonar-web/src/main/js/sonar-aligned/components/measure/Measure.tsx index 8af8804f9ac..bbb4fe26f3b 100644 --- a/server/sonar-web/src/main/js/sonar-aligned/components/measure/Measure.tsx +++ b/server/sonar-web/src/main/js/sonar-aligned/components/measure/Measure.tsx @@ -17,19 +17,19 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Tooltip } from '@sonarsource/echoes-react'; import classNames from 'classnames'; -import { MetricsRatingBadge, QualityGateIndicator, RatingLabel } from 'design-system'; +import { QualityGateIndicator } from 'design-system'; import React from 'react'; import { useIntl } from 'react-intl'; import { formatMeasure } from '~sonar-aligned/helpers/measures'; import { Status } from '~sonar-aligned/types/common'; -import { MetricType } from '~sonar-aligned/types/metrics'; -import RatingTooltipContent from '../../../components/measure/RatingTooltipContent'; +import { MetricKey, MetricType } from '~sonar-aligned/types/metrics'; +import RatingComponent from '../../../app/components/metrics/RatingComponent'; interface Props { badgeSize?: 'xs' | 'sm' | 'md'; className?: string; + componentKey: string; decimals?: number; fontClassName?: `sw-body-${string}` | `sw-heading-lg`; metricKey: string; @@ -40,6 +40,7 @@ interface Props { export default function Measure({ className, + componentKey, badgeSize, decimals, fontClassName, @@ -89,28 +90,31 @@ export default function Measure({ return {formattedValue ?? '—'}; } - const tooltip = ; + // const tooltip = ; const rating = ( - ); return ( - + <> + {/* */} {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */} {rating} - + {/* */} + ); } diff --git a/server/sonar-web/src/main/js/sonar-aligned/types/metrics.ts b/server/sonar-web/src/main/js/sonar-aligned/types/metrics.ts index 53aed15b4fb..684c9c2edcc 100644 --- a/server/sonar-web/src/main/js/sonar-aligned/types/metrics.ts +++ b/server/sonar-web/src/main/js/sonar-aligned/types/metrics.ts @@ -47,6 +47,7 @@ export enum MetricKey { duplicated_lines_density = 'duplicated_lines_density', duplications_data = 'duplications_data', effort_to_reach_maintainability_rating_a = 'effort_to_reach_maintainability_rating_a', + effort_to_reach_maintainability_rating_a_new = 'effort_to_reach_maintainability_rating_a_new', executable_lines_data = 'executable_lines_data', false_positive_issues = 'false_positive_issues', file_complexity = 'file_complexity', @@ -74,6 +75,7 @@ export enum MetricKey { lines_to_cover = 'lines_to_cover', maintainability_issues = 'maintainability_issues', maintainability_rating_distribution = 'maintainability_rating_distribution', + maintainability_rating_distribution_new = 'maintainability_rating_distribution_new', maintainability_rating_effort = 'maintainability_rating_effort', major_violations = 'major_violations', minor_violations = 'minor_violations', @@ -99,21 +101,29 @@ export enum MetricKey { new_lines_to_cover = 'new_lines_to_cover', new_maintainability_issues = 'new_maintainability_issues', new_maintainability_rating = 'new_maintainability_rating', + new_maintainability_rating_new = 'new_maintainability_rating_new', new_maintainability_rating_distribution = 'new_maintainability_rating_distribution', + new_maintainability_rating_distribution_new = 'new_maintainability_rating_distribution_new', new_major_violations = 'new_major_violations', new_minor_violations = 'new_minor_violations', new_reliability_issues = 'new_reliability_issues', new_reliability_rating = 'new_reliability_rating', + new_reliability_rating_new = 'new_reliability_rating_new', new_reliability_rating_distribution = 'new_reliability_rating_distribution', + new_reliability_rating_distribution_new = 'new_reliability_rating_distribution_new', new_reliability_remediation_effort = 'new_reliability_remediation_effort', new_security_hotspots = 'new_security_hotspots', new_security_hotspots_reviewed = 'new_security_hotspots_reviewed', new_security_issues = 'new_security_issues', new_security_rating = 'new_security_rating', + new_security_rating_new = 'new_security_rating_new', new_security_rating_distribution = 'new_security_rating_distribution', + new_security_rating_distribution_new = 'new_security_rating_distribution_new', new_security_remediation_effort = 'new_security_remediation_effort', new_security_review_rating = 'new_security_review_rating', + new_security_review_rating_new = 'new_security_review_rating_new', new_security_review_rating_distribution = 'new_security_review_rating_distribution', + new_security_review_rating_distribution_new = 'new_security_review_rating_distribution_new', new_sqale_debt_ratio = 'new_sqale_debt_ratio', new_technical_debt = 'new_technical_debt', new_uncovered_conditions = 'new_uncovered_conditions', @@ -132,10 +142,14 @@ export enum MetricKey { quality_profiles = 'quality_profiles', releasability_effort = 'releasability_effort', releasability_rating = 'releasability_rating', + releasability_rating_new = 'releasability_rating_new', releasability_rating_distribution = 'releasability_rating_distribution', + releasability_rating_distribution_new = 'releasability_rating_distribution_new', reliability_issues = 'reliability_issues', reliability_rating = 'reliability_rating', + reliability_rating_new = 'reliability_rating_new', reliability_rating_distribution = 'reliability_rating_distribution', + reliability_rating_distribution_new = 'reliability_rating_distribution_new', reliability_rating_effort = 'reliability_rating_effort', reliability_remediation_effort = 'reliability_remediation_effort', reopened_issues = 'reopened_issues', @@ -143,17 +157,22 @@ export enum MetricKey { security_hotspots_reviewed = 'security_hotspots_reviewed', security_issues = 'security_issues', security_rating = 'security_rating', + security_rating_new = 'security_rating_new', security_rating_distribution = 'security_rating_distribution', + security_rating_distribution_new = 'security_rating_distribution_new', security_rating_effort = 'security_rating_effort', security_remediation_effort = 'security_remediation_effort', security_review_rating = 'security_review_rating', + security_review_rating_new = 'security_review_rating_new', security_review_rating_distribution = 'security_review_rating_distribution', + security_review_rating_distribution_new = 'security_review_rating_distribution_new', security_review_rating_effort = 'security_review_rating_effort', skipped_tests = 'skipped_tests', sonarjava_feedback = 'sonarjava_feedback', sqale_debt_ratio = 'sqale_debt_ratio', sqale_index = 'sqale_index', sqale_rating = 'sqale_rating', + sqale_rating_new = 'sqale_rating_new', statements = 'statements', team_at_sonarsource = 'team_at_sonarsource', team_size = 'team_size', -- 2.39.5