diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2023-06-06 16:11:38 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-06-09 20:03:10 +0000 |
commit | f0347a3cf3e278c3d7ddb263a5dd0062b9421bff (patch) | |
tree | 8a802cd16eacd6554a36e81209fda1ca5ba77f72 /server/sonar-web/src/main | |
parent | 2aaaff3deb00d311cf6fa1c871e7ea11c4871070 (diff) | |
download | sonarqube-f0347a3cf3e278c3d7ddb263a5dd0062b9421bff.tar.gz sonarqube-f0347a3cf3e278c3d7ddb263a5dd0062b9421bff.zip |
SONAR-19472 Migrate the component table to the new UI
Diffstat (limited to 'server/sonar-web/src/main')
12 files changed, 292 insertions, 335 deletions
diff --git a/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts b/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts index bb2b888e1aa..61b202ab4dc 100644 --- a/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts +++ b/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts @@ -78,7 +78,7 @@ it('should allow navigating through the tree', async () => { // Navigate by clicking on an element. await ui.clickOnChildComponent(/folderA$/); - expect(await ui.childComponent(/out\.tsx/).find()).toBeInTheDocument(); + expect(await ui.childComponent(/out\.tsx/).findAll()).toHaveLength(2); // One for the pin, one for the name column // Navigate back using the breadcrumb. await ui.clickOnBreadcrumb(/Foo$/); diff --git a/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx b/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx index f440a027ece..47cab3fba89 100644 --- a/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx @@ -17,8 +17,14 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import classNames from 'classnames'; -import { FlagMessage, HelperHintIcon, LargeCenteredLayout } from 'design-system'; +import { + Card, + DeferredSpinner, + FlagMessage, + HelperHintIcon, + LargeCenteredLayout, + LightLabel, +} from 'design-system'; import { intersection } from 'lodash'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; @@ -36,7 +42,6 @@ import { getCodeMetrics } from '../utils'; import CodeBreadcrumbs from './CodeBreadcrumbs'; import Components from './Components'; import Search from './Search'; -import SearchResults from './SearchResults'; import SourceViewerWrapper from './SourceViewerWrapper'; interface Props { @@ -90,11 +95,6 @@ export default function CodeAppRenderer(props: Props) { const showComponentList = sourceViewer === undefined && components.length > 0 && !showSearch; - const componentsClassName = classNames('boxed-group', 'spacer-top', { - 'new-loading': loading, - 'search-results': showSearch, - }); - const metricKeys = intersection( getCodeMetrics(component.qualifier, branchLike, { newCode: newCodeSelected }), Object.keys(metrics) @@ -111,7 +111,7 @@ export default function CodeAppRenderer(props: Props) { const isPortfolio = isPortfolioLike(qualifier); return ( - <LargeCenteredLayout className="sw-py-8 sw-body-md"> + <LargeCenteredLayout className="sw-py-8 sw-body-md" id="code-page"> <Suggestions suggestions="code" /> <Helmet defer={false} title={sourceViewer !== undefined ? sourceViewer.name : defaultTitle} /> @@ -145,15 +145,15 @@ export default function CodeAppRenderer(props: Props) { /> )} - <div className="code-components"> + <div> {!hasComponents && sourceViewer === undefined && ( - <div className="display-flex-center display-flex-column no-file"> - <span className="h1 text-muted"> + <div className="sw-flex sw-align-center sw-flex-col sw-fixed sw-top-1/2"> + <LightLabel> {translate( 'code_viewer.no_source_code_displayed_due_to_empty_analysis', component.qualifier )} - </span> + </LightLabel> </div> )} @@ -165,47 +165,46 @@ export default function CodeAppRenderer(props: Props) { /> )} - <div className={componentsClassName}> - {showComponentList && ( - <Components - baseComponent={baseComponent} - branchLike={branchLike} - components={components} - cycle - metrics={filteredMetrics} - onEndOfList={props.handleLoadMore} - onGoToParent={props.handleGoToParent} - onHighlight={props.handleHighlight} - onSelect={props.handleSelect} - rootComponent={component} - selected={highlighted} - newCodeSelected={newCodeSelected} - showAnalysisDate={isPortfolio} - /> - )} - - {showSearch && ( - <SearchResults - branchLike={branchLike} - components={searchResults} - onHighlight={props.handleHighlight} - onSelect={props.handleSelect} - rootComponent={component} - selected={highlighted} - /> - )} - - <div role="status" className={showSearch ? 'text-center big-padded-bottom' : undefined}> - {searchResults?.length === 0 && translate('no_results')} - </div> - </div> + <Card className="sw-mt-2"> + <DeferredSpinner loading={loading}> + {showComponentList && ( + <Components + baseComponent={baseComponent} + branchLike={branchLike} + components={components} + cycle + metrics={filteredMetrics} + onEndOfList={props.handleLoadMore} + onGoToParent={props.handleGoToParent} + onHighlight={props.handleHighlight} + onSelect={props.handleSelect} + rootComponent={component} + selected={highlighted} + newCodeSelected={newCodeSelected} + showAnalysisDate={isPortfolio} + /> + )} + + {showSearch && ( + <Components + branchLike={branchLike} + components={searchResults} + metrics={[]} + onHighlight={props.handleHighlight} + onSelect={props.handleSelect} + rootComponent={component} + selected={highlighted} + /> + )} + </DeferredSpinner> + </Card> {showComponentList && ( <ListFooter count={components.length} loadMore={props.handleLoadMore} total={total} /> )} {sourceViewer !== undefined && !showSearch && ( - <div className="spacer-top"> + <div className="sw-mt-2"> <SourceViewerWrapper branchLike={branchLike} component={sourceViewer.key} diff --git a/server/sonar-web/src/main/js/apps/code/components/Component.tsx b/server/sonar-web/src/main/js/apps/code/components/Component.tsx index d42aa9fb47b..f18c72bc6fc 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Component.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/Component.tsx @@ -17,14 +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 classNames from 'classnames'; +import { ContentCell, NumericalCell, TableRowInteractive } from 'design-system'; import * as React from 'react'; -import { withScrollTo } from '../../../components/hoc/withScrollTo'; import DateFromNow from '../../../components/intl/DateFromNow'; import { WorkspaceContext } from '../../../components/workspace/context'; import { BranchLike } from '../../../types/branch-like'; import { ComponentQualifier } from '../../../types/component'; -import { ComponentMeasure as TypeComponentMeasure, Metric } from '../../../types/types'; +import { Metric, ComponentMeasure as TypeComponentMeasure } from '../../../types/types'; import ComponentMeasure from './ComponentMeasure'; import ComponentName from './ComponentName'; import ComponentPin from './ComponentPin'; @@ -34,7 +33,6 @@ interface Props { canBePinned?: boolean; canBrowse?: boolean; component: TypeComponentMeasure; - hasBaseComponent: boolean; isBaseComponent?: boolean; metrics: Metric[]; previous?: TypeComponentMeasure; @@ -44,75 +42,64 @@ interface Props { showAnalysisDate?: boolean; } -class Component extends React.PureComponent<Props> { - render() { - const { - branchLike, - canBePinned = true, - canBrowse = false, - component, - hasBaseComponent, - isBaseComponent = false, - metrics, - previous, - rootComponent, - selected = false, - newCodeSelected, - showAnalysisDate, - } = this.props; +export default function Component(props: Props) { + const { + branchLike, + canBePinned = true, + canBrowse = false, + component, + isBaseComponent = false, + metrics, + previous, + rootComponent, + selected = false, + newCodeSelected, + showAnalysisDate, + } = props; - const isFile = - component.qualifier === ComponentQualifier.File || - component.qualifier === ComponentQualifier.TestFile; + const isFile = + component.qualifier === ComponentQualifier.File || + component.qualifier === ComponentQualifier.TestFile; - return ( - <tr className={classNames({ selected, 'current-folder': isBaseComponent })}> - {canBePinned && ( - <td className="thin nowrap"> - {isFile && ( - <WorkspaceContext.Consumer> - {({ openComponent }) => ( - <ComponentPin - branchLike={branchLike} - component={component} - openComponent={openComponent} - /> - )} - </WorkspaceContext.Consumer> - )} - </td> - )} - <td className="code-name-cell"> - <div className="display-flex-center"> - {hasBaseComponent && <div className="code-child-component-icon" />} - <ComponentName - branchLike={branchLike} - canBrowse={canBrowse} - component={component} - previous={previous} - rootComponent={rootComponent} - unclickable={isBaseComponent} - newCodeSelected={newCodeSelected} - /> - </div> - </td> + return ( + <TableRowInteractive selected={selected}> + {canBePinned && ( + <ContentCell className="sw-py-3"> + {isFile && ( + <WorkspaceContext.Consumer> + {({ openComponent }) => ( + <ComponentPin + branchLike={branchLike} + component={component} + openComponent={openComponent} + /> + )} + </WorkspaceContext.Consumer> + )} + </ContentCell> + )} + <ContentCell className="it__code-name-cell sw-overflow-hidden"> + <ComponentName + branchLike={branchLike} + canBrowse={canBrowse} + component={component} + previous={previous} + rootComponent={rootComponent} + unclickable={isBaseComponent} + newCodeSelected={newCodeSelected} + /> + </ContentCell> - {metrics.map((metric) => ( - <td className="text-center" key={metric.key}> - <ComponentMeasure component={component} metric={metric} /> - </td> - ))} + {metrics.map((metric) => ( + <ComponentMeasure component={component} key={metric.key} metric={metric} /> + ))} - {showAnalysisDate && isBaseComponent && <td />} - - {showAnalysisDate && !isBaseComponent && ( - <td className="text-center"> - {component.analysisDate ? <DateFromNow date={component.analysisDate} /> : '—'} - </td> - )} - </tr> - ); - } + {showAnalysisDate && ( + <NumericalCell> + {!isBaseComponent && + (component.analysisDate ? <DateFromNow date={component.analysisDate} /> : '—')} + </NumericalCell> + )} + </TableRowInteractive> + ); } - -export default withScrollTo(Component); 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 3b2392fbc57..e6ec611dc88 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,35 +17,73 @@ * 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, + MetricsEnum, + MetricsRatingBadge, + NumericalCell, + QualityGateIndicator, + RatingCell, +} from 'design-system'; import * as React from 'react'; import Measure from '../../../components/measure/Measure'; import { getLeakValue } from '../../../components/measure/utils'; -import { isDiffMetric } from '../../../helpers/measures'; -import { ComponentMeasure as TypeComponentMeasure, Metric } from '../../../types/types'; +import { translateWithParameters } from '../../../helpers/l10n'; +import { formatMeasure, isDiffMetric } from '../../../helpers/measures'; +import { isApplication, isProject } from '../../../types/component'; +import { MetricKey, MetricType } from '../../../types/metrics'; +import { Metric, Status, ComponentMeasure as TypeComponentMeasure } from '../../../types/types'; interface Props { component: TypeComponentMeasure; metric: Metric; } -export default class ComponentMeasure extends React.PureComponent<Props> { - render() { - const { component, metric } = this.props; - const isProject = component.qualifier === 'TRK'; - const isReleasability = metric.key === 'releasability_rating'; +export default function ComponentMeasure(props: Props) { + const { component, metric } = props; + const isProjectLike = isProject(component.qualifier) || isApplication(component.qualifier); + const isReleasability = metric.key === MetricKey.releasability_rating; - const finalMetricKey = isProject && isReleasability ? 'alert_status' : metric.key; - const finalMetricType = isProject && isReleasability ? 'LEVEL' : metric.type; + const finalMetricKey = isProjectLike && isReleasability ? MetricKey.alert_status : metric.key; + const finalMetricType = isProjectLike && isReleasability ? MetricType.Level : metric.type; - const measure = - Array.isArray(component.measures) && - component.measures.find((measure) => measure.metric === finalMetricKey); + const measure = Array.isArray(component.measures) + ? component.measures.find((measure) => measure.metric === finalMetricKey) + : undefined; - if (!measure) { - return measure === false ? <span /> : <span>—</span>; - } + const value = isDiffMetric(metric.key) ? getLeakValue(measure) : measure?.value; + + switch (finalMetricType) { + case MetricType.Level: { + const formatted = formatMeasure(value, MetricType.Level); + const ariaLabel = translateWithParameters('overview.quality_gate_x', formatted); - const value = isDiffMetric(metric.key) ? getLeakValue(measure) : measure.value; - return <Measure metricKey={finalMetricKey} metricType={finalMetricType} value={value} />; + return ( + <ContentCell> + <QualityGateIndicator + status={(value as Status) ?? 'NONE'} + className="sw-mr-2" + ariaLabel={ariaLabel} + size="sm" + /> + <span>{formatted}</span> + </ContentCell> + ); + } + case MetricType.Rating: + return ( + <RatingCell> + <MetricsRatingBadge + label={value ?? '—'} + rating={formatMeasure(value, MetricType.Rating) as MetricsEnum} + /> + </RatingCell> + ); + default: + return ( + <NumericalCell> + <Measure metricKey={finalMetricKey} metricType={finalMetricType} value={value} /> + </NumericalCell> + ); } } diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx index dbe0000d59d..d3a5baf2cef 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx @@ -17,9 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { HoverLink, LightLabel, QualifierIcon } from 'design-system'; +import { Badge, BranchIcon, HoverLink, LightLabel, Note, QualifierIcon } from 'design-system'; import * as React from 'react'; -import BranchIcon from '../../../components/icons/BranchIcon'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { translate } from '../../../helpers/l10n'; import { CodeScope, getComponentOverviewUrl, queryToSearch } from '../../../helpers/urls'; @@ -75,8 +74,8 @@ export default function ComponentName({ ) ) { return ( - <span className="max-width-100 display-inline-flex-center"> - <span className="text-ellipsis" title={getTooltip(component)} aria-label={ariaLabel}> + <span className="sw-flex sw-items-center sw-overflow-hidden"> + <div className="sw-truncate" title={getTooltip(component)} aria-label={ariaLabel}> {renderNameWithIcon( branchLike, component, @@ -86,14 +85,16 @@ export default function ComponentName({ canBrowse, newCodeSelected )} - </span> + </div> {component.branch ? ( - <span className="text-ellipsis spacer-left"> + <div className="sw-truncate sw-ml-2"> <BranchIcon className="sw-mr-1" /> - <span className="note">{component.branch}</span> - </span> + <Note>{component.branch}</Note> + </div> ) : ( - <span className="spacer-left badge flex-1">{translate('branches.main_branch')}</span> + <Badge className="sw-ml-1" variant="default"> + {translate('branches.main_branch')} + </Badge> )} </span> ); @@ -129,6 +130,7 @@ function renderNameWithIcon( : undefined; return ( <HoverLink + icon={<QualifierIcon className="sw-mr-2" qualifier={component.qualifier} />} to={getComponentOverviewUrl( component.refKey ?? component.key, component.qualifier, @@ -136,8 +138,7 @@ function renderNameWithIcon( codeType )} > - <QualifierIcon className="sw-mr-1" qualifier={component.qualifier} /> - <span>{name}</span> + {name} </HoverLink> ); } else if (canBrowse) { @@ -146,15 +147,17 @@ function renderNameWithIcon( Object.assign(query, { selected: component.key }); } return ( - <HoverLink to={{ pathname: '/code', search: queryToSearch(query) }}> - <QualifierIcon className="sw-mr-1" qualifier={component.qualifier} /> - <span>{name}</span> + <HoverLink + icon={<QualifierIcon className="sw-mr-2" qualifier={component.qualifier} />} + to={{ pathname: '/code', search: queryToSearch(query) }} + > + {name} </HoverLink> ); } return ( - <span className="sw-flex sw-items-center"> - <QualifierIcon className="sw-mr-1" qualifier={component.qualifier} /> + <span> + <QualifierIcon className="sw-mr-2 sw-align-text-bottom" qualifier={component.qualifier} /> {name} </span> ); diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx index 05a31151eb0..15de4e4d04c 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx @@ -17,10 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { InteractiveIcon, PinIcon } from 'design-system'; import * as React from 'react'; -import theme from '../../../app/theme'; -import { ButtonPlain } from '../../../components/controls/buttons'; -import PinIcon from '../../../components/icons/PinIcon'; import { WorkspaceContextShape } from '../../../components/workspace/context'; import { translateWithParameters } from '../../../helpers/l10n'; import { BranchLike } from '../../../types/branch-like'; @@ -32,27 +30,23 @@ interface Props { openComponent: WorkspaceContextShape['openComponent']; } -export default class ComponentPin extends React.PureComponent<Props> { - handleClick = () => { - this.props.openComponent({ - branchLike: this.props.branchLike, - key: this.props.component.key, - name: this.props.component.path, - qualifier: this.props.component.qualifier, +export default function ComponentPin(props: Props) { + const { branchLike, component, openComponent } = props; + + const handleClick = React.useCallback(() => { + openComponent({ + branchLike, + key: component.key, + name: component.path, + qualifier: component.qualifier, }); - }; + }, [branchLike, component, openComponent]); + + const label = translateWithParameters('component_viewer.open_in_workspace_X', component.name); - render() { - const { name } = this.props.component; - return ( - <ButtonPlain - className="link-no-underline" - preventDefault - onClick={this.handleClick} - title={translateWithParameters('component_viewer.open_in_workspace_X', name)} - > - <PinIcon fill={theme.colors.primary} /> - </ButtonPlain> - ); - } + return ( + <span title={label}> + <InteractiveIcon aria-label={label} Icon={PinIcon} onClick={handleClick} /> + </span> + ); } diff --git a/server/sonar-web/src/main/js/apps/code/components/Components.tsx b/server/sonar-web/src/main/js/apps/code/components/Components.tsx index 738cc70f5a9..588d0c8c198 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Components.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/Components.tsx @@ -17,6 +17,7 @@ * 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, Table, TableRow } from 'design-system'; import { sortBy } from 'lodash'; import * as React from 'react'; import withKeyboardNavigation from '../../../components/hoc/withKeyboardNavigation'; @@ -52,7 +53,7 @@ function Components(props: ComponentsProps) { } = props; const canBePinned = - baseComponent && + baseComponent !== undefined && ![ ComponentQualifier.Application, ComponentQualifier.Portfolio, @@ -61,69 +62,71 @@ function Components(props: ComponentsProps) { return ( <div className="big-spacer-bottom table-wrapper"> - <table className="data zebra"> - {baseComponent && ( - <ComponentsHeader - baseComponent={baseComponent} - canBePinned={canBePinned} - metrics={metrics.map((metric) => metric.key)} - rootComponent={rootComponent} - showAnalysisDate={showAnalysisDate} - /> - )} - <tbody> - {baseComponent && ( - <> - <Component - branchLike={branchLike} + <Table + gridTemplate={`${canBePinned ? 'min-content' : ''} auto repeat(${ + metrics.length + (showAnalysisDate ? 1 : 0) + }, max-content)`} + header={ + baseComponent && ( + <TableRow> + <ComponentsHeader + baseComponent={baseComponent} canBePinned={canBePinned} - component={baseComponent} - hasBaseComponent={false} - isBaseComponent - key={baseComponent.key} - metrics={metrics} + metrics={metrics.map((metric) => metric.key)} rootComponent={rootComponent} - newCodeSelected={newCodeSelected} showAnalysisDate={showAnalysisDate} /> - <tr className="blank"> - <td - colSpan={metrics.length + 1 + (canBePinned ? 1 : 0) + (showAnalysisDate ? 1 : 0)} - /> - </tr> - </> - )} + </TableRow> + ) + } + > + {baseComponent && ( + <> + <Component + branchLike={branchLike} + canBePinned={canBePinned} + component={baseComponent} + isBaseComponent + key={baseComponent.key} + metrics={metrics} + rootComponent={rootComponent} + newCodeSelected={newCodeSelected} + showAnalysisDate={showAnalysisDate} + /> + <TableRow> + <ContentCell className="sw-col-span-full" /> + </TableRow> + </> + )} - {components.length ? ( - sortBy( - components, - (c) => c.qualifier, - (c) => c.name.toLowerCase(), - (c) => (c.branch ? c.branch.toLowerCase() : '') - ).map((component, index, list) => ( - <Component - branchLike={branchLike} - canBePinned={canBePinned} - canBrowse - component={component} - hasBaseComponent={baseComponent !== undefined} - key={getComponentMeasureUniqueKey(component)} - metrics={metrics} - previous={index > 0 ? list[index - 1] : undefined} - rootComponent={rootComponent} - newCodeSelected={newCodeSelected} - showAnalysisDate={showAnalysisDate} - selected={ - selected && - getComponentMeasureUniqueKey(component) === getComponentMeasureUniqueKey(selected) - } - /> - )) - ) : ( - <ComponentsEmpty canBePinned={canBePinned} /> - )} - </tbody> - </table> + {components.length ? ( + sortBy( + components, + (c) => c.qualifier, + (c) => c.name.toLowerCase(), + (c) => (c.branch ? c.branch.toLowerCase() : '') + ).map((component, index, list) => ( + <Component + branchLike={branchLike} + canBePinned={canBePinned} + canBrowse + component={component} + key={getComponentMeasureUniqueKey(component)} + metrics={metrics} + previous={index > 0 ? list[index - 1] : undefined} + rootComponent={rootComponent} + newCodeSelected={newCodeSelected} + showAnalysisDate={showAnalysisDate} + selected={ + selected && + getComponentMeasureUniqueKey(component) === getComponentMeasureUniqueKey(selected) + } + /> + )) + ) : ( + <ComponentsEmpty /> + )} + </Table> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentsEmpty.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentsEmpty.tsx index c6214240a13..ea492fc6c2e 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentsEmpty.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentsEmpty.tsx @@ -17,20 +17,16 @@ * 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, Note, TableRow } from 'design-system'; import * as React from 'react'; import { translate } from '../../../helpers/l10n'; -interface Props { - canBePinned?: boolean; -} - -export default function ComponentsEmpty({ canBePinned = true }: Props) { +export default function ComponentsEmpty() { return ( - <tr> - {canBePinned && <td />} - <td className="note" colSpan={10}> - {translate('no_results')} - </td> - </tr> + <TableRow> + <ContentCell className="sw-col-span-full"> + <Note role="status">{translate('no_results')}</Note> + </ContentCell> + </TableRow> ); } diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.tsx index 6041ebe807f..d37f4a9c544 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.tsx @@ -17,9 +17,11 @@ * 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, NumericalCell, RatingCell } from 'design-system'; import * as React from 'react'; import { translate } from '../../../helpers/l10n'; import { isPortfolioLike } from '../../../types/component'; +import { MetricKey } from '../../../types/metrics'; import { ComponentMeasure } from '../../../types/types'; interface ComponentsHeaderProps { @@ -31,16 +33,17 @@ interface ComponentsHeaderProps { } const SHORT_NAME_METRICS = [ - 'duplicated_lines_density', - 'new_lines', - 'new_coverage', - 'new_duplicated_lines_density', + MetricKey.duplicated_lines_density, + MetricKey.new_lines, + MetricKey.new_coverage, + MetricKey.new_duplicated_lines_density, ]; export default function ComponentsHeader(props: ComponentsHeaderProps) { const { baseComponent, canBePinned = true, metrics, rootComponent, showAnalysisDate } = props; const isPortfolio = isPortfolioLike(rootComponent.qualifier); let columns: string[] = []; + let Cell: typeof NumericalCell; if (isPortfolio) { columns = [ translate('metric_domain.Releasability'), @@ -54,24 +57,25 @@ export default function ComponentsHeader(props: ComponentsHeaderProps) { if (showAnalysisDate) { columns.push(translate('code.last_analysis_date')); } + + Cell = RatingCell; } else { columns = metrics.map((metric) => - translate('metric', metric, SHORT_NAME_METRICS.includes(metric) ? 'short_name' : 'name') + translate( + 'metric', + metric, + SHORT_NAME_METRICS.includes(metric as MetricKey) ? 'short_name' : 'name' + ) ); + + Cell = NumericalCell; } return ( - <thead> - <tr className="code-components-header"> - {canBePinned && <th className="thin" aria-label={translate('code.pin')} />} - <th className="code-name-cell" aria-label={translate('code.name')} /> - {baseComponent && - columns.map((column) => ( - <th className="text-center" key={column}> - {column} - </th> - ))} - </tr> - </thead> + <> + {canBePinned && <ContentCell aria-label={translate('code.pin')} />} + <ContentCell aria-label={translate('code.name')} /> + {baseComponent && columns.map((column) => <Cell key={column}>{column}</Cell>)} + </> ); } diff --git a/server/sonar-web/src/main/js/apps/code/components/SearchResults.tsx b/server/sonar-web/src/main/js/apps/code/components/SearchResults.tsx deleted file mode 100644 index db9e03c4672..00000000000 --- a/server/sonar-web/src/main/js/apps/code/components/SearchResults.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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 classNames from 'classnames'; -import { sortBy } from 'lodash'; -import * as React from 'react'; -import withKeyboardNavigation, { - WithKeyboardNavigationProps, -} from '../../../components/hoc/withKeyboardNavigation'; -import { getComponentMeasureUniqueKey } from '../../../helpers/component'; -import { BranchLike } from '../../../types/branch-like'; -import { ComponentMeasure } from '../../../types/types'; -import ComponentName from './ComponentName'; - -export interface SearchResultsProps extends WithKeyboardNavigationProps { - branchLike?: BranchLike; - rootComponent: ComponentMeasure; - newCodeSelected?: boolean; -} - -function SearchResults(props: SearchResultsProps) { - const { branchLike, components, newCodeSelected, rootComponent, selected } = props; - - return ( - <ul> - {components && - components.length > 0 && - sortBy( - components, - (c) => c.qualifier, - (c) => c.name.toLowerCase(), - (c) => (c.branch ? c.branch.toLowerCase() : '') - ).map((component) => ( - <li - className={classNames({ selected: selected?.key === component.key })} - key={getComponentMeasureUniqueKey(component)} - > - <ComponentName - branchLike={branchLike} - canBrowse - component={component} - rootComponent={rootComponent} - newCodeSelected={newCodeSelected} - /> - </li> - ))} - </ul> - ); -} - -export default withKeyboardNavigation(SearchResults); diff --git a/server/sonar-web/src/main/js/components/measure/Measure.tsx b/server/sonar-web/src/main/js/components/measure/Measure.tsx index bf83d336a6a..4ed74baf0ae 100644 --- a/server/sonar-web/src/main/js/components/measure/Measure.tsx +++ b/server/sonar-web/src/main/js/components/measure/Measure.tsx @@ -45,7 +45,7 @@ export default function Measure({ ratingComponent, }: Props) { if (value === undefined) { - return <span className={className}>–</span>; + return <span className={className}>—</span>; } if (metricType === MetricType.Level) { @@ -57,11 +57,11 @@ export default function Measure({ decimals, omitExtraDecimalZeros: metricType === MetricType.Percent, }); - return <span className={className}>{formattedValue != null ? formattedValue : '–'}</span>; + return <span className={className}>{formattedValue ?? '—'}</span>; } const tooltip = <RatingTooltipContent metricKey={metricKey} value={value} />; - const rating = ratingComponent || <Rating value={value} />; + const rating = ratingComponent ?? <Rating value={value} />; if (tooltip) { return ( diff --git a/server/sonar-web/src/main/js/components/measure/__tests__/__snapshots__/Measure-test.tsx.snap b/server/sonar-web/src/main/js/components/measure/__tests__/__snapshots__/Measure-test.tsx.snap index 29e685efb6b..3a30c9bed84 100644 --- a/server/sonar-web/src/main/js/components/measure/__tests__/__snapshots__/Measure-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/measure/__tests__/__snapshots__/Measure-test.tsx.snap @@ -37,6 +37,6 @@ exports[`renders trivial measure 1`] = ` exports[`renders undefined measure 1`] = ` <span> - – + — </span> `; |