* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { useTheme } from '@emotion/react';
+import styled from '@emotion/styled';
+import {
+ IconArrowDownRight,
+ IconArrowUpRight,
+ IconEqual,
+ IconProps,
+} from '@sonarsource/echoes-react';
import { themeColor } from '../../helpers/theme';
-import { CustomIcon, IconProps } from './Icon';
+import { ThemeColors } from '../../types';
export const enum TrendDirection {
Down = 'down',
}
export function TrendIcon(props: Readonly<Props>) {
- const theme = useTheme();
const { direction, type, ...iconProps } = props;
- const fill = themeColor(
- (
- {
- [TrendType.Positive]: 'iconTrendPositive',
- [TrendType.Negative]: 'iconTrendNegative',
- [TrendType.Neutral]: 'iconTrendNeutral',
- [TrendType.Disabled]: 'iconTrendDisabled',
- } as const
- )[type],
- )({ theme });
+ if (direction === TrendDirection.Up) {
+ return (
+ <TrendIconWrapper trendType={type}>
+ <IconArrowUpRight aria-label="trend-up" {...iconProps} />
+ </TrendIconWrapper>
+ );
+ }
+
+ if (direction === TrendDirection.Down) {
+ return (
+ <TrendIconWrapper trendType={type}>
+ <IconArrowDownRight aria-label="trend-down" {...iconProps} />
+ </TrendIconWrapper>
+ );
+ }
return (
- <CustomIcon {...iconProps}>
- {direction === TrendDirection.Up && (
- <path
- aria-label="trend-up"
- clipRule="evenodd"
- d="M4.75802 4.3611a.74997.74997 0 0 1 .74953-.74953H11.518a.74985.74985 0 0 1 .5298.21967.74967.74967 0 0 1 .2197.52986v6.0104a.75043.75043 0 0 1-.2286.5132.75053.75053 0 0 1-.5209.2104.75017.75017 0 0 1-.5209-.2104.75054.75054 0 0 1-.2287-.5132V6.1713l-5.26085 5.2609a.75014.75014 0 0 1-1.06066 0 .75004.75004 0 0 1 0-1.0607l5.26088-5.26086H5.50755a.75001.75001 0 0 1-.74953-.74954Z"
- fill={fill}
- fillRule="evenodd"
- />
- )}
- {direction === TrendDirection.Down && (
- <path
- aria-label="trend-down"
- clipRule="evenodd"
- d="M11.5052 4.14237a.75026.75026 0 0 1 .5299.21967.74997.74997 0 0 1 .2196.52986v6.0104a.7501.7501 0 0 1-.2196.5299.75027.75027 0 0 1-.5299.2196H5.49479a.74976.74976 0 0 1-.51314-.2286.75004.75004 0 0 1 0-1.0418.74976.74976 0 0 1 .51314-.2286h4.20022L4.43413 4.8919a.75001.75001 0 0 1 1.06066-1.06066l5.26091 5.26087V4.8919a.74997.74997 0 0 1 .2196-.52986.75008.75008 0 0 1 .5299-.21967Z"
- fill={fill}
- fillRule="evenodd"
- />
- )}
- {direction === TrendDirection.Equal && (
- <g aria-label="trend-equal">
- <rect fill={fill} height="1.5" rx=".75" width="8" x="4" y="5" />
- <rect fill={fill} height="1.5" rx=".75" width="8" x="4" y="9" />
- </g>
- )}
- </CustomIcon>
+ <TrendIconWrapper trendType={type}>
+ <IconEqual aria-label="trend-equal" {...iconProps} />
+ </TrendIconWrapper>
);
}
+
+const ICON_COLORS: Record<TrendType, ThemeColors> = {
+ [TrendType.Positive]: 'iconTrendPositive',
+ [TrendType.Negative]: 'iconTrendNegative',
+ [TrendType.Neutral]: 'iconTrendNeutral',
+ [TrendType.Disabled]: 'iconTrendDisabled',
+};
+
+const TrendIconWrapper = styled.span<{
+ trendType: TrendType;
+}>`
+ color: ${({ trendType }) => themeColor(ICON_COLORS[trendType])};
+`;
iconNegativeUpdate: COLORS.red[300],
iconTrendPositive: COLORS.green[400],
iconTrendNegative: COLORS.red[400],
- iconTrendNeutral: COLORS.blue[400],
+ iconTrendNeutral: COLORS.blue[600],
iconTrendDisabled: COLORS.blueGrey[400],
iconError: danger.default,
iconWarning: COLORS.yellow[600],
trendIconDirection = variation > 0 ? TrendDirection.Up : TrendDirection.Down;
trendIconType = variation > 0 === isGoodIfGrowing ? TrendType.Positive : TrendType.Negative;
}
- const variationIcon = (
- <TrendIcon
- className="sw-text-lg"
- direction={trendIconDirection}
- height={20}
- type={trendIconType}
- width={20}
- />
- );
+ const variationIcon = <TrendIcon direction={trendIconDirection} type={trendIconType} />;
const variationToDisplay = formattedValue.startsWith('-') ? formattedValue : `+${formattedValue}`;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import styled from '@emotion/styled';
import { LinkBox, TextMuted } from 'design-system';
import * as React from 'react';
import { Path } from 'react-router-dom';
};
if (METRICS_TO_URL_MAPPING[metricKey]) {
- return <LinkBox to={METRICS_TO_URL_MAPPING[metricKey]()}>{children}</LinkBox>;
+ return (
+ <LinkBox className="link-box-wrapper" to={METRICS_TO_URL_MAPPING[metricKey]()}>
+ {children}
+ </LinkBox>
+ );
}
const url = isIssueMeasure(condition.measure.metric.key)
listView: true,
});
- return <LinkBox to={url}>{children}</LinkBox>;
+ return (
+ <LinkBox className="link-box-wrapper" to={url}>
+ {children}
+ </LinkBox>
+ );
}
getPrimaryText = () => {
{this.getPrimaryText()}
</span>
</div>
- <TextMuted text={`${operator} ${formatMeasure(threshold, metric.type)}`} />
+ <StyledMutedText text={`${operator} ${formatMeasure(threshold, metric.type)}`} />
</div>
</div>,
);
}
export default withMetricsContext(QualityGateCondition);
+
+const StyledMutedText = styled(TextMuted)`
+ .link-box-wrapper:hover & {
+ color: unset;
+ }
+`;
renderProjectActivityAppContainer();
await ui.appLoaded();
- expect(ui.graphTypeIssues.get()).toBeInTheDocument();
+ expect(ui.graphTypeSelect.get()).toHaveValue('project_activity.graphs.issues');
});
it('should render new code legend for applications', async () => {
renderProjectActivityAppContainer();
await ui.appLoaded();
- expect(ui.graphTypeCustom.get()).toBeInTheDocument();
+ expect(ui.graphTypeSelect.get()).toHaveValue('project_activity.graphs.custom');
});
it('should correctly fetch the top level component when dealing with sub portfolios', async () => {
const ui = {
// Graph types.
graphTypeSelect: byLabelText('project_activity.graphs.choose_type'),
- graphTypeIssues: byText('project_activity.graphs.issues'),
- graphTypeCustom: byText('project_activity.graphs.custom'),
// Graphs.
graphs: byLabelText('project_activity.graphs.explanation_x', { exact: false }),
*/
import { Button, IconChevronDown } from '@sonarsource/echoes-react';
-import { Dropdown, TextMuted } from 'design-system';
+import { Dropdown } from 'design-system';
import { sortBy } from 'lodash';
import * as React from 'react';
import { MetricKey, MetricType } from '~sonar-aligned/types/metrics';
}
>
<Button suffix={<IconChevronDown />}>
- <TextMuted
- className="sw-body-sm sw-flex"
- text={translate('project_activity.graphs.custom.add')}
- />
+ <span className="sw-body-sm sw-flex">
+ {translate('project_activity.graphs.custom.add')}
+ </span>
</Button>
</Dropdown>
);
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import {
- Button,
- ButtonGroup,
- DropdownMenu,
- DropdownMenuAlign,
- IconChevronDown,
-} from '@sonarsource/echoes-react';
-import { TextMuted } from 'design-system';
+import { ButtonGroup, InputSize, Select } from '@sonarsource/echoes-react';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
import { GraphType } from '../../types/project-activity';
const noCustomGraph =
props.onAddCustomMetric === undefined || props.onRemoveCustomMetric === undefined;
- const options = React.useMemo(() => {
- const types = getGraphTypes(noCustomGraph);
-
- return types.map((type) => {
- const label = translate('project_activity.graphs', type);
-
- return (
- <DropdownMenu.ItemButton key={label} onClick={() => handleGraphChange(type)}>
- {label}
- </DropdownMenu.ItemButton>
- );
- });
- }, [noCustomGraph, handleGraphChange]);
-
return (
<div className={className}>
<ButtonGroup>
- <DropdownMenu.Root align={DropdownMenuAlign.Start} id="activity-graph-type" items={options}>
- <Button
- aria-label={translate('project_activity.graphs.choose_type')}
- suffix={<IconChevronDown />}
- >
- <TextMuted
- className="sw-body-sm sw-flex"
- text={translate('project_activity.graphs', graph)}
- />
- </Button>
- </DropdownMenu.Root>
+ <label htmlFor="graph-type" className="sw-body-sm-highlight">
+ {translate('project_activity.graphs.choose_type')}
+ </label>
+ <Select
+ id="graph-type"
+ hasDropdownAutoWidth
+ onChange={handleGraphChange}
+ isNotClearable
+ value={graph}
+ size={InputSize.Small}
+ data={getGraphTypes(noCustomGraph).map((type) => ({
+ value: type,
+ label: translate('project_activity.graphs', type),
+ }))}
+ />
{isCustomGraph(graph) &&
props.onAddCustomMetric !== undefined &&
project_activity.new_code_period_start=Everything above this line is New Code
project_activity.new_code_period_start.help=The analysis below this mark is the baseline for New Code comparison
-project_activity.graphs.choose_type=Choose graph type
+project_activity.graphs.choose_type=Graph type
project_activity.graphs.explanation_x=This interactive graph shows data for the following project measures over time: {0}
project_activity.graphs.issues=Issues
project_activity.graphs.coverage=Coverage