mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
Dashboard health stats widget (#6262)
This commit is contained in:
parent
474e53460a
commit
7682429839
@ -18,6 +18,7 @@ import { ProjectHealthChart } from './ProjectHealthChart/ProjectHealthChart';
|
|||||||
import { TimeToProductionChart } from './TimeToProductionChart/TimeToProductionChart';
|
import { TimeToProductionChart } from './TimeToProductionChart/TimeToProductionChart';
|
||||||
import { TimeToProduction } from './TimeToProduction/TimeToProduction';
|
import { TimeToProduction } from './TimeToProduction/TimeToProduction';
|
||||||
import { ProjectSelect, allOption } from './ProjectSelect/ProjectSelect';
|
import { ProjectSelect, allOption } from './ProjectSelect/ProjectSelect';
|
||||||
|
import { HealthStats } from './HealthStats/HealthStats';
|
||||||
|
|
||||||
const StyledGrid = styled(Box)(({ theme }) => ({
|
const StyledGrid = styled(Box)(({ theme }) => ({
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
@ -144,20 +145,27 @@ export const ExecutiveDashboard: VFC = () => {
|
|||||||
projectFlagTrends={filteredProjectFlagTrends}
|
projectFlagTrends={filteredProjectFlagTrends}
|
||||||
/>
|
/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget
|
<Widget title='Average health' order={6}>
|
||||||
title='Health per project'
|
<HealthStats
|
||||||
order={6}
|
// FIXME: data from API
|
||||||
span={largeChartSpan}
|
value={90}
|
||||||
>
|
healthy={50}
|
||||||
|
stale={10}
|
||||||
|
potenciallyStale={5}
|
||||||
|
/>
|
||||||
|
</Widget>
|
||||||
|
<Widget title='Health per project' order={7} span={chartSpan}>
|
||||||
<ProjectHealthChart
|
<ProjectHealthChart
|
||||||
projectFlagTrends={filteredProjectFlagTrends}
|
projectFlagTrends={filteredProjectFlagTrends}
|
||||||
/>
|
/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget title='Average time to production' order={7}>
|
<Widget title='Average time to production' order={8}>
|
||||||
{/* FIXME: data from API */}
|
<TimeToProduction
|
||||||
<TimeToProduction daysToProduction={12} />
|
//FIXME: data from API
|
||||||
|
daysToProduction={12}
|
||||||
|
/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget title='Time to production' order={8} span={chartSpan}>
|
<Widget title='Time to production' order={9} span={chartSpan}>
|
||||||
<TimeToProductionChart
|
<TimeToProductionChart
|
||||||
projectFlagTrends={filteredProjectFlagTrends}
|
projectFlagTrends={filteredProjectFlagTrends}
|
||||||
/>
|
/>
|
||||||
|
@ -13,5 +13,5 @@ export const FlagsProjectChart: VFC<IFlagsProjectChartProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const data = useProjectChartData(projectFlagTrends, 'total');
|
const data = useProjectChartData(projectFlagTrends, 'total');
|
||||||
|
|
||||||
return <LineChart data={data} />;
|
return <LineChart data={data} isLocalTooltip />;
|
||||||
};
|
};
|
||||||
|
@ -23,7 +23,7 @@ const describeArc = (radius: number, startAngle: number, endAngle: number) => {
|
|||||||
const start = polarToCartesian(0, 0, radius, endAngle);
|
const start = polarToCartesian(0, 0, radius, endAngle);
|
||||||
const end = polarToCartesian(0, 0, radius, startAngle);
|
const end = polarToCartesian(0, 0, radius, startAngle);
|
||||||
const largeArcFlag = endAngle - startAngle <= Math.PI ? '0' : '1';
|
const largeArcFlag = endAngle - startAngle <= Math.PI ? '0' : '1';
|
||||||
const d = [
|
const dSvgAttribute = [
|
||||||
'M',
|
'M',
|
||||||
start.x,
|
start.x,
|
||||||
start.y,
|
start.y,
|
||||||
@ -36,7 +36,7 @@ const describeArc = (radius: number, startAngle: number, endAngle: number) => {
|
|||||||
end.x,
|
end.x,
|
||||||
end.y,
|
end.y,
|
||||||
].join(' ');
|
].join(' ');
|
||||||
return d;
|
return dSvgAttribute;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GaugeLines = () => {
|
const GaugeLines = () => {
|
||||||
|
@ -0,0 +1,312 @@
|
|||||||
|
import { VFC } from 'react';
|
||||||
|
import { useThemeMode } from 'hooks/useThemeMode';
|
||||||
|
import { useTheme } from '@mui/material';
|
||||||
|
|
||||||
|
interface IHealthStatsProps {
|
||||||
|
value: number;
|
||||||
|
healthy: number;
|
||||||
|
stale: number;
|
||||||
|
potenciallyStale: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HealthStats: VFC<IHealthStatsProps> = ({
|
||||||
|
value,
|
||||||
|
healthy,
|
||||||
|
stale,
|
||||||
|
potenciallyStale,
|
||||||
|
}) => {
|
||||||
|
const { themeMode } = useThemeMode();
|
||||||
|
const isDark = themeMode === 'dark';
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox='0 0 268 281'
|
||||||
|
fill='none'
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
>
|
||||||
|
<title>Health Stats</title>
|
||||||
|
<g filter={!isDark ? 'url(#filter0_d_22043_268578)' : undefined}>
|
||||||
|
<circle
|
||||||
|
cx='134'
|
||||||
|
cy='129'
|
||||||
|
r='97'
|
||||||
|
fill={theme.palette.charts.health.mainCircleBackground}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<circle
|
||||||
|
cx='134'
|
||||||
|
cy='129'
|
||||||
|
r='121'
|
||||||
|
stroke={theme.palette.charts.health.orbit}
|
||||||
|
stroke-width='3'
|
||||||
|
/>
|
||||||
|
<text
|
||||||
|
x={134}
|
||||||
|
y={149}
|
||||||
|
textAnchor='middle'
|
||||||
|
fontSize={48}
|
||||||
|
fill={theme.palette.charts.health.title}
|
||||||
|
fontWeight={700}
|
||||||
|
>
|
||||||
|
{value !== undefined ? `${value}%` : 'N/A'}
|
||||||
|
</text>
|
||||||
|
<g filter={!isDark ? 'url(#filter1_d_22043_268578)' : undefined}>
|
||||||
|
<circle
|
||||||
|
cx='206'
|
||||||
|
cy='58'
|
||||||
|
r='50'
|
||||||
|
fill={theme.palette.charts.health.circles}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x={206}
|
||||||
|
y={56}
|
||||||
|
fill={theme.palette.charts.health.healthy}
|
||||||
|
fontWeight={700}
|
||||||
|
fontSize={20}
|
||||||
|
textAnchor='middle'
|
||||||
|
>
|
||||||
|
{healthy || 0}
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x={206}
|
||||||
|
y={72}
|
||||||
|
fill={theme.palette.charts.health.text}
|
||||||
|
fontSize={12}
|
||||||
|
textAnchor='middle'
|
||||||
|
>
|
||||||
|
Healthy
|
||||||
|
</text>
|
||||||
|
<g filter={!isDark ? 'url(#filter2_d_22043_268578)' : undefined}>
|
||||||
|
<circle
|
||||||
|
cx='53'
|
||||||
|
cy='66'
|
||||||
|
r='41'
|
||||||
|
fill={theme.palette.charts.health.circles}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x={53}
|
||||||
|
y={65}
|
||||||
|
fill={theme.palette.charts.health.stale}
|
||||||
|
fontWeight={700}
|
||||||
|
fontSize={20}
|
||||||
|
textAnchor='middle'
|
||||||
|
>
|
||||||
|
{stale || 0}
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x={53}
|
||||||
|
y={81}
|
||||||
|
fill={theme.palette.charts.health.text}
|
||||||
|
fontSize={12}
|
||||||
|
textAnchor='middle'
|
||||||
|
>
|
||||||
|
Stale
|
||||||
|
</text>
|
||||||
|
<g filter={!isDark ? 'url(#filter3_d_22043_268578)' : undefined}>
|
||||||
|
<circle
|
||||||
|
cx='144'
|
||||||
|
cy='224'
|
||||||
|
r='41'
|
||||||
|
fill={theme.palette.charts.health.circles}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x={144}
|
||||||
|
y={216}
|
||||||
|
fill={theme.palette.charts.health.potenciallyStale}
|
||||||
|
fontWeight={700}
|
||||||
|
fontSize={20}
|
||||||
|
textAnchor='middle'
|
||||||
|
>
|
||||||
|
{potenciallyStale || 0}
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x={144}
|
||||||
|
y={232}
|
||||||
|
fill={theme.palette.charts.health.text}
|
||||||
|
fontSize={12}
|
||||||
|
textAnchor='middle'
|
||||||
|
>
|
||||||
|
<tspan x={144} dy='0'>
|
||||||
|
Potentially
|
||||||
|
</tspan>
|
||||||
|
<tspan x={144} dy='1.2em'>
|
||||||
|
stale
|
||||||
|
</tspan>
|
||||||
|
</text>
|
||||||
|
<path
|
||||||
|
d='M99.5 247.275C99.5 251.693 103.082 255.275 107.5 255.275C111.918 255.275 115.5 251.693 115.5 247.275C115.5 242.857 111.918 239.275 107.5 239.275C103.082 239.275 99.5 242.857 99.5 247.275ZM10.8811 92C10.8811 96.4183 14.4629 100 18.8811 100C23.2994 100 26.8811 96.4183 26.8811 92C26.8811 87.5817 23.2994 84 18.8811 84C14.4629 84 10.8811 87.5817 10.8811 92ZM107.827 245.811C54.4151 233.886 14.5 186.258 14.5 129.325H11.5C11.5 187.696 52.4223 236.515 107.173 248.739L107.827 245.811ZM14.5 129.325C14.5 116.458 16.5379 104.07 20.3078 92.4634L17.4545 91.5366C13.5886 103.439 11.5 116.14 11.5 129.325H14.5Z'
|
||||||
|
fill='url(#paint0_linear_22043_268578)'
|
||||||
|
/>
|
||||||
|
<defs>
|
||||||
|
<filter
|
||||||
|
id='filter0_d_22043_268578'
|
||||||
|
x='15'
|
||||||
|
y='13'
|
||||||
|
width='238'
|
||||||
|
height='238'
|
||||||
|
filterUnits='userSpaceOnUse'
|
||||||
|
color-interpolation-filters='sRGB'
|
||||||
|
>
|
||||||
|
<feFlood flood-opacity='0' result='BackgroundImageFix' />
|
||||||
|
<feColorMatrix
|
||||||
|
in='SourceAlpha'
|
||||||
|
type='matrix'
|
||||||
|
values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'
|
||||||
|
result='hardAlpha'
|
||||||
|
/>
|
||||||
|
<feMorphology
|
||||||
|
radius='2'
|
||||||
|
operator='dilate'
|
||||||
|
in='SourceAlpha'
|
||||||
|
result='effect1_dropShadow_22043_268578'
|
||||||
|
/>
|
||||||
|
<feOffset dy='3' />
|
||||||
|
<feGaussianBlur stdDeviation='10' />
|
||||||
|
<feComposite in2='hardAlpha' operator='out' />
|
||||||
|
<feColorMatrix
|
||||||
|
type='matrix'
|
||||||
|
values='0 0 0 0 0.505882 0 0 0 0 0.478431 0 0 0 0 0.996078 0 0 0 0.36 0'
|
||||||
|
/>
|
||||||
|
<feBlend
|
||||||
|
mode='normal'
|
||||||
|
in2='BackgroundImageFix'
|
||||||
|
result='effect1_dropShadow_22043_268578'
|
||||||
|
/>
|
||||||
|
<feBlend
|
||||||
|
mode='normal'
|
||||||
|
in='SourceGraphic'
|
||||||
|
in2='effect1_dropShadow_22043_268578'
|
||||||
|
result='shape'
|
||||||
|
/>
|
||||||
|
</filter>
|
||||||
|
<filter
|
||||||
|
id='filter1_d_22043_268578'
|
||||||
|
x='144'
|
||||||
|
y='0'
|
||||||
|
width='124'
|
||||||
|
height='124'
|
||||||
|
filterUnits='userSpaceOnUse'
|
||||||
|
color-interpolation-filters='sRGB'
|
||||||
|
>
|
||||||
|
<feFlood flood-opacity='0' result='BackgroundImageFix' />
|
||||||
|
<feColorMatrix
|
||||||
|
in='SourceAlpha'
|
||||||
|
type='matrix'
|
||||||
|
values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'
|
||||||
|
result='hardAlpha'
|
||||||
|
/>
|
||||||
|
<feOffset dy='4' />
|
||||||
|
<feGaussianBlur stdDeviation='6' />
|
||||||
|
<feComposite in2='hardAlpha' operator='out' />
|
||||||
|
<feColorMatrix
|
||||||
|
type='matrix'
|
||||||
|
values='0 0 0 0 0.380392 0 0 0 0 0.356863 0 0 0 0 0.760784 0 0 0 0.16 0'
|
||||||
|
/>
|
||||||
|
<feBlend
|
||||||
|
mode='normal'
|
||||||
|
in2='BackgroundImageFix'
|
||||||
|
result='effect1_dropShadow_22043_268578'
|
||||||
|
/>
|
||||||
|
<feBlend
|
||||||
|
mode='normal'
|
||||||
|
in='SourceGraphic'
|
||||||
|
in2='effect1_dropShadow_22043_268578'
|
||||||
|
result='shape'
|
||||||
|
/>
|
||||||
|
</filter>
|
||||||
|
<filter
|
||||||
|
id='filter2_d_22043_268578'
|
||||||
|
x='0'
|
||||||
|
y='17'
|
||||||
|
width='106'
|
||||||
|
height='106'
|
||||||
|
filterUnits='userSpaceOnUse'
|
||||||
|
color-interpolation-filters='sRGB'
|
||||||
|
>
|
||||||
|
<feFlood flood-opacity='0' result='BackgroundImageFix' />
|
||||||
|
<feColorMatrix
|
||||||
|
in='SourceAlpha'
|
||||||
|
type='matrix'
|
||||||
|
values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'
|
||||||
|
result='hardAlpha'
|
||||||
|
/>
|
||||||
|
<feOffset dy='4' />
|
||||||
|
<feGaussianBlur stdDeviation='6' />
|
||||||
|
<feComposite in2='hardAlpha' operator='out' />
|
||||||
|
<feColorMatrix
|
||||||
|
type='matrix'
|
||||||
|
values='0 0 0 0 0.380392 0 0 0 0 0.356863 0 0 0 0 0.760784 0 0 0 0.16 0'
|
||||||
|
/>
|
||||||
|
<feBlend
|
||||||
|
mode='normal'
|
||||||
|
in2='BackgroundImageFix'
|
||||||
|
result='effect1_dropShadow_22043_268578'
|
||||||
|
/>
|
||||||
|
<feBlend
|
||||||
|
mode='normal'
|
||||||
|
in='SourceGraphic'
|
||||||
|
in2='effect1_dropShadow_22043_268578'
|
||||||
|
result='shape'
|
||||||
|
/>
|
||||||
|
</filter>
|
||||||
|
<filter
|
||||||
|
id='filter3_d_22043_268578'
|
||||||
|
x='91'
|
||||||
|
y='175'
|
||||||
|
width='106'
|
||||||
|
height='106'
|
||||||
|
filterUnits='userSpaceOnUse'
|
||||||
|
color-interpolation-filters='sRGB'
|
||||||
|
>
|
||||||
|
<feFlood flood-opacity='0' result='BackgroundImageFix' />
|
||||||
|
<feColorMatrix
|
||||||
|
in='SourceAlpha'
|
||||||
|
type='matrix'
|
||||||
|
values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'
|
||||||
|
result='hardAlpha'
|
||||||
|
/>
|
||||||
|
<feOffset dy='4' />
|
||||||
|
<feGaussianBlur stdDeviation='6' />
|
||||||
|
<feComposite in2='hardAlpha' operator='out' />
|
||||||
|
<feColorMatrix
|
||||||
|
type='matrix'
|
||||||
|
values='0 0 0 0 0.380392 0 0 0 0 0.356863 0 0 0 0 0.760784 0 0 0 0.16 0'
|
||||||
|
/>
|
||||||
|
<feBlend
|
||||||
|
mode='normal'
|
||||||
|
in2='BackgroundImageFix'
|
||||||
|
result='effect1_dropShadow_22043_268578'
|
||||||
|
/>
|
||||||
|
<feBlend
|
||||||
|
mode='normal'
|
||||||
|
in='SourceGraphic'
|
||||||
|
in2='effect1_dropShadow_22043_268578'
|
||||||
|
result='shape'
|
||||||
|
/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient
|
||||||
|
id='paint0_linear_22043_268578'
|
||||||
|
x1='64.2447'
|
||||||
|
y1='87'
|
||||||
|
x2='64.2447'
|
||||||
|
y2='249'
|
||||||
|
gradientUnits='userSpaceOnUse'
|
||||||
|
>
|
||||||
|
<stop
|
||||||
|
stop-color={theme.palette.charts.health.gradientStale}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset='1'
|
||||||
|
stop-color={
|
||||||
|
theme.palette.charts.health.gradientPotenciallyStale
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -28,6 +28,7 @@ const createOptions = (
|
|||||||
locationSettings: ILocationSettings,
|
locationSettings: ILocationSettings,
|
||||||
setTooltip: React.Dispatch<React.SetStateAction<TooltipState | null>>,
|
setTooltip: React.Dispatch<React.SetStateAction<TooltipState | null>>,
|
||||||
isPlaceholder?: boolean,
|
isPlaceholder?: boolean,
|
||||||
|
localTooltip?: boolean,
|
||||||
) =>
|
) =>
|
||||||
({
|
({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
@ -82,13 +83,12 @@ const createOptions = (
|
|||||||
tooltip: {
|
tooltip: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
external: (context: any) => {
|
external: (context: any) => {
|
||||||
const tooltipModel = context.tooltip;
|
const tooltip = context.tooltip;
|
||||||
if (tooltipModel.opacity === 0) {
|
if (tooltip.opacity === 0) {
|
||||||
setTooltip(null);
|
setTooltip(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tooltip = context.tooltip;
|
|
||||||
setTooltip({
|
setTooltip({
|
||||||
caretX: tooltip?.caretX,
|
caretX: tooltip?.caretX,
|
||||||
caretY: tooltip?.caretY,
|
caretY: tooltip?.caretY,
|
||||||
@ -106,15 +106,17 @@ const createOptions = (
|
|||||||
},
|
},
|
||||||
locale: locationSettings.locale,
|
locale: locationSettings.locale,
|
||||||
interaction: {
|
interaction: {
|
||||||
intersect: false,
|
intersect: localTooltip || false,
|
||||||
axis: 'x',
|
axis: 'x',
|
||||||
},
|
},
|
||||||
elements: {
|
elements: {
|
||||||
point: {
|
point: {
|
||||||
radius: 0,
|
radius: 0,
|
||||||
|
hitRadius: 15,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// cubicInterpolationMode: 'monotone',
|
// cubicInterpolationMode: 'monotone',
|
||||||
|
tension: 0.1,
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
scales: {
|
scales: {
|
||||||
y: {
|
y: {
|
||||||
@ -127,12 +129,14 @@ const createOptions = (
|
|||||||
ticks: {
|
ticks: {
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
display: !isPlaceholder,
|
display: !isPlaceholder,
|
||||||
|
precision: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
x: {
|
x: {
|
||||||
type: 'time',
|
type: 'time',
|
||||||
time: {
|
time: {
|
||||||
unit: 'month',
|
unit: 'day',
|
||||||
|
tooltipFormat: 'PPP',
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
color: 'transparent',
|
color: 'transparent',
|
||||||
@ -206,14 +210,21 @@ const LineChartComponent: VFC<{
|
|||||||
data: ChartData<'line', (number | ScatterDataPoint | null)[], unknown>;
|
data: ChartData<'line', (number | ScatterDataPoint | null)[], unknown>;
|
||||||
aspectRatio?: number;
|
aspectRatio?: number;
|
||||||
cover?: ReactNode;
|
cover?: ReactNode;
|
||||||
}> = ({ data, aspectRatio, cover }) => {
|
isLocalTooltip?: boolean;
|
||||||
|
}> = ({ data, aspectRatio, cover, isLocalTooltip }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { locationSettings } = useLocationSettings();
|
const { locationSettings } = useLocationSettings();
|
||||||
|
|
||||||
const [tooltip, setTooltip] = useState<null | TooltipState>(null);
|
const [tooltip, setTooltip] = useState<null | TooltipState>(null);
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createOptions(theme, locationSettings, setTooltip, Boolean(cover)),
|
createOptions(
|
||||||
|
theme,
|
||||||
|
locationSettings,
|
||||||
|
setTooltip,
|
||||||
|
Boolean(cover),
|
||||||
|
isLocalTooltip,
|
||||||
|
),
|
||||||
[theme, locationSettings],
|
[theme, locationSettings],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -13,5 +13,5 @@ export const ProjectHealthChart: VFC<IFlagsProjectChartProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const data = useProjectChartData(projectFlagTrends, 'health');
|
const data = useProjectChartData(projectFlagTrends, 'health');
|
||||||
|
|
||||||
return <LineChart data={data} />;
|
return <LineChart data={data} isLocalTooltip />;
|
||||||
};
|
};
|
||||||
|
@ -13,5 +13,5 @@ export const TimeToProductionChart: VFC<IFlagsProjectChartProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const data = useProjectChartData(projectFlagTrends, 'timeToProduction');
|
const data = useProjectChartData(projectFlagTrends, 'timeToProduction');
|
||||||
|
|
||||||
return <LineChart data={data} />;
|
return <LineChart data={data} isLocalTooltip />;
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
LineChart,
|
LineChart,
|
||||||
NotEnoughData,
|
NotEnoughData,
|
||||||
} from '../LineChart/LineChart';
|
} from '../LineChart/LineChart';
|
||||||
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
|
||||||
interface IUsersChartProps {
|
interface IUsersChartProps {
|
||||||
userTrends: ExecutiveSummarySchema['userTrends'];
|
userTrends: ExecutiveSummarySchema['userTrends'];
|
||||||
@ -17,6 +18,7 @@ export const UsersChart: VFC<IUsersChartProps> = ({
|
|||||||
userTrends,
|
userTrends,
|
||||||
isLoading,
|
isLoading,
|
||||||
}) => {
|
}) => {
|
||||||
|
const showInactiveUsers = useUiFlag('showInactiveUsers');
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const notEnoughData = userTrends.length < 2;
|
const notEnoughData = userTrends.length < 2;
|
||||||
const placeholderData = useMemo(
|
const placeholderData = useMemo(
|
||||||
@ -50,23 +52,28 @@ export const UsersChart: VFC<IUsersChartProps> = ({
|
|||||||
data: userTrends.map((item) => item.total),
|
data: userTrends.map((item) => item.total),
|
||||||
borderColor: theme.palette.primary.light,
|
borderColor: theme.palette.primary.light,
|
||||||
backgroundColor: fillGradientPrimary,
|
backgroundColor: fillGradientPrimary,
|
||||||
|
pointBackgroundColor: theme.palette.primary.main,
|
||||||
fill: true,
|
fill: true,
|
||||||
order: 3,
|
order: 3,
|
||||||
},
|
},
|
||||||
{
|
...(showInactiveUsers
|
||||||
label: 'Active users',
|
? [
|
||||||
data: userTrends.map((item) => item.active),
|
{
|
||||||
borderColor: theme.palette.success.border,
|
label: 'Active users',
|
||||||
backgroundColor: theme.palette.success.border,
|
data: userTrends.map((item) => item.active),
|
||||||
order: 2,
|
borderColor: theme.palette.success.border,
|
||||||
},
|
backgroundColor: theme.palette.success.border,
|
||||||
{
|
order: 2,
|
||||||
label: 'Inactive users',
|
},
|
||||||
data: userTrends.map((item) => item.inactive),
|
{
|
||||||
borderColor: theme.palette.warning.border,
|
label: 'Inactive users',
|
||||||
backgroundColor: theme.palette.warning.border,
|
data: userTrends.map((item) => item.inactive),
|
||||||
order: 1,
|
borderColor: theme.palette.warning.border,
|
||||||
},
|
backgroundColor: theme.palette.warning.border,
|
||||||
|
order: 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
[theme, userTrends],
|
[theme, userTrends],
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
// TODO: Replace this function with something more tailored to our color palette
|
import { colors } from 'themes/colors';
|
||||||
export const getRandomColor = () => {
|
|
||||||
const letters = '0123456789ABCDEF';
|
export const getProjectColor = (str: string): string => {
|
||||||
let color = '#';
|
if (str === 'default') {
|
||||||
for (let i = 0; i < 6; i++) {
|
// Special case for default project - use primary color
|
||||||
color += letters[Math.floor(Math.random() * 16)];
|
return colors.purple[800];
|
||||||
}
|
}
|
||||||
return color;
|
|
||||||
|
let hash = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
const c = (hash & 0x00ffffff).toString(16).toUpperCase();
|
||||||
|
return `#${'00000'.substring(0, 6 - c.length)}${c}`;
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
ExecutiveSummarySchema,
|
ExecutiveSummarySchema,
|
||||||
ExecutiveSummarySchemaProjectFlagTrendsItem,
|
ExecutiveSummarySchemaProjectFlagTrendsItem,
|
||||||
} from '../../openapi';
|
} from '../../openapi';
|
||||||
import { getRandomColor } from './executive-dashboard-utils';
|
import { getProjectColor } from './executive-dashboard-utils';
|
||||||
import { useTheme } from '@mui/material';
|
import { useTheme } from '@mui/material';
|
||||||
|
|
||||||
type ProjectFlagTrends = ExecutiveSummarySchema['projectFlagTrends'];
|
type ProjectFlagTrends = ExecutiveSummarySchema['projectFlagTrends'];
|
||||||
@ -27,7 +27,7 @@ export const useProjectChartData = (
|
|||||||
|
|
||||||
const datasets = Object.entries(groupedFlagTrends).map(
|
const datasets = Object.entries(groupedFlagTrends).map(
|
||||||
([project, trends]) => {
|
([project, trends]) => {
|
||||||
const color = getRandomColor();
|
const color = getProjectColor(project);
|
||||||
return {
|
return {
|
||||||
label: project,
|
label: project,
|
||||||
data: trends.map((item) => {
|
data: trends.map((item) => {
|
||||||
|
@ -290,6 +290,18 @@ const theme = {
|
|||||||
sectionLine: '#8c89bf',
|
sectionLine: '#8c89bf',
|
||||||
text: colors.grey[800],
|
text: colors.grey[800],
|
||||||
},
|
},
|
||||||
|
health: {
|
||||||
|
mainCircleBackground: '#34325E',
|
||||||
|
orbit: '#4C4992',
|
||||||
|
circles: '#2B2A3C',
|
||||||
|
text: colors.grey[500],
|
||||||
|
title: colors.grey[50],
|
||||||
|
healthy: colors.purple[800],
|
||||||
|
stale: colors.red[800],
|
||||||
|
potenciallyStale: colors.orange[800],
|
||||||
|
gradientStale: '#8A3E45',
|
||||||
|
gradientPotenciallyStale: '#875D21',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -275,6 +275,18 @@ export const theme = {
|
|||||||
sectionLine: colors.purple[500],
|
sectionLine: colors.purple[500],
|
||||||
text: colors.grey[600],
|
text: colors.grey[600],
|
||||||
},
|
},
|
||||||
|
health: {
|
||||||
|
mainCircleBackground: colors.purple[800],
|
||||||
|
orbit: colors.grey[300],
|
||||||
|
circles: colors.grey[50],
|
||||||
|
text: colors.grey[900],
|
||||||
|
title: colors.grey[50],
|
||||||
|
healthy: colors.purple[800],
|
||||||
|
stale: colors.red[800],
|
||||||
|
potenciallyStale: colors.orange[800],
|
||||||
|
gradientStale: colors.red[300],
|
||||||
|
gradientPotenciallyStale: colors.orange[500],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -133,6 +133,18 @@ declare module '@mui/material/styles' {
|
|||||||
sectionLine: string;
|
sectionLine: string;
|
||||||
text: string;
|
text: string;
|
||||||
};
|
};
|
||||||
|
health: {
|
||||||
|
mainCircleBackground: string;
|
||||||
|
orbit: string;
|
||||||
|
circles: string;
|
||||||
|
text: string;
|
||||||
|
title: string;
|
||||||
|
healthy: string;
|
||||||
|
stale: string;
|
||||||
|
potenciallyStale: string;
|
||||||
|
gradientStale: string;
|
||||||
|
gradientPotenciallyStale: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
interface Theme extends CustomTheme {}
|
interface Theme extends CustomTheme {}
|
||||||
|
Loading…
Reference in New Issue
Block a user