Skip to content

Commit

Permalink
feat: add project flags component (#6070)
Browse files Browse the repository at this point in the history
This PR adds project flags line chart component
  • Loading branch information
FredrikOseberg committed Jan 31, 2024
1 parent c6a2303 commit aae1d05
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 2 deletions.
Expand Up @@ -13,6 +13,7 @@ import { useExecutiveDashboard } from 'hooks/api/getters/useExecutiveSummary/use
import { UserStats } from './UserStats/UserStats';
import { FlagStats } from './FlagStats/FlagStats';
import { Widget } from './Widget/Widget';
import { FlagsProjectChart } from './FlagsProjectChart/FlagsProjectChart';

const StyledGrid = styled(Box)(({ theme }) => ({
display: 'grid',
Expand Down Expand Up @@ -107,6 +108,10 @@ export const ExecutiveDashboard: VFC = () => {
/>
</Widget>
</StyledGrid>

<FlagsProjectChart
projectFlagTrends={executiveDashboardData.projectFlagTrends}
/>
</>
);
};
Expand Up @@ -64,7 +64,11 @@ const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
const date =
item?.chart?.data?.labels?.[item.dataIndex];
return date
? formatDateYMD(date, locationSettings.locale)
? formatDateYMD(
date,
locationSettings.locale,
'UTC',
)
: '';
},
},
Expand Down
@@ -0,0 +1,5 @@
import { lazy } from 'react';

export const FlagsProjectChart = lazy(
() => import('./FlagsProjectChartComponent'),
);
@@ -0,0 +1,162 @@
import { useMemo, type VFC } from 'react';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
TimeScale,
} from 'chart.js';
import { Line } from 'react-chartjs-2';
import 'chartjs-adapter-date-fns';
import { Paper, Theme, Typography, useTheme } from '@mui/material';
import {
useLocationSettings,
type ILocationSettings,
} from 'hooks/useLocationSettings';
import { formatDateYMD } from 'utils/formatDate';
import {
ExecutiveSummarySchema,
ExecutiveSummarySchemaProjectFlagTrendsItem,
} from 'openapi';

const getRandomColor = () => {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};

const createData = (
theme: Theme,
flagTrends: ExecutiveSummarySchema['projectFlagTrends'] = [],
) => {
const groupedFlagTrends = flagTrends.reduce<
Record<string, ExecutiveSummarySchemaProjectFlagTrendsItem[]>
>((groups, item) => {
if (!groups[item.project]) {
groups[item.project] = [];
}
groups[item.project].push(item);
return groups;
}, {});

const datasets = Object.entries(groupedFlagTrends).map(
([project, trends]) => {
const color = getRandomColor();
return {
label: project,
data: trends.map((item) => item.total),
borderColor: color,
backgroundColor: color,
fill: true,
};
},
);

return {
labels: flagTrends.map((item) => item.date),
datasets,
};
};

const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
({
responsive: true,
plugins: {
legend: {
position: 'bottom',
},
tooltip: {
callbacks: {
title: (tooltipItems: any) => {
const item = tooltipItems?.[0];
const date =
item?.chart?.data?.labels?.[item.dataIndex];
return date
? formatDateYMD(
date,
locationSettings.locale,
'UTC',
)
: '';
},
},
},
},
locale: locationSettings.locale,
interaction: {
intersect: false,
axis: 'x',
},
color: theme.palette.text.secondary,
scales: {
y: {
type: 'linear',
grid: {
color: theme.palette.divider,
borderColor: theme.palette.divider,
},
ticks: { color: theme.palette.text.secondary },
},
x: {
type: 'time',
time: {
unit: 'month',
},
grid: {
color: theme.palette.divider,
borderColor: theme.palette.divider,
},
ticks: {
color: theme.palette.text.secondary,
},
},
},
}) as const;

interface IFlagsChartComponentProps {
projectFlagTrends: ExecutiveSummarySchema['projectFlagTrends'];
}

const FlagsProjectChart: VFC<IFlagsChartComponentProps> = ({
projectFlagTrends,
}) => {
const theme = useTheme();
const { locationSettings } = useLocationSettings();
const data = useMemo(
() => createData(theme, projectFlagTrends),
[theme, projectFlagTrends],
);
const options = createOptions(theme, locationSettings);

return (
<Paper sx={(theme) => ({ padding: theme.spacing(4) })}>
<Typography
variant='h3'
sx={(theme) => ({ marginBottom: theme.spacing(3) })}
>
Number of flags per project
</Typography>
<Line options={options} data={data} />
</Paper>
);
};

ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
TimeScale,
Title,
Tooltip,
Legend,
);

export default FlagsProjectChart;
Expand Up @@ -32,6 +32,7 @@ export const useExecutiveDashboard = (
flags: { total: 0 },
userTrends: [],
flagTrends: [],
projectFlagTrends: [],
},
refetchExecutiveDashboard,
loading: !error && !data,
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/openapi/models/executiveSummarySchema.ts
Expand Up @@ -5,6 +5,7 @@
*/
import type { ExecutiveSummarySchemaFlags } from './executiveSummarySchemaFlags';
import type { ExecutiveSummarySchemaFlagTrendsItem } from './executiveSummarySchemaFlagTrendsItem';
import type { ExecutiveSummarySchemaProjectFlagTrendsItem } from './executiveSummarySchemaProjectFlagTrendsItem';
import type { ExecutiveSummarySchemaUsers } from './executiveSummarySchemaUsers';
import type { ExecutiveSummarySchemaUserTrendsItem } from './executiveSummarySchemaUserTrendsItem';

Expand All @@ -16,6 +17,8 @@ export interface ExecutiveSummarySchema {
flags: ExecutiveSummarySchemaFlags;
/** How number of flags changed over time */
flagTrends: ExecutiveSummarySchemaFlagTrendsItem[];
/** How number of flags per project changed over time */
projectFlagTrends: ExecutiveSummarySchemaProjectFlagTrendsItem[];
/** High level user count statistics */
users: ExecutiveSummarySchemaUsers;
/** How number of users changed over time */
Expand Down
Expand Up @@ -10,7 +10,7 @@ export type ExecutiveSummarySchemaFlagTrendsItem = {
/** A UTC date when the stats were captured. Time is the very end of a given day. */
date: string;
/** The number of time calculated potentially stale flags on a particular day */
potentiallyStale?: number;
potentiallyStale: number;
/** The number of user marked stale flags on a particular day */
stale: number;
/** The number of all flags on a particular day */
Expand Down
@@ -0,0 +1,24 @@
/**
* Generated by Orval
* Do not edit manually.
* See `gen:api` script in package.json
*/

export type ExecutiveSummarySchemaProjectFlagTrendsItem = {
/** The number of active flags on a particular day */
active: number;
/** A UTC date when the stats were captured. Time is the very end of a given day. */
date: string;
/** An indicator of the [project's health](https://docs.getunleash.io/reference/technical-debt#health-rating) on a scale from 0 to 100 */
health?: number;
/** The number of time calculated potentially stale flags on a particular day */
potentiallyStale: number;
/** Project id of the project the flag trends belong to */
project: string;
/** The number of user marked stale flags on a particular day */
stale: number;
/** The average time from when a feature was created to when it was enabled in the "production" environment during the current window */
timeToProduction?: number;
/** The number of all flags on a particular day */
total: number;
};
1 change: 1 addition & 0 deletions frontend/src/openapi/models/index.ts
Expand Up @@ -497,6 +497,7 @@ export * from './eventsSchemaVersion';
export * from './executiveSummarySchema';
export * from './executiveSummarySchemaFlagTrendsItem';
export * from './executiveSummarySchemaFlags';
export * from './executiveSummarySchemaProjectFlagTrendsItem';
export * from './executiveSummarySchemaUserTrendsItem';
export * from './executiveSummarySchemaUsers';
export * from './exportFeatures404';
Expand Down

0 comments on commit aae1d05

Please sign in to comment.