diff options
author | Viktor Vorona <viktor.vorona@sonarsource.com> | 2024-08-07 10:42:24 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-08-26 20:03:05 +0000 |
commit | 764e818569f3328a3d39470ec54837b52117dea5 (patch) | |
tree | 532358d9a703869ab8ba24d74f5fd32a7388b600 /server/sonar-web/src/main/js/apps | |
parent | 28ff86fcd8312d8e6b61a679106df554bcea072d (diff) | |
download | sonarqube-764e818569f3328a3d39470ec54837b52117dea5.tar.gz sonarqube-764e818569f3328a3d39470ec54837b52117dea5.zip |
SONAR-22697 RatingsComponent POC
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
24 files changed, 179 insertions, 169 deletions
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 ( <RatingCell className="sw-whitespace-nowrap"> - <MetricsRatingBadge - label={value ?? '—'} - rating={formatMeasure(value, MetricType.Rating) as RatingEnum} - /> + <RatingComponent componentKey={component.key} ratingMetric={metric.key as MetricKey} /> </RatingCell> ); default: return ( <NumericalCell className="sw-whitespace-nowrap"> - <Measure metricKey={finalMetricKey} metricType={finalMetricType} value={value} /> + <Measure + componentKey={component.key} + metricKey={finalMetricKey} + metricType={finalMetricType} + value={value} + /> </NumericalCell> ); } 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<Props, State> { 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<Props, State> { {measures.length > 0 ? ( <div className="sw-grid sw-grid-cols-12 sw-w-full"> <Sidebar + componentKey={key} measures={measures} selectedMetric={metric ? metric.key : query.metric} showFullMeasures={showFullMeasures} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx index 59bea826a71..ad7ace64e8b 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx @@ -63,6 +63,7 @@ export default function MeasureHeader(props: Readonly<Props>) { <div className="sw-flex sw-items-center sw-ml-2"> <Measure + componentKey={component.key} className={classNames('it__measure-details-value sw-body-md')} metricKey={metric.key} metricType={metric.type} diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.tsx index 2768f777df9..61fbe5405a5 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.tsx @@ -38,7 +38,13 @@ export default function MeasureCell({ component, measure, metric }: Readonly<Pro return ( <NumericalCell className="sw-py-3"> - <Measure metricKey={metric.key} metricType={metric.type} value={value} small /> + <Measure + componentKey={component.key} + metricKey={metric.key} + metricType={metric.type} + value={value} + small + /> </NumericalCell> ); } 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<Props>) { - 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<Props>) { ) : ( <DomainSubnavigationItem key={item.metric.key} + componentKey={componentKey} measure={item} name={getMetricSubnavigationName(item.metric, translateMetric)} onChange={onChange} diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigationItem.tsx b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigationItem.tsx index 791b6025f32..cfb2184ff25 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigationItem.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigationItem.tsx @@ -23,6 +23,7 @@ import { MeasureEnhanced } from '../../../types/types'; import SubnavigationMeasureValue from './SubnavigationMeasureValue'; interface Props { + componentKey: string; measure: MeasureEnhanced; name: string; onChange: (metric: string) => 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} - <SubnavigationMeasureValue measure={measure} /> + <SubnavigationMeasureValue measure={measure} componentKey={componentKey} /> </SubnavigationItem> ); } 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<Props>) { - 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<Props>) { {domains.map((domain: Domain) => ( <DomainSubnavigation + componentKey={componentKey} domain={domain} key={domain.name} onChange={handleChangeMetric} diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/SubnavigationMeasureValue.tsx b/server/sonar-web/src/main/js/apps/component-measures/sidebar/SubnavigationMeasureValue.tsx index 9079b998aca..3ac980e5bea 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/SubnavigationMeasureValue.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/SubnavigationMeasureValue.tsx @@ -24,10 +24,11 @@ import { isDiffMetric } from '../../../helpers/measures'; import { MeasureEnhanced } from '../../../types/types'; interface Props { + componentKey: string; measure: MeasureEnhanced; } -export default function SubnavigationMeasureValue({ measure }: Readonly<Props>) { +export default function SubnavigationMeasureValue({ measure, componentKey }: Readonly<Props>) { const isDiff = isDiffMetric(measure.metric.key); const value = isDiff ? measure.leak : measure.value; @@ -37,6 +38,7 @@ export default function SubnavigationMeasureValue({ measure }: Readonly<Props>) id={`measure-${measure.metric.key}-${isDiff ? 'leak' : 'value'}`} > <Measure + componentKey={componentKey} badgeSize="xs" metricKey={measure.metric.key} metricType={measure.metric.type} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx index a6b421c8e38..cd576b12c52 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx @@ -21,7 +21,6 @@ import styled from '@emotion/styled'; import classNames from 'classnames'; import { LightLabel, - MetricsRatingBadge, NoDataIcon, SnoozeCircleIcon, TextError, @@ -39,10 +38,11 @@ import { getComponentSecurityHotspotsUrl, } from '~sonar-aligned/helpers/urls'; import { MetricKey, MetricType } from '~sonar-aligned/types/metrics'; +import RatingComponent from '../../../app/components/metrics/RatingComponent'; import { getLeakValue } from '../../../components/measure/utils'; import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; import { translate } from '../../../helpers/l10n'; -import { findMeasure, formatRating, isDiffMetric } from '../../../helpers/measures'; +import { findMeasure, isDiffMetric } from '../../../helpers/measures'; import { CodeScope, getComponentDrilldownUrl } from '../../../helpers/urls'; import { ApplicationPeriod } from '../../../types/application'; import { Branch } from '../../../types/branch-like'; @@ -291,9 +291,9 @@ export default function NewCodeMeasuresPanel(props: Readonly<Props>) { showRequired={!isApp} icon={ newSecurityReviewRating ? ( - <MetricsRatingBadge - label={newSecurityReviewRating} - rating={formatRating(newSecurityReviewRating)} + <RatingComponent + componentKey={component.key} + ratingMetric={MetricKey.new_security_review_rating} size="md" /> ) : ( 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<OverallCodeMeas showRequired={!isApp} icon={ securityRating ? ( - <MetricsRatingBadge - label={securityRating} - rating={formatRating(securityRating)} + <RatingComponent + componentKey={component.key} + ratingMetric={MetricKey.security_review_rating} size="md" /> ) : ( 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<Props> { }; 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<Props> { <MeasureIndicator className="sw-flex sw-justify-center sw-w-6 sw-mx-4" decimals={2} + componentKey={component.key} metricKey={measure.metric.key} metricType={measure.metric.type} value={actual} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx index 420f7e65e9e..17800780d9b 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx @@ -65,9 +65,6 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow (m) => 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<SoftwareImpactBreakdow <div className="sw-flex-grow sw-flex sw-justify-end"> <SoftwareImpactMeasureRating softwareQuality={softwareQuality} - value={ratingMeasure?.value} + componentKey={component.key} + ratingMetricKey={ratingMetricKey} /> </div> </div> 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<SoftwareImpactMeasureRatingProps>) { - const { softwareQuality, value } = props; + const { ratingMetricKey, componentKey } = props; - const intl = useIntl(); + // const intl = useIntl(); - const rating = formatRating(value); - - const additionalInfo = ( - <SoftwareImpactRatingTooltipContent rating={rating} softwareQuality={softwareQuality} /> - ); + // const additionalInfo = ( + // <SoftwareImpactRatingTooltipContent rating={rating} softwareQuality={softwareQuality} /> + // ); return ( <> - <Tooltip content={additionalInfo}> - <MetricsRatingBadge - size="md" - className="sw-text-sm" - rating={rating} - label={intl.formatMessage( - { - id: 'overview.project.software_impact.has_rating', - }, - { - softwareQuality: intl.formatMessage({ id: `software_quality.${softwareQuality}` }), - rating, - }, - )} - /> - </Tooltip> + {/* <Tooltip content={additionalInfo}> */} + <RatingComponent + size="md" + className="sw-text-sm" + ratingMetric={ratingMetricKey} + componentKey={componentKey} + // label={intl.formatMessage( + // { + // id: 'overview.project.software_impact.has_rating', + // }, + // { + // softwareQuality: intl.formatMessage({ id: `software_quality.${softwareQuality}` }), + // rating, + // }, + // )} + /> + {/* </Tooltip> */} {/* The badge is not interactive, so show the tooltip content for screen-readers only */} - <span className="sw-sr-only">{additionalInfo}</span> + {/* <span className="sw-sr-only">{additionalInfo}</span> */} </> ); } 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<Project, 'measures'>[]; 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<Project, 'measures'>[]; query: Query; total?: number; } -export default class ProjectsList extends React.PureComponent<Props> { - renderNoProjects() { - const { currentUser, isFavorite, isFiltered, query } = this.props; - if (isFiltered) { - return isFavorite ? <EmptyFavoriteSearch query={query} /> : <EmptySearch />; - } - return isFavorite ? <NoFavoriteProjects /> : <EmptyInstance currentUser={currentUser} />; - } +export default function ProjectsList(props: Readonly<Props>) { + 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 ( <div key="footer" style={{ ...style }}> <ListFooter loadMoreAriaLabel={translate('projects.show_more')} count={projects !== undefined ? projects.length : 0} - loadMore={this.props.loadMore} + loadMore={loadMore} loading={loading} ready={!loading} total={total ?? 0} @@ -78,6 +92,19 @@ export default class ProjectsList extends React.PureComponent<Props> { } 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<string, string>, + ) ?? {}; return ( <div @@ -88,53 +115,52 @@ export default class ProjectsList extends React.PureComponent<Props> { > <div className="sw-h-full" role="gridcell"> <ProjectCard - currentUser={this.props.currentUser} - handleFavorite={this.props.handleFavorite} + currentUser={currentUser} + handleFavorite={handleFavorite} key={project.key} - project={project} - type={this.props.cardType} + project={{ ...project, measures: componentMeasures }} + type={cardType} /> </div> </div> ); }; - renderList() { - return this.props.loading ? ( - <Spinner /> - ) : ( + if (projects.length === 0) { + if (isFiltered) { + return isFavorite ? <EmptyFavoriteSearch query={query} /> : <EmptySearch />; + } + return isFavorite ? <NoFavoriteProjects /> : <EmptyInstance currentUser={currentUser} />; + } + + return ( + <Spinner isLoading={loading || measuresLoading}> <AutoSizer> {({ height, width }) => ( <List aria-label={translate('project_plural')} height={height} overscanRowCount={2} - rowCount={this.props.projects.length + 1} + rowCount={projects.length + 1} rowHeight={({ index }) => { 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} /> )} </AutoSizer> - ); - } - - render() { - const { projects } = this.props; - - return projects.length > 0 ? this.renderList() : this.renderNoProjects(); - } + </Spinner> + ); } 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( <div> <span className="sw-body-sm-highlight sw-mr-1" data-key={MetricKey.new_lines}> <Measure + componentKey={key} metricKey={MetricKey.new_lines} metricType={MetricType.ShortInteger} value={measures.new_lines} @@ -189,6 +190,7 @@ function renderFirstLine( <div> <span className="sw-body-sm-highlight sw-mr-1" data-key={MetricKey.ncloc}> <Measure + componentKey={key} metricKey={MetricKey.ncloc} metricType={MetricType.ShortInteger} value={measures.ncloc} @@ -237,6 +239,7 @@ function renderSecondLine( <ProjectCardMeasures measures={measures} componentQualifier={qualifier} + componentKey={key} isNewCode={isNewCode} /> ); 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<string | undefined>; } 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`)} > <Measure + componentKey={componentKey} metricKey={MetricKey.new_violations} metricType={MetricType.ShortInteger} value={measures[MetricKey.new_violations]} @@ -65,7 +66,7 @@ function renderNewIssues(props: ProjectCardMeasuresProps) { } function renderCoverage(props: ProjectCardMeasuresProps) { - const { measures, isNewCode } = props; + const { measures, isNewCode, componentKey } = props; const coverageMetric = isNewCode ? MetricKey.new_coverage : MetricKey.coverage; return ( @@ -73,6 +74,7 @@ function renderCoverage(props: ProjectCardMeasuresProps) { <div> {measures[coverageMetric] && <CoverageIndicator value={measures[coverageMetric]} />} <Measure + componentKey={componentKey} metricKey={coverageMetric} metricType={MetricType.Percent} value={measures[coverageMetric]} @@ -84,7 +86,7 @@ function renderCoverage(props: ProjectCardMeasuresProps) { } function renderDuplication(props: ProjectCardMeasuresProps) { - const { measures, isNewCode } = props; + const { measures, isNewCode, componentKey } = props; const duplicationMetric = isNewCode ? MetricKey.new_duplicated_lines_density : MetricKey.duplicated_lines_density; @@ -102,6 +104,7 @@ function renderDuplication(props: ProjectCardMeasuresProps) { <div> {measures[duplicationMetric] != null && <DuplicationsIndicator rating={rating} />} <Measure + componentKey={componentKey} metricKey={duplicationMetric} metricType={MetricType.Percent} value={measures[duplicationMetric]} @@ -113,7 +116,7 @@ function renderDuplication(props: ProjectCardMeasuresProps) { } function renderRatings(props: ProjectCardMeasuresProps) { - const { isNewCode, measures } = props; + const { isNewCode, measures, componentKey } = props; const measuresByCodeLeak = isNewCode ? [] @@ -165,7 +168,6 @@ function renderRatings(props: ProjectCardMeasuresProps) { return measureList.map((measure) => { const { iconLabel, metricKey, metricRatingKey, metricType } = measure; - const value = formatRating(measures[metricRatingKey]); const measureValue = [ @@ -178,8 +180,9 @@ function renderRatings(props: ProjectCardMeasuresProps) { return ( <ProjectCardMeasure key={metricKey} metricKey={metricKey} label={iconLabel}> - <MetricsRatingBadge label={metricKey} rating={value as RatingLabel} /> + <RatingComponent ratingMetric={metricRatingKey} componentKey={componentKey} /> <Measure + componentKey={componentKey} metricKey={metricKey} metricType={metricType} value={measureValue} diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasures-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasures-test.tsx index c2bc48d9936..afbe8925074 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasures-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCardMeasures-test.tsx @@ -92,6 +92,7 @@ function renderProjectCardMeasures( renderComponent( <ProjectCardMeasures + componentKey="test" componentQualifier={ComponentQualifier.Project} isNewCode={false} measures={measures} 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 854d057f476..fa442cc28c4 100644 --- a/server/sonar-web/src/main/js/apps/projects/utils.ts +++ b/server/sonar-web/src/main/js/apps/projects/utils.ts @@ -20,9 +20,7 @@ import { invert } from 'lodash'; import { MetricKey } from '~sonar-aligned/types/metrics'; import { Facet, getScannableProjects, searchProjects } from '../../api/components'; -import { getMeasuresForProjects } from '../../api/measures'; import { translate, translateWithParameters } from '../../helpers/l10n'; -import { isDiffMetric } from '../../helpers/measures'; import { RequestData } from '../../helpers/request'; import { Dict } from '../../types/types'; import { Query, convertToFilter } from './query'; @@ -192,33 +190,14 @@ export function fetchProjects({ }); return searchProjects(data) - .then((response) => - 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<string> = {}; - 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<number> = {}; 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<Props>) { 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) { <CoverageIndicator value={hotspotsReviewedMeasure} /> )} - <Measure - className="it__hs-review-percentage sw-body-sm-highlight sw-ml-2" - metricKey={ - isBranch(branchLike) && !filters.inNewCodePeriod - ? MetricKey.security_hotspots_reviewed - : MetricKey.new_security_hotspots_reviewed - } - metricType={MetricType.Percent} - value={hotspotsReviewedMeasure} - /> + {component && ( + <Measure + className="it__hs-review-percentage sw-body-sm-highlight sw-ml-2" + componentKey={component.key} + metricKey={ + isBranch(branchLike) && !filters.inNewCodePeriod + ? MetricKey.security_hotspots_reviewed + : MetricKey.new_security_hotspots_reviewed + } + metricType={MetricType.Percent} + value={hotspotsReviewedMeasure} + /> + )} </Spinner> <span className="sw-body-sm sw-ml-1"> 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<Props>) { const [success, setSuccess] = React.useState(false); const [changedValue, setChangedValue] = React.useState<FieldValue>(); const [validationMessage, setValidationMessage] = React.useState<string>(); - 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<Props>) { 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<Props>) { 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' && |