From f71badd2558634a06148037d09cc5d0e8809b568 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Mon, 19 Feb 2024 16:28:03 +0200 Subject: [PATCH] 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) Screenshot 2024-02-16 at 15 57 24 --------- Signed-off-by: andreas-unleash --- .../executiveDashboard/ExecutiveDashboard.tsx | 31 ++++-- .../ProjectSelect/ProjectSelect.tsx | 103 ++++++++++++++++++ 2 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 frontend/src/component/executiveDashboard/ProjectSelect/ProjectSelect.tsx diff --git a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx index d40b0189cd..bc9c3723bb 100644 --- a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx +++ b/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, @@ -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', @@ -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 ( @@ -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, @@ -120,15 +134,16 @@ export const ExecutiveDashboard: VFC = () => { isLoading={loading} /> + + + { span={largeChartSpan} > @@ -148,9 +161,7 @@ export const ExecutiveDashboard: VFC = () => { diff --git a/frontend/src/component/executiveDashboard/ProjectSelect/ProjectSelect.tsx b/frontend/src/component/executiveDashboard/ProjectSelect/ProjectSelect.tsx new file mode 100644 index 0000000000..88d58a4332 --- /dev/null +++ b/frontend/src/component/executiveDashboard/ProjectSelect/ProjectSelect.tsx @@ -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>; +} + +export const ProjectSelect: VFC = ({ + 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['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 ( + + ( + + )} + renderOption={renderOption} + getOptionLabel={({ label }) => label} + disableCloseOnSelect + size='small' + value={ + isAllProjects + ? allOption + : projectsOptions.filter(({ id }) => + selectedProjects.includes(id), + ) + } + onChange={onProjectsChange} + data-testid={'DASHBOARD_PROJECT_SELECT'} + /> + + ); +};