diff --git a/frontend/src/component/common/ProjectSelect/ProjectSelect.tsx b/frontend/src/component/common/ProjectSelect/ProjectSelect.tsx new file mode 100644 index 0000000000..d54171d0ce --- /dev/null +++ b/frontend/src/component/common/ProjectSelect/ProjectSelect.tsx @@ -0,0 +1,112 @@ +import { ComponentProps, Dispatch, SetStateAction, VFC } from 'react'; +import { Autocomplete, SxProps, TextField } from '@mui/material'; +import { renderOption } from 'component/playground/Playground/PlaygroundForm/renderOption'; +import useProjects from 'hooks/api/getters/useProjects/useProjects'; + +interface IOption { + label: string; + id: string; +} + +export const allOption = { label: 'ALL', id: '*' }; + +interface IProjectSelectProps { + selectedProjects: string[]; + onChange: Dispatch>; + dataTestId?: string; + sx?: SxProps; + disabled?: boolean; +} + +function findAllIndexes(arr: string[], name: string): number[] { + const indexes: number[] = []; + arr.forEach((currentValue, index) => { + if (currentValue === name) { + indexes.push(index); + } + }); + return indexes; +} + +export const ProjectSelect: VFC = ({ + selectedProjects, + onChange, + dataTestId, + sx, + disabled, +}) => { + const { projects: availableProjects } = useProjects(); + + const projectNames = availableProjects.map(({ name }) => name); + + const projectsOptions = [ + allOption, + ...availableProjects.map(({ name, id }) => { + const indexes = findAllIndexes(projectNames, name); + const isDuplicate = indexes.length > 1; + + return { + label: isDuplicate ? `${name} - (${id})` : name, + 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' + disabled={disabled} + value={ + isAllProjects + ? allOption + : projectsOptions.filter(({ id }) => + selectedProjects.includes(id), + ) + } + onChange={onProjectsChange} + data-testid={dataTestId ? dataTestId : 'PROJECT_SELECT'} + /> + ); +}; diff --git a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx index 39e0b0ee47..9140820e17 100644 --- a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx +++ b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx @@ -1,5 +1,11 @@ import { useMemo, useState, VFC } from 'react'; -import { Box, styled, useMediaQuery, useTheme } from '@mui/material'; +import { + Box, + styled, + Typography, + useMediaQuery, + useTheme, +} from '@mui/material'; import { UsersChart } from './UsersChart/UsersChart'; import { FlagsChart } from './FlagsChart/FlagsChart'; import { useExecutiveDashboard } from 'hooks/api/getters/useExecutiveSummary/useExecutiveSummary'; @@ -10,7 +16,10 @@ 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'; +import { + ProjectSelect, + allOption, +} from '../common/ProjectSelect/ProjectSelect'; import { MetricsSummaryChart } from './MetricsSummaryChart/MetricsSummaryChart'; import { ExecutiveSummarySchemaMetricsSummaryTrendsItem, @@ -26,6 +35,18 @@ const StyledGrid = styled(Box)(({ theme }) => ({ gap: theme.spacing(2), })); +const StyledBox = styled(Box)(({ theme }) => ({ + marginBottom: theme.spacing(4), + marginTop: theme.spacing(4), + [theme.breakpoints.down('lg')]: { + width: '100%', + marginLeft: 0, + }, + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', +})); + const useDashboardGrid = () => { const theme = useTheme(); const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg')); @@ -153,7 +174,17 @@ export const ExecutiveDashboard: VFC = () => { /> - + + + Insights per project + + + ({ - marginBottom: theme.spacing(4), - marginTop: theme.spacing(4), - [theme.breakpoints.down('lg')]: { - width: '100%', - marginLeft: 0, - }, - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', -})); - -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 ( - - - Insights per project - - ( - - )} - renderOption={renderOption} - getOptionLabel={({ label }) => label} - disableCloseOnSelect - size='small' - value={ - isAllProjects - ? allOption - : projectsOptions.filter(({ id }) => - selectedProjects.includes(id), - ) - } - onChange={onProjectsChange} - data-testid={'DASHBOARD_PROJECT_SELECT'} - /> - - ); -}; diff --git a/frontend/src/component/executiveDashboard/hooks/useFilteredTrends.ts b/frontend/src/component/executiveDashboard/hooks/useFilteredTrends.ts index cd334b3db8..3bd1c44ee0 100644 --- a/frontend/src/component/executiveDashboard/hooks/useFilteredTrends.ts +++ b/frontend/src/component/executiveDashboard/hooks/useFilteredTrends.ts @@ -1,5 +1,5 @@ import { useMemo } from 'react'; -import { allOption } from '../ProjectSelect/ProjectSelect'; +import { allOption } from 'component/common/ProjectSelect/ProjectSelect'; export const useFilteredTrends = < T extends { diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx index f233faa6aa..1706fbddaa 100644 --- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx @@ -1,4 +1,4 @@ -import { ComponentProps, useState, VFC } from 'react'; +import { ComponentProps, Dispatch, SetStateAction, useState, VFC } from 'react'; import { Autocomplete, Box, @@ -22,14 +22,15 @@ import { validateTokenFormat, } from '../../playground.utils'; import { Clear } from '@mui/icons-material'; +import { ProjectSelect } from '../../../../common/ProjectSelect/ProjectSelect'; interface IPlaygroundConnectionFieldsetProps { environments: string[]; projects: string[]; token?: string; - setProjects: (projects: string[]) => void; - setEnvironments: (environments: string[]) => void; - setToken?: (token: string) => void; + setProjects: Dispatch>; + setEnvironments: Dispatch>; + setToken?: Dispatch>; availableEnvironments: string[]; } @@ -76,33 +77,6 @@ export const PlaygroundConnectionFieldset: VFC< })), ]; - const onProjectsChange: ComponentProps['onChange'] = ( - event, - value, - reason, - ) => { - const newProjects = value as IOption | IOption[]; - if (reason === 'clear' || newProjects === null) { - return setProjects([allOption.id]); - } - if (Array.isArray(newProjects)) { - if (newProjects.length === 0) { - return setProjects([allOption.id]); - } - if ( - newProjects.find(({ id }) => id === allOption.id) !== undefined - ) { - return setProjects([allOption.id]); - } - return setProjects(newProjects.map(({ id }) => id)); - } - if (newProjects.id === allOption.id) { - return setProjects([allOption.id]); - } - - return setProjects([newProjects.id]); - }; - const onEnvironmentsChange: ComponentProps< typeof Autocomplete >['onChange'] = (event, value, reason) => { @@ -120,11 +94,6 @@ export const PlaygroundConnectionFieldset: VFC< return setEnvironments([newEnvironments.id]); }; - const isAllProjects = - projects && - (projects.length === 0 || - (projects.length === 1 && projects[0] === '*')); - const envValue = environmentOptions.filter(({ id }) => environments.includes(id), ); @@ -235,68 +204,53 @@ export const PlaygroundConnectionFieldset: VFC< - - ( - - )} - renderOption={renderOption} - getOptionLabel={({ label }) => label} - disableCloseOnSelect={false} - size='small' - value={envValue} - onChange={onEnvironmentsChange} - disabled={Boolean(token)} - data-testid={'PLAYGROUND_ENVIRONMENT_SELECT'} - /> - - - ( - - )} - renderOption={renderOption} - getOptionLabel={({ label }) => label} - disableCloseOnSelect - size='small' - value={ - isAllProjects - ? allOption - : projectsOptions.filter(({ id }) => - projects.includes(id), - ) + + - + > + ( + + )} + renderOption={renderOption} + getOptionLabel={({ label }) => label} + disableCloseOnSelect={false} + size='small' + value={envValue} + onChange={onEnvironmentsChange} + disabled={Boolean(token)} + data-testid={'PLAYGROUND_ENVIRONMENT_SELECT'} + /> + + + + + + +