export function MetricsRatingBadge({ className, size = 'sm', label, rating, ...ariaAttrs }: Props) {
if (!rating) {
return (
- <span aria-label={label} className={className} {...ariaAttrs}>
- –
- </span>
+ <StyledNoRatingBadge
+ aria-label={label}
+ className={className}
+ size={SIZE_MAPPING[size]}
+ {...ariaAttrs}
+ >
+ —
+ </StyledNoRatingBadge>
);
}
return (
);
}
+const StyledNoRatingBadge = styled.div<{ size: string }>`
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+
+ width: ${getProp('size')};
+ height: ${getProp('size')};
+`;
+
const MetricsRatingBadgeStyled = styled.div<{ rating: MetricsLabel; size: string }>`
width: ${getProp('size')};
height: ${getProp('size')};
font-size: ${({ size }) => (size === '2rem' ? '0.875rem' : '0.75rem')};
background-color: ${({ rating }) => themeColor(`rating.${rating}`)};
- ${tw`sw-inline-flex sw-items-center sw-justify-center`};
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+
${tw`sw-rounded-pill`};
${tw`sw-font-semibold`};
`;
`;
export const ContentCell = styled(CellComponent)`
- ${tw`sw-text-left`}
+ ${tw`sw-text-left sw-justify-start`}
`;
export const NumericalCell = styled(CellComponent)`
- ${tw`sw-text-right`}
+ ${tw`sw-text-right sw-justify-end`}
`;
export const RatingCell = styled(CellComponent)`
- ${tw`sw-text-right`}
+ ${tw`sw-text-right sw-justify-end`}
`;
export const CheckboxCell = styled(CellComponent)`
${tw`sw-text-center`}
const CellComponentStyled = styled.td`
color: ${themeColor('pageContent')};
-
+ ${tw`sw-flex sw-items-center`}
${tw`sw-body-sm`}
${tw`sw-py-4 sw-px-2`}
${tw`sw-align-top`}
--- /dev/null
+/*
+ * 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 { PinIcon as OcticonPinIcon } from '@primer/octicons-react';
+import { OcticonHoc } from './Icon';
+
+export const PinIcon = OcticonHoc(OcticonPinIcon, 'PinIcon');
export { OverviewQGNotComputedIcon } from './OverviewQGNotComputedIcon';
export { OverviewQGPassedIcon } from './OverviewQGPassedIcon';
export { PencilIcon } from './PencilIcon';
+export { PinIcon } from './PinIcon';
export { ProjectIcon } from './ProjectIcon';
export { PullRequestIcon } from './PullRequestIcon';
export { QualifierIcon } from './QualifierIcon';
// 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$/);
* 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';
import CodeBreadcrumbs from './CodeBreadcrumbs';
import Components from './Components';
import Search from './Search';
-import SearchResults from './SearchResults';
import SourceViewerWrapper from './SourceViewerWrapper';
interface 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)
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} />
/>
)}
- <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>
)}
/>
)}
- <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}
* 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';
canBePinned?: boolean;
canBrowse?: boolean;
component: TypeComponentMeasure;
- hasBaseComponent: boolean;
isBaseComponent?: boolean;
metrics: Metric[];
previous?: TypeComponentMeasure;
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);
* 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>
+ );
}
}
* 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';
)
) {
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,
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>
);
: undefined;
return (
<HoverLink
+ icon={<QualifierIcon className="sw-mr-2" qualifier={component.qualifier} />}
to={getComponentOverviewUrl(
component.refKey ?? component.key,
component.qualifier,
codeType
)}
>
- <QualifierIcon className="sw-mr-1" qualifier={component.qualifier} />
- <span>{name}</span>
+ {name}
</HoverLink>
);
} else if (canBrowse) {
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>
);
* 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';
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>
+ );
}
* 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';
} = props;
const canBePinned =
- baseComponent &&
+ baseComponent !== undefined &&
![
ComponentQualifier.Application,
ComponentQualifier.Portfolio,
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>
);
}
* 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>
);
}
* 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 {
}
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'),
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>)}
+ </>
);
}
+++ /dev/null
-/*
- * 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);
ratingComponent,
}: Props) {
if (value === undefined) {
- return <span className={className}>â\80\93</span>;
+ return <span className={className}>â\80\94</span>;
}
if (metricType === MetricType.Level) {
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 (
exports[`renders undefined measure 1`] = `
<span>
- â\80\93
+ â\80\94
</span>
`;