diff --git a/backend/app/api/v1/commons/constants.py b/backend/app/api/v1/commons/constants.py new file mode 100644 index 00000000..650e9b8c --- /dev/null +++ b/backend/app/api/v1/commons/constants.py @@ -0,0 +1,23 @@ +# Define the keywords for sorting. +DIRECTIONS = ("asc", "desc") +FIELDS = ( + "ciSystem.keyword", + "benchmark.keyword", + "ocpVersion.keyword", + "releaseStream.keyword", + "platform.keyword", + "networkType.keyword", + "ipsec.keyword", + "fips.keyword", + "encrypted.keyword", + "publish.keyword", + "computeArch.keyword", + "controlPlaneArch.keyword", + "jobStatus.keyword", + "startDate", + "endDate", + "workerNodesCount", + "masterNodesCount", + "infraNodesCount", + "totalNodesCount", +) diff --git a/backend/app/api/v1/commons/hce.py b/backend/app/api/v1/commons/hce.py index a9b66e7a..f397ce0a 100644 --- a/backend/app/api/v1/commons/hce.py +++ b/backend/app/api/v1/commons/hce.py @@ -7,7 +7,9 @@ async def getData( start_datetime: date, end_datetime: date, size: int, offset: int, configpath: str ): query = { - "query": {"bool": {"filter": {"range": {"date": {"format": "yyyy-MM-dd"}}}}} + "size": size, + "from": offset, + "query": {"bool": {"filter": {"range": {"date": {"format": "yyyy-MM-dd"}}}}}, } es = ElasticService(configpath=configpath) response = await es.post( @@ -25,5 +27,4 @@ async def getData( jobs[["group"]] = jobs[["group"]].fillna(0) jobs.fillna("", inplace=True) - return {"data": jobs, "total": response["total"]} diff --git a/backend/app/api/v1/commons/ocp.py b/backend/app/api/v1/commons/ocp.py index 1eaa3c54..51551fbd 100644 --- a/backend/app/api/v1/commons/ocp.py +++ b/backend/app/api/v1/commons/ocp.py @@ -5,7 +5,12 @@ async def getData( - start_datetime: date, end_datetime: date, size: int, offset: int, configpath: str + start_datetime: date, + end_datetime: date, + size: int, + offset: int, + sort: str, + configpath: str, ): query = { "size": size, @@ -14,6 +19,8 @@ async def getData( "bool": {"filter": {"range": {"timestamp": {"format": "yyyy-MM-dd"}}}} }, } + if sort: + query["sort"] = utils.build_sort_terms(sort) es = ElasticService(configpath=configpath) response = await es.post( diff --git a/backend/app/api/v1/commons/quay.py b/backend/app/api/v1/commons/quay.py index 71deeb8f..45dad74e 100644 --- a/backend/app/api/v1/commons/quay.py +++ b/backend/app/api/v1/commons/quay.py @@ -5,7 +5,7 @@ async def getData( - start_datetime: date, end_datetime: date, size, offset, configpath: str + start_datetime: date, end_datetime: date, size, offset, sort: str, configpath: str ): query = { "size": size, @@ -15,6 +15,9 @@ async def getData( }, } + if sort: + query["sort"] = utils.build_sort_terms(sort) + es = ElasticService(configpath=configpath) response = await es.post( query=query, diff --git a/backend/app/api/v1/commons/utils.py b/backend/app/api/v1/commons/utils.py index ddcef9d2..61646f5b 100644 --- a/backend/app/api/v1/commons/utils.py +++ b/backend/app/api/v1/commons/utils.py @@ -1,4 +1,7 @@ from app.services.search import ElasticService +from fastapi import HTTPException, status +import re +import app.api.v1.commons.constants as constants async def getMetadata(uuid: str, configpath: str): @@ -65,3 +68,31 @@ def getReleaseStream(row): elif row["releaseStream"].__contains__("ec"): return "Engineering Candidate" return "Stable" + + +def build_sort_terms(sort_string: str) -> list[dict[str, str]]: + """ + + Validates and transforms a sort string in the format 'sort=key:direction' to + a list of dictionaries [{key: {"order": direction}}]. + + :param sort_string: str, input string in the format 'sort=key:direction' + + :return: list, transformed sort structure or raises a ValueError for invalid input + + """ + sort_terms = [] + if sort_string: + key, dir = sort_string.split(":", maxsplit=1) + if dir not in constants.DIRECTIONS: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + f"Sort direction {dir!r} must be one of {','.join(constants.DIRECTIONS)}", + ) + if key not in constants.FIELDS: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + f"Sort key {key!r} must be one of {','.join(constants.FIELDS)}", + ) + sort_terms.append({f"{key}": {"order": dir}}) + return sort_terms diff --git a/backend/app/api/v1/endpoints/cpt/maps/hce.py b/backend/app/api/v1/endpoints/cpt/maps/hce.py index 3c812d0d..e7439b9a 100644 --- a/backend/app/api/v1/endpoints/cpt/maps/hce.py +++ b/backend/app/api/v1/endpoints/cpt/maps/hce.py @@ -2,6 +2,7 @@ from datetime import date import pandas as pd + ################################################################ # This will return a Dictionary from HCE required by the CPT # endpoint, it contians totalJobs and a Dataframe with the following columns: @@ -16,8 +17,6 @@ # "version" # "testName" ################################################################ - - async def hceMapper(start_datetime: date, end_datetime: date, size: int, offset: int): response = await getData( start_datetime, end_datetime, size, offset, f"hce.elasticsearch" diff --git a/backend/app/api/v1/endpoints/ocp/ocpJobs.py b/backend/app/api/v1/endpoints/ocp/ocpJobs.py index 5637b29d..07b689a5 100644 --- a/backend/app/api/v1/endpoints/ocp/ocpJobs.py +++ b/backend/app/api/v1/endpoints/ocp/ocpJobs.py @@ -35,6 +35,7 @@ async def jobs( pretty: bool = Query(False, description="Output content in pretty format."), size: int = Query(None, description="Number of jobs to fetch"), offset: int = Query(None, description="Offset Number to fetch jobs from"), + sort: str = Query(None, description="To sort fields on specified direction"), ): if start_date is None: start_date = datetime.utcnow().date() @@ -59,7 +60,9 @@ async def jobs( elif not offset: offset = 0 - results = await getData(start_date, end_date, size, offset, "ocp.elasticsearch") + results = await getData( + start_date, end_date, size, offset, sort, "ocp.elasticsearch" + ) jobs = [] if "data" in results and len(results["data"]) >= 1: jobs = results["data"].to_dict("records") diff --git a/backend/app/api/v1/endpoints/quay/quayJobs.py b/backend/app/api/v1/endpoints/quay/quayJobs.py index be05d074..0a86db56 100644 --- a/backend/app/api/v1/endpoints/quay/quayJobs.py +++ b/backend/app/api/v1/endpoints/quay/quayJobs.py @@ -35,6 +35,7 @@ async def jobs( pretty: bool = Query(False, description="Output content in pretty format."), size: int = Query(None, description="Number of jobs to fetch"), offset: int = Query(None, description="Offset Number to fetch jobs from"), + sort: str = Query(None, description="To sort fields on specified direction"), ): if start_date is None: start_date = datetime.utcnow().date() @@ -59,7 +60,9 @@ async def jobs( elif not offset: offset = 0 - results = await getData(start_date, end_date, size, offset, "quay.elasticsearch") + results = await getData( + start_date, end_date, size, offset, sort, "quay.elasticsearch" + ) jobs = [] if "data" in results and len(results["data"]) >= 1: diff --git a/backend/app/services/search.py b/backend/app/services/search.py index 9f1837fb..f9c9e53d 100644 --- a/backend/app/services/search.py +++ b/backend/app/services/search.py @@ -47,7 +47,7 @@ async def post( self, query, indice=None, - size=10000, + size=None, start_date=None, end_date=None, timestamp_field=None, diff --git a/frontend/src/actions/commonActions.js b/frontend/src/actions/commonActions.js index 0551bd0f..0eec4c94 100644 --- a/frontend/src/actions/commonActions.js +++ b/frontend/src/actions/commonActions.js @@ -6,69 +6,6 @@ import { setOCPCatFilters } from "./ocpActions"; import { setQuayCatFilters } from "./quayActions"; import { setTelcoCatFilters } from "./telcoActions"; -const getSortableRowValues = (result, tableColumns) => { - const tableKeys = tableColumns.map((item) => item.value); - return tableKeys.map((key) => result[key]); -}; - -export const sortTable = (currState) => (dispatch, getState) => { - const results = [...getState()[currState].filteredResults]; - const { activeSortDir, activeSortIndex, tableColumns } = - getState()[currState]; - try { - if (activeSortIndex !== null && typeof activeSortIndex !== "undefined") { - const sortedResults = results.sort((a, b) => { - const aValue = getSortableRowValues(a, tableColumns)[activeSortIndex]; - const bValue = getSortableRowValues(b, tableColumns)[activeSortIndex]; - if (typeof aValue === "number") { - if (activeSortDir === "asc") { - return aValue - bValue; - } - return bValue - aValue; - } else { - if (activeSortDir === "asc") { - return aValue.localeCompare(bValue); - } - return bValue.localeCompare(aValue); - } - }); - dispatch(sortedTableRows(currState, sortedResults)); - } - } catch (error) { - console.log(error); - } -}; - -const sortedTableRows = (currState, sortedResults) => (dispatch) => { - if (currState === "cpt") { - dispatch({ - type: TYPES.SET_FILTERED_DATA, - payload: sortedResults, - }); - return; - } - if (currState === "ocp") { - dispatch({ - type: TYPES.SET_OCP_FILTERED_DATA, - payload: sortedResults, - }); - return; - } - if (currState === "quay") { - dispatch({ - type: TYPES.SET_QUAY_FILTERED_DATA, - payload: sortedResults, - }); - return; - } - if (currState === "telco") { - dispatch({ - type: TYPES.SET_TELCO_FILTERED_DATA, - payload: sortedResults, - }); - } -}; - const findItemCount = (data, key, value) => { return data.reduce(function (n, item) { return n + (item[key].toLowerCase() === value); @@ -203,13 +140,14 @@ export const getSelectedFilter = }; export const getRequestParams = (type) => (dispatch, getState) => { - const { start_date, end_date, size, offset } = getState()[type]; + const { start_date, end_date, size, offset, sort } = getState()[type]; const params = { pretty: true, ...(start_date && { start_date }), ...(end_date && { end_date }), size: size, offset: offset, + ...(sort && { sort }), }; return params; diff --git a/frontend/src/actions/homeActions.js b/frontend/src/actions/homeActions.js index 7a6dd735..fda6284b 100644 --- a/frontend/src/actions/homeActions.js +++ b/frontend/src/actions/homeActions.js @@ -9,7 +9,6 @@ import { getFilteredData, getRequestParams, getSelectedFilter, - sortTable, } from "./commonActions"; import API from "@/utils/axiosInstance"; @@ -56,7 +55,6 @@ export const fetchOCPJobsData = }); dispatch(applyFilters()); - dispatch(sortTable("cpt")); dispatch(tableReCalcValues()); } } catch (error) { diff --git a/frontend/src/actions/ocpActions.js b/frontend/src/actions/ocpActions.js index 94657b15..c7634c84 100644 --- a/frontend/src/actions/ocpActions.js +++ b/frontend/src/actions/ocpActions.js @@ -9,7 +9,6 @@ import { getFilteredData, getRequestParams, getSelectedFilter, - sortTable, } from "./commonActions"; import API from "@/utils/axiosInstance"; @@ -52,7 +51,6 @@ export const fetchOCPJobs = () => async (dispatch) => { }); dispatch(applyFilters()); - dispatch(sortTable("ocp")); dispatch(tableReCalcValues()); } } catch (error) { diff --git a/frontend/src/actions/sortingActions.js b/frontend/src/actions/sortingActions.js index 742820b5..15e72cb8 100644 --- a/frontend/src/actions/sortingActions.js +++ b/frontend/src/actions/sortingActions.js @@ -1,9 +1,18 @@ +import * as TYPES from "@/actions/types.js"; + +import { fetchOCPJobs, setOCPSortDir, setOCPSortIndex } from "./ocpActions"; +import { + fetchQuayJobsData, + setQuaySortDir, + setQuaySortIndex, +} from "./quayActions"; +import { + fetchTelcoJobsData, + setTelcoSortDir, + setTelcoSortIndex, +} from "./telcoActions"; import { setCPTSortDir, setCPTSortIndex } from "./homeActions"; -import { setOCPSortDir, setOCPSortIndex } from "./ocpActions"; -import { setQuaySortDir, setQuaySortIndex } from "./quayActions"; -import { setTelcoSortDir, setTelcoSortIndex } from "./telcoActions"; -import { sortTable } from "./commonActions"; import store from "@/store/store"; const { dispatch } = store; @@ -29,6 +38,56 @@ export const setActiveSortIndex = (index, currType) => { dispatch(setTelcoSortIndex(index)); } }; -export const handleOnSort = (currType) => { - dispatch(sortTable(currType)); +export const handleOnSort = (colName, currType) => { + dispatch(sortTable(colName, currType)); +}; + +const offsetActions = { + cpt: TYPES.SET_CPT_OFFSET, + ocp: TYPES.SET_OCP_OFFSET, + quay: TYPES.SET_QUAY_OFFSET, + telco: TYPES.SET_TELCO_OFFSET, +}; +const fetchJobsMap = { + ocp: fetchOCPJobs, + quay: fetchQuayJobsData, + telco: fetchTelcoJobsData, +}; +const sortObjActions = { + ocp: TYPES.SET_OCP_SORT_OBJ, + quay: TYPES.SET_QUAY_SORT_OBJ, +}; +export const sortTable = (colName, currState) => (dispatch, getState) => { + const { activeSortDir, activeSortIndex } = getState()[currState]; + const countObj = [ + "masterNodesCount", + "workerNodesCount", + "infraNodesCount", + "totalNodesCount", + "startDate", + "endDate", + ]; + try { + if ( + typeof activeSortDir !== "undefined" && + typeof activeSortIndex !== "undefined" + ) { + dispatch({ type: offsetActions[currState], payload: 0 }); + let fieldName = countObj.includes(colName) + ? colName + : `${colName}.keyword`; + if (colName === "build") { + fieldName = "ocpVersion.keyword"; + } + + const sortParam = `${fieldName}:${activeSortDir}`; + dispatch({ type: sortObjActions[currState], payload: sortParam }); + console.log(sortParam); + const isFromSorting = true; + + dispatch(fetchJobsMap[currState](isFromSorting)); + } + } catch (error) { + console.log(error); + } }; diff --git a/frontend/src/actions/types.js b/frontend/src/actions/types.js index 734d6568..2677f164 100644 --- a/frontend/src/actions/types.js +++ b/frontend/src/actions/types.js @@ -33,6 +33,7 @@ export const SET_OCP_JOBS_DATA = "SET_OCP_JOBS_DATA"; export const SET_OCP_DATE_FILTER = "SET_OCP_DATE_FILTER"; export const SET_OCP_SORT_INDEX = "SET_OCP_SORT_INDEX"; export const SET_OCP_SORT_DIR = "SET_OCP_SORT_DIR"; +export const SET_OCP_SORT_OBJ = "SET_OCP_SORT_OBJ"; export const SET_OCP_PAGE = "SET_OCP_PAGE"; export const SET_OCP_PAGE_OPTIONS = "SET_OCP_PAGE_OPTIONS"; export const SET_OCP_INIT_JOBS = "SET_OCP_INIT_JOBS"; @@ -52,6 +53,7 @@ export const SET_QUAY_JOBS_DATA = "SET_QUAY_JOBS_DATA"; export const SET_QUAY_DATE_FILTER = "SET_QUAY_DATE_FILTER"; export const SET_QUAY_SORT_INDEX = "SET_QUAY_SORT_INDEX"; export const SET_QUAY_SORT_DIR = "SET_QUAY_SORT_DIR"; +export const SET_QUAY_SORT_OBJ = "SET_QUAY_SORT_OBJ"; export const SET_QUAY_PAGE = "SET_QUAY_PAGE"; export const SET_QUAY_PAGE_OPTIONS = "SET_QUAY_PAGE_OPTIONS"; export const SET_QUAY_INIT_JOBS = "SET_QUAY_INIT_JOBS"; diff --git a/frontend/src/components/organisms/TableLayout/index.jsx b/frontend/src/components/organisms/TableLayout/index.jsx index 500c1bc9..e24def32 100644 --- a/frontend/src/components/organisms/TableLayout/index.jsx +++ b/frontend/src/components/organisms/TableLayout/index.jsx @@ -21,9 +21,10 @@ const TableLayout = (props) => { totalItems, addExpansion, type, + shouldSort, } = props; - const getSortParams = (columnIndex) => ({ + const getSortParams = (columnIndex, colName) => ({ sortBy: { index: activeSortIndex, direction: activeSortDir, @@ -32,7 +33,7 @@ const TableLayout = (props) => { onSort: (_event, index, direction) => { setActiveSortIndex(index, type); setActiveSortDir(direction, type); - handleOnSort(type); + handleOnSort(colName, type); }, columnIndex, }); @@ -46,7 +47,10 @@ const TableLayout = (props) => { {tableColumns?.length > 0 && tableColumns.map((col, idx) => ( -