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:
parent
0587203ad6
commit
87b997698b
@ -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,
|
||||||
|
32
frontend/src/component/common/Chart/customHighlightPlugin.ts
Normal file
32
frontend/src/component/common/Chart/customHighlightPlugin.ts
Normal 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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
16
frontend/src/component/common/Chart/formatTickValue.ts
Normal file
16
frontend/src/component/common/Chart/formatTickValue.ts
Normal 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;
|
||||||
|
};
|
126
frontend/src/component/personalDashboard/FlagMetricsChart.tsx
Normal file
126
frontend/src/component/personalDashboard/FlagMetricsChart.tsx
Normal 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,
|
||||||
|
);
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user