diff --git a/dashboard/src/locales/messages/index.ts b/dashboard/src/locales/messages/index.ts index 9e8c264..ee4517e 100644 --- a/dashboard/src/locales/messages/index.ts +++ b/dashboard/src/locales/messages/index.ts @@ -9,6 +9,11 @@ export const messages = { filters: 'Filters', seconds: 'sec', successful: 'Successful', + architecture: 'Architecture', + branch: 'Branch', + configs: 'Configs', + status: 'Status', + timing: 'Timing', }, routes: { deviceMonitor: 'Devices', @@ -45,11 +50,16 @@ export const messages = { tree: 'Tree', }, filter: { - min: 'Min', - max: 'Max', + architectureSubtitle: 'Please select one or more Architectures:', + branchSubtitle: 'Please select one or more Branches:', + configsSubtitle: 'Please select one or more configs:', filtering: 'Filtering', - treeURL: 'Tree URL', + max: 'Max', + min: 'Min', refresh: 'Refresh', + statusSubtitle: 'Please select one or more Status:', + treeURL: 'Tree URL', + timingSubtitle: 'Please select a range of timing:', }, }, }; diff --git a/dashboard/src/routes/TreeDetails/TreeDetails.tsx b/dashboard/src/routes/TreeDetails/TreeDetails.tsx index 81dddf4..303bfd1 100644 --- a/dashboard/src/routes/TreeDetails/TreeDetails.tsx +++ b/dashboard/src/routes/TreeDetails/TreeDetails.tsx @@ -1,19 +1,19 @@ import { useParams } from 'react-router-dom'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; -import { FormattedMessage } from 'react-intl'; - -import { MdExpandMore } from 'react-icons/md'; - -import TreeDetailsTab from '@/components/Tabs/TreeDetailsTab'; -import ButtonWithIcon from '@/components/Button/ButtonWithIcon'; import { useTreeDetails } from '@/api/TreeDetails'; - +import TreeDetailsTab from '@/components/Tabs/TreeDetailsTab'; import { IListingItem } from '@/components/ListingItem/ListingItem'; import { ISummaryItem } from '@/components/Summary/Summary'; -import { Results } from '@/types/tree/TreeDetails'; import { AccordionItemBuildsTrigger } from '@/components/Accordion/Accordion'; +import { + Results, + TTreeDetailsFilter, + TreeDetails as TreeDetailsType, +} from '@/types/tree/TreeDetails'; + +import TreeDetailsFilter from './TreeDetailsFilter'; export interface ITreeDetails { archs: ISummaryItem[]; @@ -24,9 +24,16 @@ export interface ITreeDetails { const TreeDetails = (): JSX.Element => { const { treeId } = useParams(); - const { data } = useTreeDetails(treeId ?? ''); + const [filter, setFilter] = useState< + TTreeDetailsFilter | Record + >({}); + const { data } = useTreeDetails(treeId ?? '', filter); const [treeDetailsData, setTreeDetailsData] = useState(); + const initialDataRef = useRef(); + if (!initialDataRef.current) { + initialDataRef.current = data; + } useEffect(() => { if (data) { @@ -75,9 +82,9 @@ const TreeDetails = (): JSX.Element => {
- } - label={} +
diff --git a/dashboard/src/routes/TreeDetails/TreeDetailsFilter.tsx b/dashboard/src/routes/TreeDetails/TreeDetailsFilter.tsx new file mode 100644 index 0000000..cdaa5ea --- /dev/null +++ b/dashboard/src/routes/TreeDetails/TreeDetailsFilter.tsx @@ -0,0 +1,132 @@ +import { useCallback, useMemo } from 'react'; +import { useIntl } from 'react-intl'; + +import FilterDrawer from '@/components/Filter/Drawer'; +import FilterSummarySection from '@/components/Filter/SummarySection'; +import FilterCheckboxSection, { + ICheckboxSection, +} from '@/components/Filter/CheckboxSection'; +import { + TreeDetails as TreeDetailsType, + TTreeDetailsFilter, +} from '@/types/tree/TreeDetails'; + +interface ITreeDetailsFilter { + data?: TreeDetailsType; + onFilter: (filter: TTreeDetailsFilter) => void; +} + +type TFilterApplied = { [key: string]: boolean }; + +const sanitizeData = ( + data: TreeDetailsType | undefined, +): [TFilterApplied, TFilterApplied, TFilterApplied, TFilterApplied, string] => { + const status = { TRUE: false, FALSE: false }; + const branches: TFilterApplied = {}; + const configs: TFilterApplied = {}; + const archs: TFilterApplied = {}; + let treeUrl = ''; + + if (data) + data.builds.forEach(b => { + if (b.git_repository_branch) branches[b.git_repository_branch] = false; + if (b.config_name) configs[b.config_name] = false; + if (b.architecture) archs[b.architecture] = false; + if (!treeUrl && b.git_repository_url) treeUrl = b.git_repository_url; + }); + + return [status, branches, configs, archs, treeUrl]; +}; + +const getFilterListFromObj = (filterObj: TFilterApplied): string[] => + Object.keys(filterObj).filter(key => filterObj[key]); + +const TreeDetailsFilter = ({ + data, + onFilter, +}: ITreeDetailsFilter): JSX.Element => { + const intl = useIntl(); + + const [statusObj, branchObj, configObj, archObj, treeUrl] = useMemo( + () => sanitizeData(data), + [data], + ); + + const onClickFilterHandle = useCallback(() => { + const filter: TTreeDetailsFilter = {}; + + filter.config_name = getFilterListFromObj(configObj); + filter.git_repository_branch = getFilterListFromObj(branchObj); + filter.architecture = getFilterListFromObj(archObj); + filter.valid = getFilterListFromObj(statusObj); + + onFilter(filter); + }, [onFilter, configObj, branchObj, archObj, statusObj]); + + const checkboxSectionsProps: ICheckboxSection[] = useMemo(() => { + return [ + { + title: intl.formatMessage({ id: 'global.branch' }), + subtitle: intl.formatMessage({ id: 'filter.branchSubtitle' }), + items: branchObj, + onClickItem: (branch: string, isChecked: boolean) => + (branchObj[branch] = isChecked), + }, + { + title: intl.formatMessage({ id: 'global.status' }), + subtitle: intl.formatMessage({ id: 'filter.statusSubtitle' }), + items: Object.keys(statusObj).reduce((acc, k) => { + const newKey = k == 'TRUE' ? 'valid' : 'invalid'; + acc[newKey] = statusObj[k]; + return acc; + }, {} as TFilterApplied), + onClickItem: (status: string, isChecked: boolean) => + (statusObj[status == 'valid' ? 'TRUE' : 'FALSE'] = isChecked), + }, + { + title: intl.formatMessage({ id: 'global.configs' }), + subtitle: intl.formatMessage({ id: 'filter.configsSubtitle' }), + items: configObj, + onClickItem: (config: string, isChecked: boolean) => + (configObj[config] = isChecked), + }, + { + title: intl.formatMessage({ id: 'global.architecture' }), + subtitle: intl.formatMessage({ id: 'filter.architectureSubtitle' }), + items: archObj, + onClickItem: (arch: string, isChecked: boolean) => + (archObj[arch] = isChecked), + }, + ]; + }, [statusObj, branchObj, configObj, archObj, intl]); + + const checkboxSectionsComponents = useMemo( + () => + checkboxSectionsProps.map(props => ( + + )), + [checkboxSectionsProps], + ); + + return ( + + + {checkboxSectionsComponents} + + ); +}; + +export default TreeDetailsFilter; + +const summarySectionProps = { + title: 'Tree', + columns: [ + { title: 'Tree', value: 'stable-rc' }, + { title: 'Matainer', value: '' }, + { title: 'Estimate to complete', value: '' }, + { + title: 'Commit/tag', + value: '5.15.150-rc1 - 3ab4d9c9e190217ee7e974c70b96795cd2f74611', + }, + ], +}; diff --git a/dashboard/src/types/tree/TreeDetails.tsx b/dashboard/src/types/tree/TreeDetails.tsx index 4f3205b..e0b9e17 100644 --- a/dashboard/src/types/tree/TreeDetails.tsx +++ b/dashboard/src/types/tree/TreeDetails.tsx @@ -37,3 +37,12 @@ export type Results = { invalid: number; null: number; }; +export interface TTreeDetailsFilter + extends Partial<{ + [K in keyof Omit< + TreeDetailsBuild, + 'test_status' | 'misc' | 'valid' + >]: TreeDetailsBuild[K][]; + }> { + valid?: string[]; +}