Skip to content

Commit

Permalink
feat: dashboard project filtering (#6259)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
andreas-unleash committed Feb 19, 2024
1 parent 9b980bb commit f71badd
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 10 deletions.
31 changes: 21 additions & 10 deletions frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx
@@ -1,7 +1,9 @@
import { useMemo, VFC } from 'react';
import { ComponentProps, useEffect, useMemo, useState, VFC } from 'react';
import {
Autocomplete,
Box,
styled,
TextField,
Typography,
useMediaQuery,
useTheme,
Expand All @@ -17,6 +19,7 @@ import { FlagsProjectChart } from './FlagsProjectChart/FlagsProjectChart';
import { ProjectHealthChart } from './ProjectHealthChart/ProjectHealthChart';
import { TimeToProductionChart } from './TimeToProductionChart/TimeToProductionChart';
import { TimeToProduction } from './TimeToProduction/TimeToProduction';
import { ProjectSelect, allOption } from './ProjectSelect/ProjectSelect';

const StyledGrid = styled(Box)(({ theme }) => ({
display: 'grid',
Expand Down Expand Up @@ -61,6 +64,7 @@ const useDashboardGrid = () => {

export const ExecutiveDashboard: VFC = () => {
const { executiveDashboardData, loading, error } = useExecutiveDashboard();
const [projects, setProjects] = useState([allOption.id]);

const flagPerUsers = useMemo(() => {
if (
Expand All @@ -75,6 +79,16 @@ export const ExecutiveDashboard: VFC = () => {
).toFixed(1);
}, [executiveDashboardData]);

const filteredProjectFlagTrends = useMemo(() => {
if (projects[0] === allOption.id) {
return executiveDashboardData.projectFlagTrends;
}

return executiveDashboardData.projectFlagTrends.filter((trend) =>
projects.includes(trend.project),
);
}, [executiveDashboardData, projects]);

const {
gridTemplateColumns,
chartSpan,
Expand Down Expand Up @@ -120,15 +134,16 @@ export const ExecutiveDashboard: VFC = () => {
isLoading={loading}
/>
</Widget>
</StyledGrid>
<ProjectSelect selectedProjects={projects} onChange={setProjects} />
<StyledGrid>
<Widget
title='Number of flags per project'
order={5}
span={largeChartSpan}
>
<FlagsProjectChart
projectFlagTrends={
executiveDashboardData.projectFlagTrends
}
projectFlagTrends={filteredProjectFlagTrends}
/>
</Widget>
<Widget
Expand All @@ -137,9 +152,7 @@ export const ExecutiveDashboard: VFC = () => {
span={largeChartSpan}
>
<ProjectHealthChart
projectFlagTrends={
executiveDashboardData.projectFlagTrends
}
projectFlagTrends={filteredProjectFlagTrends}
/>
</Widget>
<Widget title='Average time to production' order={7}>
Expand All @@ -148,9 +161,7 @@ export const ExecutiveDashboard: VFC = () => {
</Widget>
<Widget title='Time to production' order={8} span={chartSpan}>
<TimeToProductionChart
projectFlagTrends={
executiveDashboardData.projectFlagTrends
}
projectFlagTrends={filteredProjectFlagTrends}
/>
</Widget>
</StyledGrid>
Expand Down
@@ -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>
);
};

0 comments on commit f71badd

Please sign in to comment.