diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 712af100..09b9f200 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,6 +12,7 @@ "@patternfly/react-table": "^5.2.4", "@reduxjs/toolkit": "^2.2.3", "axios": "^1.6.8", + "lodash": "^4.17.21", "plotly.js": "^2.32.0", "react": "^18.2.0", "react-date-picker": "^10.6.0", diff --git a/frontend/package.json b/frontend/package.json index 6e9fd634..edb6cc1b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,7 @@ "@patternfly/react-table": "^5.2.4", "@reduxjs/toolkit": "^2.2.3", "axios": "^1.6.8", + "lodash": "^4.17.21", "plotly.js": "^2.32.0", "react": "^18.2.0", "react-date-picker": "^10.6.0", diff --git a/frontend/src/actions/commonActions.js b/frontend/src/actions/commonActions.js index 60976963..81546e7b 100644 --- a/frontend/src/actions/commonActions.js +++ b/frontend/src/actions/commonActions.js @@ -1,9 +1,10 @@ import * as TYPES from "@/actions/types.js"; +import { setCatFilters, sliceTableRows } from "./homeActions"; import { setOCPCatFilters, sliceOCPTableRows } from "./ocpActions"; import { DEFAULT_PER_PAGE } from "@/assets/constants/paginationConstants"; -import { sliceTableRows } from "./homeActions"; +import { cloneDeep } from "lodash"; const getSortableRowValues = (result, tableColumns) => { const tableKeys = tableColumns.map((item) => item.value); @@ -75,7 +76,8 @@ export const calculateMetrics = (results) => { }; export const buildFilterData = (currState) => (dispatch, getState) => { - const results = [...getState()[currState].results]; + const results = [...getState()[currState].filteredResults]; + const categoryFilterValue = getState()[currState].categoryFilterValue; const tableFilters = [...getState()[currState].tableFilters]; @@ -85,11 +87,19 @@ export const buildFilterData = (currState) => (dispatch, getState) => { let obj = { name: filter.name, key, - value: [...new Set(results.map((item) => item[key]))], + value: [ + ...new Set(results.map((item) => item[key]?.toString()?.toLowerCase())), + ], }; filterData.push(obj); } - dispatch(setFilterData(filterData, currState, tableFilters[0].name)); + dispatch( + setFilterData( + filterData, + currState, + categoryFilterValue || tableFilters[0].name + ) + ); }; const setFilterData = (filterData, currState, activeFilter) => (dispatch) => { @@ -99,6 +109,12 @@ const setFilterData = (filterData, currState, activeFilter) => (dispatch) => { payload: filterData, }); dispatch(setOCPCatFilters(activeFilter)); + } else if (currState === "cpt") { + dispatch({ + type: TYPES.SET_CPT_FILTER_DATA, + payload: filterData, + }); + dispatch(setCatFilters(activeFilter)); } }; @@ -111,10 +127,10 @@ export const getFilteredData = (appliedFilters, results) => { if (isFilterApplied) { filtered = results.filter((el) => { for (const key in appliedFilters) { - if ( - el[key]?.toString()?.toLowerCase() !== - appliedFilters[key]?.toString()?.toLowerCase() - ) { + const valueMap = appliedFilters[key]?.map((i) => + i?.toString()?.toLowerCase() + ); + if (!valueMap.includes(el[key]?.toString()?.toLowerCase())) { return false; } } @@ -124,15 +140,40 @@ export const getFilteredData = (appliedFilters, results) => { return filtered; }; -export const getAppliedFilters = - (currState, selectedOption) => (dispatch, getState) => { - const { categoryFilterValue, filterData } = getState()[currState]; - const appliedFilters = { ...getState()[currState].appliedFilters }; +export const deleteAppliedFilters = + (filterKey, filterValue, currState) => (dispatch, getState) => { + const appliedFilters = cloneDeep(getState()[currState].appliedFilters); + + const index = appliedFilters[filterKey].indexOf( + filterValue?.toString()?.toLowerCase() + ); + if (index >= 0) { + appliedFilters[filterKey].splice(index, 1); + if (appliedFilters[filterKey].length === 0) { + delete appliedFilters[filterKey]; + } + } + return appliedFilters; + }; - const category = filterData.filter( - (item) => item.name === categoryFilterValue - )[0].key; - appliedFilters[category] = selectedOption; +export const getSelectedFilter = + (selectedCategory, selectedOption, currState, isFromMetrics) => + (dispatch, getState) => { + const selectedFilters = cloneDeep(getState()[currState].selectedFilters); - return appliedFilters; + const obj = selectedFilters.find((i) => i.name === selectedCategory); + selectedOption = selectedOption?.toString()?.toLowerCase(); + + const objValue = obj.value.map((i) => i?.toString()?.toLowerCase()); + + if (objValue.includes(selectedOption)) { + const arr = objValue.filter((selection) => selection !== selectedOption); + obj.value = arr; + } else { + obj.value = isFromMetrics + ? [selectedOption] + : [...obj.value, selectedOption]; + } + + return selectedFilters; }; diff --git a/frontend/src/actions/homeActions.js b/frontend/src/actions/homeActions.js index d050b494..84296a98 100644 --- a/frontend/src/actions/homeActions.js +++ b/frontend/src/actions/homeActions.js @@ -6,9 +6,17 @@ import { START_PAGE, } from "@/assets/constants/paginationConstants"; import { appendDateFilter, appendQueryString } from "@/utils/helper"; -import { calculateMetrics, getFilteredData, sortTable } from "./commonActions"; +import { + buildFilterData, + calculateMetrics, + deleteAppliedFilters, + getFilteredData, + getSelectedFilter, + sortTable, +} from "./commonActions"; import API from "@/utils/axiosInstance"; +import { cloneDeep } from "lodash"; import { showFailureToast } from "@/actions/toastActions"; export const fetchOCPJobsData = () => async (dispatch, getState) => { @@ -20,8 +28,6 @@ export const fetchOCPJobsData = () => async (dispatch, getState) => { pretty: true, ...(start_date && { start_date }), ...(end_date && { end_date }), - // start_date: "2024-04-21", - // end_date: "2024-04-22", }, }); if (response?.data?.results?.length > 0) { @@ -43,10 +49,7 @@ export const fetchOCPJobsData = () => async (dispatch, getState) => { }); dispatch(applyFilters()); dispatch(sortTable("cpt")); - dispatch(getCPTSummary()); - dispatch(setPageOptions(START_PAGE, DEFAULT_PER_PAGE)); - dispatch(sliceTableRows(0, DEFAULT_PER_PAGE)); - dispatch(buildFilterData()); + dispatch(tableReCalcValues()); } } catch (error) { dispatch(showFailureToast()); @@ -73,28 +76,6 @@ export const sliceTableRows = (startIdx, endIdx) => (dispatch, getState) => { }); }; -export const buildFilterData = () => (dispatch, getState) => { - const results = [...getState().cpt.results]; - - const tableFilters = [...getState().cpt.tableFilters]; - - const filterData = []; - for (const filter of tableFilters) { - const key = filter.value; - let obj = { - name: filter.name, - key, - value: [...new Set(results.map((item) => item[key]?.toLowerCase()))], - }; - filterData.push(obj); - } - dispatch({ - type: TYPES.SET_CPT_FILTER_DATA, - payload: filterData, - }); - dispatch(setCatFilters(tableFilters[0].name)); -}; - export const setCatFilters = (category) => (dispatch, getState) => { const filterData = [...getState().cpt.filterData]; const options = filterData.filter((item) => item.name === category)[0].value; @@ -110,64 +91,70 @@ export const setCatFilters = (category) => (dispatch, getState) => { }); }; -export const setAppliedFilters = - (selectedOption, navigate) => (dispatch, getState) => { - const { categoryFilterValue, filterData, start_date, end_date } = - getState().cpt; - const appliedFilters = { ...getState().cpt.appliedFilters }; - - const category = filterData.filter( - (item) => item.name === categoryFilterValue - )[0].key; - appliedFilters[category] = selectedOption; - +export const setSelectedFilterFromUrl = (params) => (dispatch, getState) => { + const selectedFilters = cloneDeep(getState().cpt.selectedFilters); + for (const key in params) { + selectedFilters.find((i) => i.name === key).value = params[key].split(","); + } + dispatch({ + type: TYPES.SET_SELECTED_FILTERS, + payload: selectedFilters, + }); +}; +export const setSelectedFilter = + (selectedCategory, selectedOption, isFromMetrics) => (dispatch) => { + const selectedFilters = dispatch( + getSelectedFilter(selectedCategory, selectedOption, "cpt", isFromMetrics) + ); dispatch({ - type: TYPES.SET_APPLIED_FILTERS, - payload: appliedFilters, + type: TYPES.SET_SELECTED_FILTERS, + payload: selectedFilters, }); - appendQueryString({ ...appliedFilters, start_date, end_date }, navigate); - dispatch(applyFilters()); }; -export const filterFromSummary = - (category, value, navigate) => (dispatch, getState) => { - const { start_date, end_date } = getState().cpt; - const appliedFilters = { ...getState().cpt.appliedFilters }; - appliedFilters[category] = value; - dispatch({ - type: TYPES.SET_APPLIED_FILTERS, - payload: appliedFilters, - }); - appendQueryString({ ...appliedFilters, start_date, end_date }, navigate); - dispatch(applyFilters()); - }; +export const setAppliedFilters = (navigate) => (dispatch, getState) => { + const { selectedFilters, start_date, end_date } = getState().cpt; + + const appliedFilterArr = selectedFilters.filter((i) => i.value.length > 0); + + const appliedFilters = {}; + appliedFilterArr.forEach((item) => { + appliedFilters[item["name"]] = item.value; + }); + + dispatch({ + type: TYPES.SET_APPLIED_FILTERS, + payload: appliedFilters, + }); + appendQueryString({ ...appliedFilters, start_date, end_date }, navigate); + dispatch(applyFilters()); +}; + export const setOtherSummaryFilter = () => (dispatch, getState) => { const filteredResults = [...getState().cpt.filteredResults]; - const keyWordArr = ["SUCCESS", "FAILURE"]; + const keyWordArr = ["success", "failure"]; const data = filteredResults.filter( - (item) => !keyWordArr.includes(item.jobStatus) + (item) => !keyWordArr.includes(item.jobStatus?.toLowerCase()) ); dispatch({ type: TYPES.SET_FILTERED_DATA, payload: data, }); - dispatch(getCPTSummary()); - dispatch(setPageOptions(START_PAGE, DEFAULT_PER_PAGE)); - dispatch(sliceTableRows(0, DEFAULT_PER_PAGE)); + dispatch(tableReCalcValues()); }; export const removeAppliedFilters = - (filterKey, navigate) => (dispatch, getState) => { - const appliedFilters = { ...getState().cpt.appliedFilters }; + (filterKey, filterValue, navigate) => (dispatch, getState) => { const { start_date, end_date } = getState().cpt; - const name = filterKey; - // eslint-disable-next-line no-unused-vars - const { [name]: removedProperty, ...remainingObject } = appliedFilters; + + const appliedFilters = dispatch( + deleteAppliedFilters(filterKey, filterValue, "cpt") + ); dispatch({ type: TYPES.SET_APPLIED_FILTERS, - payload: remainingObject, + payload: appliedFilters, }); - appendQueryString({ ...remainingObject, start_date, end_date }, navigate); + appendQueryString({ ...appliedFilters, start_date, end_date }, navigate); dispatch(applyFilters()); }; @@ -178,17 +165,18 @@ export const applyFilters = () => (dispatch, getState) => { const isFilterApplied = Object.keys(appliedFilters).length > 0 && - !Object.values(appliedFilters).includes(""); + Object.values(appliedFilters).flat().length > 0; - const filtered = getFilteredData(appliedFilters, results); + const filtered = isFilterApplied + ? getFilteredData(appliedFilters, results) + : results; dispatch({ type: TYPES.SET_FILTERED_DATA, - payload: isFilterApplied ? filtered : results, + payload: filtered, }); - dispatch(getCPTSummary()); - dispatch(setPageOptions(START_PAGE, DEFAULT_PER_PAGE)); - dispatch(sliceTableRows(0, DEFAULT_PER_PAGE)); + dispatch(tableReCalcValues()); + dispatch(buildFilterData("cpt")); }; export const setFilterFromURL = (searchParams) => ({ @@ -232,3 +220,9 @@ export const getCPTSummary = () => (dispatch, getState) => { payload: countObj, }); }; + +export const tableReCalcValues = () => (dispatch) => { + dispatch(getCPTSummary()); + dispatch(setPageOptions(START_PAGE, DEFAULT_PER_PAGE)); + dispatch(sliceTableRows(0, DEFAULT_PER_PAGE)); +}; diff --git a/frontend/src/actions/ocpActions.js b/frontend/src/actions/ocpActions.js index eff9cfa9..aae00a40 100644 --- a/frontend/src/actions/ocpActions.js +++ b/frontend/src/actions/ocpActions.js @@ -1,5 +1,5 @@ import * as API_ROUTES from "@/utils/apiConstants"; -import * as TYPES from "@/actions/types.js"; +import * as TYPES from "./types.js"; import { DEFAULT_PER_PAGE, @@ -9,13 +9,15 @@ import { appendDateFilter, appendQueryString } from "@/utils/helper.js"; import { buildFilterData, calculateMetrics, + deleteAppliedFilters, getFilteredData, + getSelectedFilter, sortTable, -} from "@/actions/commonActions.js"; +} from "./commonActions"; import API from "@/utils/axiosInstance"; -import { getAppliedFilters } from "./commonActions"; -import { showFailureToast } from "@/actions/toastActions"; +import { cloneDeep } from "lodash"; +import { showFailureToast } from "./toastActions"; export const fetchOCPJobs = () => async (dispatch, getState) => { try { @@ -26,8 +28,6 @@ export const fetchOCPJobs = () => async (dispatch, getState) => { pretty: true, ...(start_date && { start_date }), ...(end_date && { end_date }), - // start_date: "2024-04-21", - // end_date: "2024-04-22", }, }); if (response?.data?.results?.length > 0) { @@ -48,10 +48,7 @@ export const fetchOCPJobs = () => async (dispatch, getState) => { }); dispatch(applyFilters()); dispatch(sortTable("ocp")); - dispatch(setPageOptions(START_PAGE, DEFAULT_PER_PAGE)); - dispatch(sliceOCPTableRows(0, DEFAULT_PER_PAGE)); - dispatch(buildFilterData("ocp")); - dispatch(getOCPSummary()); + dispatch(tableReCalcValues()); } } catch (error) { dispatch(showFailureToast()); @@ -112,42 +109,66 @@ export const applyFilters = () => (dispatch, getState) => { Object.keys(appliedFilters).length > 0 && !Object.values(appliedFilters).includes(""); - const filtered = getFilteredData(appliedFilters, results); + const filtered = isFilterApplied + ? getFilteredData(appliedFilters, results) + : results; dispatch({ type: TYPES.SET_OCP_FILTERED_DATA, - payload: isFilterApplied ? filtered : results, + payload: filtered, }); - dispatch(getOCPSummary()); - dispatch(setPageOptions(START_PAGE, DEFAULT_PER_PAGE)); - dispatch(sliceOCPTableRows(0, DEFAULT_PER_PAGE)); + dispatch(tableReCalcValues()); + dispatch(buildFilterData("ocp")); }; -export const setAppliedFilters = - (selectedOption, navigate) => (dispatch, getState) => { - const appliedFilters = dispatch(getAppliedFilters("ocp", selectedOption)); - const { start_date, end_date } = getState().ocp; +export const setSelectedFilterFromUrl = (params) => (dispatch, getState) => { + const selectedFilters = cloneDeep(getState().ocp.selectedFilters); + for (const key in params) { + selectedFilters.find((i) => i.name === key).value = params[key].split(","); + } + dispatch({ + type: TYPES.SET_SELECTED_OCP_FILTERS, + payload: selectedFilters, + }); +}; +export const setSelectedFilter = + (selectedCategory, selectedOption, isFromMetrics) => (dispatch) => { + const selectedFilters = dispatch( + getSelectedFilter(selectedCategory, selectedOption, "ocp", isFromMetrics) + ); dispatch({ - type: TYPES.SET_OCP_APPLIED_FILTERS, - payload: appliedFilters, + type: TYPES.SET_SELECTED_OCP_FILTERS, + payload: selectedFilters, }); - appendQueryString({ ...appliedFilters, start_date, end_date }, navigate); - dispatch(applyFilters()); }; +export const setAppliedFilters = (navigate) => (dispatch, getState) => { + const { start_date, end_date, selectedFilters } = getState().ocp; + const appliedFilterArr = selectedFilters.filter((i) => i.value.length > 0); + + const appliedFilters = {}; + appliedFilterArr.forEach((item) => { + appliedFilters[item["name"]] = item.value; + }); + + dispatch({ + type: TYPES.SET_OCP_APPLIED_FILTERS, + payload: appliedFilters, + }); + appendQueryString({ ...appliedFilters, start_date, end_date }, navigate); + dispatch(applyFilters()); +}; export const removeAppliedFilters = - (filterKey, navigate) => (dispatch, getState) => { - const appliedFilters = { ...getState().ocp.appliedFilters }; + (filterKey, filterValue, navigate) => (dispatch, getState) => { + const appliedFilters = dispatch( + deleteAppliedFilters(filterKey, filterValue, "ocp") + ); const { start_date, end_date } = getState().ocp; - const name = filterKey; - // eslint-disable-next-line no-unused-vars - const { [name]: removedProperty, ...remainingObject } = appliedFilters; - dispatch({ type: TYPES.SET_OCP_APPLIED_FILTERS, - payload: remainingObject, + payload: appliedFilters, }); - appendQueryString({ ...remainingObject, start_date, end_date }, navigate); + appendQueryString({ ...appliedFilters, start_date, end_date }, navigate); dispatch(applyFilters()); }; @@ -173,32 +194,17 @@ export const setFilterFromURL = (searchParams) => ({ payload: searchParams, }); -export const filterFromSummary = - (category, value, navigate) => (dispatch, getState) => { - const { start_date, end_date } = getState().ocp; - const appliedFilters = { ...getState().ocp.appliedFilters }; - appliedFilters[category] = value; - dispatch({ - type: TYPES.SET_OCP_APPLIED_FILTERS, - payload: appliedFilters, - }); - appendQueryString({ ...appliedFilters, start_date, end_date }, navigate); - dispatch(applyFilters()); - }; - export const setOtherSummaryFilter = () => (dispatch, getState) => { const filteredResults = [...getState().ocp.filteredResults]; - const keyWordArr = ["SUCCESS", "FAILURE"]; + const keyWordArr = ["success", "failure"]; const data = filteredResults.filter( - (item) => !keyWordArr.includes(item.jobStatus) + (item) => !keyWordArr.includes(item.jobStatus?.toLowerCase()) ); dispatch({ type: TYPES.SET_OCP_FILTERED_DATA, payload: data, }); - dispatch(getOCPSummary()); - dispatch(setPageOptions(START_PAGE, DEFAULT_PER_PAGE)); - dispatch(sliceOCPTableRows(0, DEFAULT_PER_PAGE)); + dispatch(tableReCalcValues()); }; export const getOCPSummary = () => (dispatch, getState) => { @@ -249,3 +255,8 @@ export const setTableColumns = (key, isAdding) => (dispatch, getState) => { payload: tableColumns, }); }; +export const tableReCalcValues = () => (dispatch) => { + dispatch(getOCPSummary()); + dispatch(setPageOptions(START_PAGE, DEFAULT_PER_PAGE)); + dispatch(sliceOCPTableRows(0, DEFAULT_PER_PAGE)); +}; diff --git a/frontend/src/actions/types.js b/frontend/src/actions/types.js index 173ac44c..77db108b 100644 --- a/frontend/src/actions/types.js +++ b/frontend/src/actions/types.js @@ -25,7 +25,7 @@ export const SET_APPLIED_FILTERS = "SET_APPLIED_FILTERS"; export const SET_PAGE = "SET_PAGE"; export const SET_PAGE_OPTIONS = "SET_PAGE_OPTIONS"; export const SET_CPT_SUMMARY = "SET_CPT_SUMMARY"; - +export const SET_SELECTED_FILTERS = "SET_SELECTED_FILTERS"; /* OCP Jobs */ export const SET_OCP_JOBS_DATA = "SET_OCP_JOBS_DATA"; export const SET_OCP_DATE_FILTER = "SET_OCP_DATE_FILTER"; @@ -42,3 +42,4 @@ export const SET_OCP_FILTERED_DATA = "SET_OCP_FILTERED_DATA"; export const SET_OCP_APPLIED_FILTERS = "SET_OCP_APPLIED_FILTERS"; export const SET_OCP_GRAPH_DATA = "SET_OCP_GRAPH_DATA"; export const SET_OCP_COLUMNS = "SET_OCP_COLUMNS"; +export const SET_SELECTED_OCP_FILTERS = "SET_SELECTED_OCP_FILTERS"; diff --git a/frontend/src/components/molecules/MultiSelectBox/index.jsx b/frontend/src/components/molecules/MultiSelectBox/index.jsx new file mode 100644 index 00000000..7acbf263 --- /dev/null +++ b/frontend/src/components/molecules/MultiSelectBox/index.jsx @@ -0,0 +1,118 @@ +import { + Chip, + ChipGroup, + MenuToggle, + Select, + SelectList, + SelectOption, + TextInputGroup, + TextInputGroupMain, +} from "@patternfly/react-core"; + +import PropTypes from "prop-types"; +import { useState } from "react"; + +const MultiSelectBox = (props) => { + const [isOpen, setIsOpen] = useState(false); + const [isDirty, setIsDirty] = useState(false); + const onSelect = (value) => { + console.log("selected", value); + setIsDirty(true); + props.onChange(props.currCategory, value); + }; + + const onToggleClick = () => { + setIsOpen(!isOpen); + }; + + const closeMenu = () => { + setIsOpen(false); + if (isDirty) { + props.applyMethod(); + } + setIsDirty(false); + }; + const toggle = (toggleRef) => { + console.log(props.selected); + return ( + + {Array.isArray(props.selected.value) && + props.selected.value.length > 0 ? ( + + + + {props.selected.value.map((selection, index) => ( + { + // ev.stopPropagation(); + // onSelect(selection); + // }} + > + {selection} + + ))} + + + + ) : ( + + + + )} + + ); + }; + const createItemId = (value) => + `select-multi-typeahead-${value.replace(" ", "-")}`; + return ( + <> + + + ); +}; + +MultiSelectBox.propTypes = { + options: PropTypes.array, + onChange: PropTypes.func, + selected: PropTypes.array, + applyMethod: PropTypes.func, + currCategory: PropTypes.string, + width: PropTypes.string, +}; +export default MultiSelectBox; diff --git a/frontend/src/components/molecules/SelectBox/index.jsx b/frontend/src/components/molecules/SelectBox/index.jsx index bff775f8..ac73eed2 100644 --- a/frontend/src/components/molecules/SelectBox/index.jsx +++ b/frontend/src/components/molecules/SelectBox/index.jsx @@ -26,6 +26,7 @@ const SelectBox = (props) => { isExpanded={isOpen} style={{ width: props.width, + height: "36px", }} > {props.selected} diff --git a/frontend/src/components/organisms/TableFilters/index.jsx b/frontend/src/components/organisms/TableFilters/index.jsx index 81160121..ddd44368 100644 --- a/frontend/src/components/organisms/TableFilters/index.jsx +++ b/frontend/src/components/organisms/TableFilters/index.jsx @@ -4,6 +4,7 @@ import "./index.less"; import { Chip, + ChipGroup, Toolbar, ToolbarContent, ToolbarItem, @@ -12,6 +13,7 @@ import { import ColumnMenuFilter from "@/components/molecules/ColumnMenuFilter"; import DatePicker from "react-date-picker"; import { FilterIcon } from "@patternfly/react-icons"; +import MultiSelectBox from "@/components/molecules/MultiSelectBox"; import PropTypes from "prop-types"; import SelectBox from "@/components/molecules/SelectBox"; import { formatDate } from "@/utils/helper"; @@ -33,39 +35,48 @@ const TableFilter = (props) => { type, showColumnMenu, setColumns, + selectedFilters, + updateSelectedFilter, } = props; - const category = filterData.filter( - (item) => item.name === categoryFilterValue - )[0].key; + const category = + filterData?.length > 0 && + filterData.filter((item) => item.name === categoryFilterValue)[0].key; const getFilterName = (key) => { - const filter = tableFilters.find((item) => item.value === key); + const filter = + tableFilters?.length > 0 && + tableFilters?.find((item) => item.value === key); return filter.name; }; return ( <> - - - } - width={"200px"} - /> - - - - - + {tableFilters?.length > 0 && filterOptions?.length > 0 && ( + + + } + width={"200px"} + /> + + + i.name === category)} + width={"300px"} + /> + + + )} + { {Object.keys(appliedFilters).length > 0 && Object.keys(appliedFilters).map((key) => ( - deleteItem(key)}> - {getFilterName(key)} : {appliedFilters[key]} - + + {getFilterName(key)} : + {appliedFilters[key].map((i) => ( + deleteItem(key, i)} key={i}> + {i} + + ))} + ))} ); @@ -120,5 +136,7 @@ TableFilter.propTypes = { type: PropTypes.string, showColumnMenu: PropTypes.bool, setColumns: PropTypes.func, + selectedFilters: PropTypes.array, + updateSelectedFilter: PropTypes.func, }; export default TableFilter; diff --git a/frontend/src/components/templates/Home/index.jsx b/frontend/src/components/templates/Home/index.jsx index 45590a1e..8e6d8254 100644 --- a/frontend/src/components/templates/Home/index.jsx +++ b/frontend/src/components/templates/Home/index.jsx @@ -1,6 +1,5 @@ import { fetchOCPJobsData, - filterFromSummary, removeAppliedFilters, setAppliedFilters, setCPTSortDir, @@ -11,6 +10,8 @@ import { setOtherSummaryFilter, setPage, setPageOptions, + setSelectedFilter, + setSelectedFilterFromUrl, sliceTableRows, } from "@/actions/homeActions.js"; import { useCallback, useEffect } from "react"; @@ -43,6 +44,7 @@ const Home = () => { page, perPage, summary, + selectedFilters, } = useSelector((state) => state.cpt); useEffect(() => { @@ -54,7 +56,12 @@ const Home = () => { searchParams.delete("start_date"); searchParams.delete("end_date"); const params = Object.fromEntries(searchParams); - dispatch(setFilterFromURL(params)); + const obj = {}; + for (const key in params) { + obj[key] = params[key].split(","); + } + dispatch(setFilterFromURL(obj)); + dispatch(setSelectedFilterFromUrl(params)); dispatch(setDateFilter(startDate, endDate, navigate)); } }, []); @@ -95,11 +102,12 @@ const Home = () => { const onCategoryChange = (_event, value) => { dispatch(setCatFilters(value)); }; - const onOptionsChange = (_event, value) => { - dispatch(setAppliedFilters(value, navigate)); + const onOptionsChange = () => { + dispatch(setAppliedFilters(navigate)); }; - const deleteItem = (key) => { - dispatch(removeAppliedFilters(key, navigate)); + const deleteItem = (key, value) => { + dispatch(removeAppliedFilters(key, value, navigate)); + updateSelectedFilter(key, value, false); }; const startDateChangeHandler = (date, key) => { dispatch(setDateFilter(date, key, navigate)); @@ -108,15 +116,27 @@ const Home = () => { dispatch(setDateFilter(key, date, navigate)); }; const removeStatusFilter = () => { - dispatch(removeAppliedFilters("jobStatus", navigate)); + if ( + Array.isArray(appliedFilters["jobStatus"]) && + appliedFilters["jobStatus"].length > 0 + ) { + appliedFilters["jobStatus"].forEach((element) => { + updateSelectedFilter("jobStatus", element, true); + dispatch(removeAppliedFilters("jobStatus", element, navigate)); + }); + } }; const applyStatusFilter = (value) => { - dispatch(filterFromSummary("jobStatus", value, navigate)); + updateSelectedFilter("jobStatus", value, true); + dispatch(setAppliedFilters(navigate)); }; const applyOtherFilter = () => { - dispatch(removeAppliedFilters("jobStatus", navigate)); + removeStatusFilter(); dispatch(setOtherSummaryFilter()); }; + const updateSelectedFilter = (category, value, isFromMetrics) => { + dispatch(setSelectedFilter(category, value, isFromMetrics)); + }; // Filter Helper return ( <> @@ -127,24 +147,25 @@ const Home = () => { applyStatusFilter={applyStatusFilter} applyOtherFilter={applyOtherFilter} /> - {tableFilters?.length > 0 && filterOptions?.length > 0 && ( - - )} + + { start_date, end_date, graphData, + selectedFilters, } = useSelector((state) => state.ocp); const modifidedTableFilters = useMemo( @@ -63,7 +65,12 @@ const OCP = () => { searchParams.delete("start_date"); searchParams.delete("end_date"); const params = Object.fromEntries(searchParams); - dispatch(setFilterFromURL(params)); + const obj = {}; + for (const key in params) { + obj[key] = params[key].split(","); + } + dispatch(setFilterFromURL(obj)); + dispatch(setSelectedFilterFromUrl(params)); dispatch(setDateFilter(startDate, endDate, navigate)); } }, []); @@ -102,26 +109,39 @@ const OCP = () => { // Pagination helper /* Summary Tab Filter*/ const removeStatusFilter = () => { - dispatch(removeAppliedFilters("jobStatus", navigate)); + if ( + Array.isArray(appliedFilters["jobStatus"]) && + appliedFilters["jobStatus"].length > 0 + ) { + appliedFilters["jobStatus"].forEach((element) => { + updateSelectedFilter("jobStatus", element, true); + dispatch(removeAppliedFilters("jobStatus", element, navigate)); + }); + } }; const applyStatusFilter = (value) => { - dispatch(filterFromSummary("jobStatus", value, navigate)); + updateSelectedFilter("jobStatus", value, true); + dispatch(setAppliedFilters(navigate)); }; const applyOtherFilter = () => { - dispatch(removeAppliedFilters("jobStatus", navigate)); + removeStatusFilter(); dispatch(setOtherSummaryFilter()); }; + const updateSelectedFilter = (category, value, isFromMetrics = false) => { + dispatch(setSelectedFilter(category, value, isFromMetrics)); + }; /* Filter helper */ const onCategoryChange = (_event, value) => { dispatch(setOCPCatFilters(value)); }; - const onOptionsChange = (_event, value) => { - dispatch(setAppliedFilters(value, navigate)); + const onOptionsChange = () => { + dispatch(setAppliedFilters(navigate)); }; - const deleteItem = (key) => { - dispatch(removeAppliedFilters(key, navigate)); + const deleteItem = (key, value) => { + dispatch(removeAppliedFilters(key, value, navigate)); + updateSelectedFilter(key, value); }; const startDateChangeHandler = (date, key) => { dispatch(setDateFilter(date, key, navigate)); @@ -160,25 +180,25 @@ const OCP = () => { applyOtherFilter={applyOtherFilter} /> - {tableFilters?.length > 0 && filterOptions?.length > 0 && ( - - )} + { return { ...state, page: payload.page, perPage: payload.perPage }; case TYPES.SET_CPT_SUMMARY: return { ...state, summary: payload }; + case TYPES.SET_SELECTED_FILTERS: + return { ...state, selectedFilters: payload }; default: return state; } diff --git a/frontend/src/reducers/ocpReducer.js b/frontend/src/reducers/ocpReducer.js index bacfddb9..99b4d160 100644 --- a/frontend/src/reducers/ocpReducer.js +++ b/frontend/src/reducers/ocpReducer.js @@ -79,6 +79,27 @@ const initialState = { { name: "Infra", value: "infraNodesCount" }, { name: "Total", value: "totalNodesCount" }, ], + selectedFilters: [ + { name: "ciSystem", value: [] }, + { name: "platform", value: [] }, + { name: "benchmark", value: [] }, + { name: "releaseStream", value: [] }, + { name: "build", value: [] }, + { name: "workerNodesCount", value: [] }, + { name: "networkType", value: [] }, + { name: "shortVersion", value: [] }, + { name: "jobType", value: [] }, + { name: "isRehearse", value: [] }, + { name: "ipsec", value: [] }, + { name: "fips", value: [] }, + { name: "encrypted", value: [] }, + { name: "publish", value: [] }, + { name: "computeArch", value: [] }, + { name: "controlPlaneArch", value: [] }, + { name: "jobStatus", value: [] }, + { name: "startDate", value: [] }, + { name: "endDate", value: [] }, + ], }; const OCPReducer = (state = initialState, action = {}) => { @@ -122,6 +143,8 @@ const OCPReducer = (state = initialState, action = {}) => { return { ...state, graphData: [...state.graphData, payload] }; case TYPES.SET_OCP_COLUMNS: return { ...state, tableColumns: payload }; + case TYPES.SET_SELECTED_OCP_FILTERS: + return { ...state, selectedFilters: payload }; default: return state; }