1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-01 01:18:10 +02:00

feat: placeholder flag metrics chart (#8197)

This commit is contained in:
Mateusz Kwasniewski 2024-09-20 11:05:53 +02:00 committed by GitHub
parent 0587203ad6
commit 87b997698b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 212 additions and 65 deletions

View File

@ -17,8 +17,6 @@ import {
Title, Title,
Tooltip, Tooltip,
Legend, Legend,
type Chart,
type Tick,
} from 'chart.js'; } from 'chart.js';
import { Bar } from 'react-chartjs-2'; import { Bar } from 'react-chartjs-2';
@ -32,43 +30,14 @@ import {
type ChartDatasetType, type ChartDatasetType,
useTrafficDataEstimation, useTrafficDataEstimation,
} from 'hooks/useTrafficData'; } from 'hooks/useTrafficData';
import { customHighlightPlugin } from 'component/common/Chart/customHighlightPlugin';
import { formatTickValue } from 'component/common/Chart/formatTickValue';
const StyledBox = styled(Box)(({ theme }) => ({ const StyledBox = styled(Box)(({ theme }) => ({
display: 'grid', display: 'grid',
gap: theme.spacing(5), gap: theme.spacing(5),
})); }));
const customHighlightPlugin = {
id: 'customLine',
beforeDraw: (chart: Chart) => {
const width = 46;
if (chart.tooltip?.opacity && chart.tooltip.x) {
const x = chart.tooltip.caretX;
const yAxis = chart.scales.y;
const ctx = chart.ctx;
ctx.save();
const gradient = ctx.createLinearGradient(
x,
yAxis.top,
x,
yAxis.bottom + 34,
);
gradient.addColorStop(0, 'rgba(129, 122, 254, 0)');
gradient.addColorStop(1, 'rgba(129, 122, 254, 0.12)');
ctx.fillStyle = gradient;
ctx.roundRect(
x - width / 2,
yAxis.top,
width,
yAxis.bottom - yAxis.top + 34,
5,
);
ctx.fill();
ctx.restore();
}
},
};
const createBarChartOptions = ( const createBarChartOptions = (
theme: Theme, theme: Theme,
tooltipTitleCallback: (tooltipItems: any) => string, tooltipTitleCallback: (tooltipItems: any) => string,
@ -150,20 +119,7 @@ const createBarChartOptions = (
ticks: { ticks: {
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
maxTicksLimit: 5, maxTicksLimit: 5,
callback: ( callback: formatTickValue,
tickValue: string | number,
index: number,
ticks: Tick[],
) => {
if (typeof tickValue === 'string') {
return tickValue;
}
const value = Number.parseInt(tickValue.toString());
if (value > 999999) {
return `${value / 1000000}M`;
}
return value > 999 ? `${value / 1000}k` : value;
},
}, },
grid: { grid: {
drawBorder: false, drawBorder: false,

View File

@ -0,0 +1,32 @@
import type { Chart } from 'chart.js';
export const customHighlightPlugin = {
id: 'customLine',
beforeDraw: (chart: Chart) => {
const width = 46;
if (chart.tooltip?.opacity && chart.tooltip.x) {
const x = chart.tooltip.caretX;
const yAxis = chart.scales.y;
const ctx = chart.ctx;
ctx.save();
const gradient = ctx.createLinearGradient(
x,
yAxis.top,
x,
yAxis.bottom + 34,
);
gradient.addColorStop(0, 'rgba(129, 122, 254, 0)');
gradient.addColorStop(1, 'rgba(129, 122, 254, 0.12)');
ctx.fillStyle = gradient;
ctx.roundRect(
x - width / 2,
yAxis.top,
width,
yAxis.bottom - yAxis.top + 34,
5,
);
ctx.fill();
ctx.restore();
}
},
};

View File

@ -0,0 +1,16 @@
import type { Tick } from 'chart.js';
export const formatTickValue = (
tickValue: string | number,
index: number,
ticks: Tick[],
) => {
if (typeof tickValue === 'string') {
return tickValue;
}
const value = Number.parseInt(tickValue.toString());
if (value > 999999) {
return `${value / 1000000}M`;
}
return value > 999 ? `${value / 1000}k` : value;
};

View File

@ -0,0 +1,126 @@
import {
BarElement,
CategoryScale,
Chart as ChartJS,
type ChartOptions,
Legend,
LinearScale,
Title,
Tooltip,
} from 'chart.js';
import annotationPlugin from 'chartjs-plugin-annotation';
import { Bar } from 'react-chartjs-2';
import type { Theme } from '@mui/material/styles/createTheme';
import useTheme from '@mui/material/styles/useTheme';
import { useMemo } from 'react';
import { formatTickValue } from 'component/common/Chart/formatTickValue';
const defaultYes = [
45_000_000, 28_000_000, 28_000_000, 25_000_000, 50_000_000, 27_000_000,
26_000_000, 50_000_000, 32_000_000, 12_000_000, 13_000_000, 31_000_000,
12_000_000, 47_000_000, 29_000_000, 46_000_000, 45_000_000, 28_000_000,
28_000_000, 25_000_000, 50_000_000, 27_000_000, 26_000_000, 50_000_000,
32_000_000, 12_000_000, 13_000_000, 31_000_000, 12_000_000, 47_000_000,
];
const defaultNo = [
5_000_000, 8_000_000, 3_000_000, 2_000_000, 2_000_000, 5_000_000, 9_000_000,
3_000_000, 7_000_000, 2_000_000, 5_000_000, 8_000_000, 3_000_000, 2_000_000,
2_000_000, 5_000_000, 1_000_000, 3_000_000, 12_000_000, 2_000_000,
1_000_000, 1_000_000, 3_000_000, 2_000_000, 2_000_000, 5_000_000, 1_000_000,
3_000_000, 8_000_000, 2_000_000,
];
const data = {
labels: Array.from({ length: 30 }, (_, i) => i + 1),
datasets: [
{
data: defaultYes,
label: 'yes',
backgroundColor: '#BEBEBE',
hoverBackgroundColor: '#BEBEBE',
},
{
data: defaultNo,
label: 'no',
backgroundColor: '#9A9A9A',
hoverBackgroundColor: '#9A9A9A',
},
],
};
const createBarChartOptions = (theme: Theme): ChartOptions<'bar'> => ({
plugins: {
legend: {
position: 'bottom',
labels: {
color: theme.palette.text.primary,
pointStyle: 'circle',
usePointStyle: true,
boxHeight: 6,
padding: 15,
boxPadding: 5,
},
},
tooltip: {
enabled: false,
},
},
responsive: true,
scales: {
x: {
stacked: true,
ticks: {
color: theme.palette.text.secondary,
},
grid: {
display: false,
},
},
y: {
stacked: true,
ticks: {
color: theme.palette.text.secondary,
maxTicksLimit: 5,
callback: formatTickValue,
},
grid: {
drawBorder: false,
},
},
},
elements: {
bar: {
borderRadius: 5,
},
},
interaction: {
mode: 'index',
intersect: false,
},
});
export const PlaceholderFlagMetricsChart = () => {
const theme = useTheme();
const options = useMemo(() => {
return createBarChartOptions(theme);
}, [theme]);
return (
<Bar
data={data}
options={options}
aria-label='A bar chart with a single feature flag exposure metrics'
/>
);
};
ChartJS.register(
annotationPlugin,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
);

View File

@ -1,6 +1,7 @@
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser'; import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
import { import {
Box, Box,
Grid,
IconButton, IconButton,
Link, Link,
List, List,
@ -8,7 +9,6 @@ import {
ListItemButton, ListItemButton,
styled, styled,
Typography, Typography,
Grid,
} from '@mui/material'; } from '@mui/material';
import type { Theme } from '@mui/material/styles/createTheme'; import type { Theme } from '@mui/material/styles/createTheme';
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon'; import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
@ -17,6 +17,7 @@ import { useProfile } from 'hooks/api/getters/useProfile/useProfile';
import LinkIcon from '@mui/icons-material/Link'; import LinkIcon from '@mui/icons-material/Link';
import { Badge } from '../common/Badge/Badge'; import { Badge } from '../common/Badge/Badge';
import { ConnectSDK, CreateFlag } from './ConnectSDK'; import { ConnectSDK, CreateFlag } from './ConnectSDK';
import { PlaceholderFlagMetricsChart } from './FlagMetricsChart';
const ScreenExplanation = styled(Typography)(({ theme }) => ({ const ScreenExplanation = styled(Typography)(({ theme }) => ({
marginTop: theme.spacing(1), marginTop: theme.spacing(1),
@ -30,7 +31,7 @@ const StyledHeaderTitle = styled(Typography)(({ theme }) => ({
marginBottom: theme.spacing(2), marginBottom: theme.spacing(2),
})); }));
const ProjectsGrid = styled(Grid)(({ theme }) => ({ const ContentGrid = styled(Grid)(({ theme }) => ({
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
borderRadius: `${theme.shape.borderRadiusLarge}px`, borderRadius: `${theme.shape.borderRadiusLarge}px`,
})); }));
@ -104,7 +105,7 @@ const ActiveProjectDetails: FC<{
); );
}; };
const SpacedGrid = styled(Grid)(({ theme }) => ({ const SpacedGridItem = styled(Grid)(({ theme }) => ({
padding: theme.spacing(4), padding: theme.spacing(4),
border: `0.5px solid ${theme.palette.divider}`, border: `0.5px solid ${theme.palette.divider}`,
})); }));
@ -148,19 +149,19 @@ export const PersonalDashboard = () => {
most of Unleash most of Unleash
</ScreenExplanation> </ScreenExplanation>
<StyledHeaderTitle>Your resources</StyledHeaderTitle> <StyledHeaderTitle>Your resources</StyledHeaderTitle>
<ProjectsGrid container columns={{ lg: 12, md: 1 }}> <ContentGrid container columns={{ lg: 12, md: 1 }}>
<SpacedGrid item lg={4} md={1}> <SpacedGridItem item lg={4} md={1}>
<Typography variant='h3'>My projects</Typography> <Typography variant='h3'>My projects</Typography>
</SpacedGrid> </SpacedGridItem>
<SpacedGrid <SpacedGridItem
item item
lg={8} lg={8}
md={1} md={1}
sx={{ display: 'flex', justifyContent: 'flex-end' }} sx={{ display: 'flex', justifyContent: 'flex-end' }}
> >
<Badge color='warning'>Setup incomplete</Badge> <Badge color='warning'>Setup incomplete</Badge>
</SpacedGrid> </SpacedGridItem>
<SpacedGrid item lg={4} md={1}> <SpacedGridItem item lg={4} md={1}>
<List <List
disablePadding={true} disablePadding={true}
sx={{ maxHeight: '400px', overflow: 'auto' }} sx={{ maxHeight: '400px', overflow: 'auto' }}
@ -207,19 +208,19 @@ export const PersonalDashboard = () => {
); );
})} })}
</List> </List>
</SpacedGrid> </SpacedGridItem>
<SpacedGrid item lg={4} md={1}> <SpacedGridItem item lg={4} md={1}>
{activeProject ? ( {activeProject ? (
<CreateFlag project={activeProject} /> <CreateFlag project={activeProject} />
) : null} ) : null}
</SpacedGrid> </SpacedGridItem>
<SpacedGrid item lg={4} md={1}> <SpacedGridItem item lg={4} md={1}>
{activeProject ? ( {activeProject ? (
<ConnectSDK project={activeProject} /> <ConnectSDK project={activeProject} />
) : null} ) : null}
</SpacedGrid> </SpacedGridItem>
<SpacedGrid item lg={4} md={1} /> <SpacedGridItem item lg={4} md={1} />
<SpacedGrid <SpacedGridItem
item item
lg={8} lg={8}
md={1} md={1}
@ -228,8 +229,24 @@ export const PersonalDashboard = () => {
<span>Your roles in this project:</span>{' '} <span>Your roles in this project:</span>{' '}
<Badge color='secondary'>Member</Badge>{' '} <Badge color='secondary'>Member</Badge>{' '}
<Badge color='secondary'>Another</Badge> <Badge color='secondary'>Another</Badge>
</SpacedGrid> </SpacedGridItem>
</ProjectsGrid> </ContentGrid>
<ContentGrid container columns={{ lg: 12, md: 1 }} sx={{ mt: 2 }}>
<SpacedGridItem item lg={4} md={1}>
<Typography variant='h3'>My feature flags</Typography>
</SpacedGridItem>
<SpacedGridItem item lg={8} md={1} />
<SpacedGridItem item lg={4} md={1}>
<Typography>
You have not created or favorited any feature flags.
Once you do, the will show up here.
</Typography>
</SpacedGridItem>
<SpacedGridItem item lg={8} md={1}>
<Typography sx={{ mb: 4 }}>Feature flag metrics</Typography>
<PlaceholderFlagMetricsChart />
</SpacedGridItem>
</ContentGrid>
</div> </div>
); );
}; };