mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
feat: dashboard project filtering (#6259)
Adds the same project selector Autocomplete as we use in the playground. Implements the filtering Closes: # [1-2036](https://linear.app/unleash/issue/1-2036/api-project-filtering) <img width="1508" alt="Screenshot 2024-02-16 at 15 57 24" src="https://github.com/Unleash/unleash/assets/104830839/4490e43c-17db-41b6-ba75-e7b0f2df0522"> --------- Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
parent
9b980bb212
commit
f71badd255
@ -1,7 +1,9 @@
|
|||||||
import { useMemo, VFC } from 'react';
|
import { ComponentProps, useEffect, useMemo, useState, VFC } from 'react';
|
||||||
import {
|
import {
|
||||||
|
Autocomplete,
|
||||||
Box,
|
Box,
|
||||||
styled,
|
styled,
|
||||||
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
@ -17,6 +19,7 @@ import { FlagsProjectChart } from './FlagsProjectChart/FlagsProjectChart';
|
|||||||
import { ProjectHealthChart } from './ProjectHealthChart/ProjectHealthChart';
|
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';
|
||||||
|
|
||||||
const StyledGrid = styled(Box)(({ theme }) => ({
|
const StyledGrid = styled(Box)(({ theme }) => ({
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
@ -61,6 +64,7 @@ const useDashboardGrid = () => {
|
|||||||
|
|
||||||
export const ExecutiveDashboard: VFC = () => {
|
export const ExecutiveDashboard: VFC = () => {
|
||||||
const { executiveDashboardData, loading, error } = useExecutiveDashboard();
|
const { executiveDashboardData, loading, error } = useExecutiveDashboard();
|
||||||
|
const [projects, setProjects] = useState([allOption.id]);
|
||||||
|
|
||||||
const flagPerUsers = useMemo(() => {
|
const flagPerUsers = useMemo(() => {
|
||||||
if (
|
if (
|
||||||
@ -75,6 +79,16 @@ export const ExecutiveDashboard: VFC = () => {
|
|||||||
).toFixed(1);
|
).toFixed(1);
|
||||||
}, [executiveDashboardData]);
|
}, [executiveDashboardData]);
|
||||||
|
|
||||||
|
const filteredProjectFlagTrends = useMemo(() => {
|
||||||
|
if (projects[0] === allOption.id) {
|
||||||
|
return executiveDashboardData.projectFlagTrends;
|
||||||
|
}
|
||||||
|
|
||||||
|
return executiveDashboardData.projectFlagTrends.filter((trend) =>
|
||||||
|
projects.includes(trend.project),
|
||||||
|
);
|
||||||
|
}, [executiveDashboardData, projects]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
gridTemplateColumns,
|
gridTemplateColumns,
|
||||||
chartSpan,
|
chartSpan,
|
||||||
@ -120,15 +134,16 @@ export const ExecutiveDashboard: VFC = () => {
|
|||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
/>
|
/>
|
||||||
</Widget>
|
</Widget>
|
||||||
|
</StyledGrid>
|
||||||
|
<ProjectSelect selectedProjects={projects} onChange={setProjects} />
|
||||||
|
<StyledGrid>
|
||||||
<Widget
|
<Widget
|
||||||
title='Number of flags per project'
|
title='Number of flags per project'
|
||||||
order={5}
|
order={5}
|
||||||
span={largeChartSpan}
|
span={largeChartSpan}
|
||||||
>
|
>
|
||||||
<FlagsProjectChart
|
<FlagsProjectChart
|
||||||
projectFlagTrends={
|
projectFlagTrends={filteredProjectFlagTrends}
|
||||||
executiveDashboardData.projectFlagTrends
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget
|
<Widget
|
||||||
@ -137,9 +152,7 @@ export const ExecutiveDashboard: VFC = () => {
|
|||||||
span={largeChartSpan}
|
span={largeChartSpan}
|
||||||
>
|
>
|
||||||
<ProjectHealthChart
|
<ProjectHealthChart
|
||||||
projectFlagTrends={
|
projectFlagTrends={filteredProjectFlagTrends}
|
||||||
executiveDashboardData.projectFlagTrends
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget title='Average time to production' order={7}>
|
<Widget title='Average time to production' order={7}>
|
||||||
@ -148,9 +161,7 @@ export const ExecutiveDashboard: VFC = () => {
|
|||||||
</Widget>
|
</Widget>
|
||||||
<Widget title='Time to production' order={8} span={chartSpan}>
|
<Widget title='Time to production' order={8} span={chartSpan}>
|
||||||
<TimeToProductionChart
|
<TimeToProductionChart
|
||||||
projectFlagTrends={
|
projectFlagTrends={filteredProjectFlagTrends}
|
||||||
executiveDashboardData.projectFlagTrends
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Widget>
|
</Widget>
|
||||||
</StyledGrid>
|
</StyledGrid>
|
||||||
|
@ -0,0 +1,103 @@
|
|||||||
|
import { ComponentProps, Dispatch, SetStateAction, VFC } from 'react';
|
||||||
|
import { Autocomplete, Box, styled, TextField } from '@mui/material';
|
||||||
|
import { renderOption } from '../../playground/Playground/PlaygroundForm/renderOption';
|
||||||
|
import useProjects from '../../../hooks/api/getters/useProjects/useProjects';
|
||||||
|
|
||||||
|
const StyledBox = styled(Box)(({ theme }) => ({
|
||||||
|
width: '25%',
|
||||||
|
marginLeft: '75%',
|
||||||
|
marginBottom: theme.spacing(4),
|
||||||
|
marginTop: theme.spacing(4),
|
||||||
|
[theme.breakpoints.down('lg')]: {
|
||||||
|
width: '100%',
|
||||||
|
marginLeft: 0,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IOption {
|
||||||
|
label: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const allOption = { label: 'ALL', id: '*' };
|
||||||
|
|
||||||
|
interface IProjectSelectProps {
|
||||||
|
selectedProjects: string[];
|
||||||
|
onChange: Dispatch<SetStateAction<string[]>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectSelect: VFC<IProjectSelectProps> = ({
|
||||||
|
selectedProjects,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
const { projects: availableProjects } = useProjects();
|
||||||
|
|
||||||
|
const projectsOptions = [
|
||||||
|
allOption,
|
||||||
|
...availableProjects.map(({ name: label, id }) => ({
|
||||||
|
label,
|
||||||
|
id,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
|
||||||
|
const isAllProjects =
|
||||||
|
selectedProjects &&
|
||||||
|
(selectedProjects.length === 0 ||
|
||||||
|
(selectedProjects.length === 1 && selectedProjects[0] === '*'));
|
||||||
|
|
||||||
|
const onProjectsChange: ComponentProps<typeof Autocomplete>['onChange'] = (
|
||||||
|
event,
|
||||||
|
value,
|
||||||
|
reason,
|
||||||
|
) => {
|
||||||
|
const newProjects = value as IOption | IOption[];
|
||||||
|
if (reason === 'clear' || newProjects === null) {
|
||||||
|
return onChange([allOption.id]);
|
||||||
|
}
|
||||||
|
if (Array.isArray(newProjects)) {
|
||||||
|
if (newProjects.length === 0) {
|
||||||
|
return onChange([allOption.id]);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
newProjects.find(({ id }) => id === allOption.id) !== undefined
|
||||||
|
) {
|
||||||
|
return onChange([allOption.id]);
|
||||||
|
}
|
||||||
|
return onChange(newProjects.map(({ id }) => id));
|
||||||
|
}
|
||||||
|
if (newProjects.id === allOption.id) {
|
||||||
|
return onChange([allOption.id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return onChange([newProjects.id]);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledBox>
|
||||||
|
<Autocomplete
|
||||||
|
disablePortal
|
||||||
|
id='projects'
|
||||||
|
limitTags={3}
|
||||||
|
multiple={!isAllProjects}
|
||||||
|
options={projectsOptions}
|
||||||
|
sx={{ flex: 1 }}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField {...params} label='Projects' />
|
||||||
|
)}
|
||||||
|
renderOption={renderOption}
|
||||||
|
getOptionLabel={({ label }) => label}
|
||||||
|
disableCloseOnSelect
|
||||||
|
size='small'
|
||||||
|
value={
|
||||||
|
isAllProjects
|
||||||
|
? allOption
|
||||||
|
: projectsOptions.filter(({ id }) =>
|
||||||
|
selectedProjects.includes(id),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onChange={onProjectsChange}
|
||||||
|
data-testid={'DASHBOARD_PROJECT_SELECT'}
|
||||||
|
/>
|
||||||
|
</StyledBox>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user