diff --git a/frontend/src/actions/commonActions.js b/frontend/src/actions/commonActions.js index 81546e7b..1b2af030 100644 --- a/frontend/src/actions/commonActions.js +++ b/frontend/src/actions/commonActions.js @@ -1,7 +1,9 @@ import * as TYPES from "@/actions/types.js"; -import { setCatFilters, sliceTableRows } from "./homeActions"; +import { setCPTCatFilters, sliceCPTTableRows } from "./homeActions"; import { setOCPCatFilters, sliceOCPTableRows } from "./ocpActions"; +import { setQuayCatFilters, sliceQuayTableRows } from "./quayActions"; +import { setTelcoCatFilters, sliceTelcoTableRows } from "./telcoActions"; import { DEFAULT_PER_PAGE } from "@/assets/constants/paginationConstants"; import { cloneDeep } from "lodash"; @@ -45,7 +47,7 @@ const sortedTableRows = (currState, sortedResults) => (dispatch) => { type: TYPES.SET_FILTERED_DATA, payload: sortedResults, }); - dispatch(sliceTableRows(0, DEFAULT_PER_PAGE)); + dispatch(sliceCPTTableRows(0, DEFAULT_PER_PAGE)); return; } if (currState === "ocp") { @@ -54,6 +56,22 @@ const sortedTableRows = (currState, sortedResults) => (dispatch) => { payload: sortedResults, }); dispatch(sliceOCPTableRows(0, DEFAULT_PER_PAGE)); + return; + } + if (currState === "quay") { + dispatch({ + type: TYPES.SET_QUAY_FILTERED_DATA, + payload: sortedResults, + }); + dispatch(sliceQuayTableRows(0, DEFAULT_PER_PAGE)); + return; + } + if (currState === "telco") { + dispatch({ + type: TYPES.SET_TELCO_FILTERED_DATA, + payload: sortedResults, + }); + dispatch(sliceTelcoTableRows(0, DEFAULT_PER_PAGE)); } }; @@ -114,7 +132,19 @@ const setFilterData = (filterData, currState, activeFilter) => (dispatch) => { type: TYPES.SET_CPT_FILTER_DATA, payload: filterData, }); - dispatch(setCatFilters(activeFilter)); + dispatch(setCPTCatFilters(activeFilter)); + } else if (currState === "quay") { + dispatch({ + type: TYPES.SET_QUAY_FILTER_DATA, + payload: filterData, + }); + dispatch(setQuayCatFilters(activeFilter)); + } else if (currState === "telco") { + dispatch({ + type: TYPES.SET_TELCO_FILTER_DATA, + payload: filterData, + }); + dispatch(setTelcoCatFilters(activeFilter)); } }; diff --git a/frontend/src/actions/filterActions.js b/frontend/src/actions/filterActions.js new file mode 100644 index 00000000..7f565887 --- /dev/null +++ b/frontend/src/actions/filterActions.js @@ -0,0 +1,92 @@ +import { + removeCPTAppliedFilters, + setCPTAppliedFilters, + setCPTCatFilters, + setCPTDateFilter, + setCPTOtherSummaryFilter, +} from "./homeActions"; +import { + removeOCPAppliedFilters, + setOCPAppliedFilters, + setOCPCatFilters, + setOCPDateFilter, + setOCPOtherSummaryFilter, +} from "./ocpActions"; +import { + removeQuayAppliedFilters, + setQuayAppliedFilters, + setQuayCatFilters, + setQuayDateFilter, + setQuayOtherSummaryFilter, +} from "./quayActions"; +import { + removeTelcoAppliedFilters, + setTelcoAppliedFilters, + setTelcoCatFilters, + setTelcoDateFilter, + setTelcoOtherSummaryFilter, +} from "./telcoActions"; + +import store from "@/store/store"; + +const { dispatch } = store; + +export const setCatFilters = (category, currType) => { + if (currType === "cpt") { + dispatch(setCPTCatFilters(category)); + } else if (currType === "ocp") { + dispatch(setOCPCatFilters(category)); + } else if (currType === "quay") { + dispatch(setQuayCatFilters(category)); + } else if (currType === "telco") { + dispatch(setTelcoCatFilters(category)); + } +}; + +export const setAppliedFilters = (navigation, currType) => { + if (currType === "cpt") { + dispatch(setCPTAppliedFilters(navigation)); + } else if (currType === "ocp") { + dispatch(setOCPAppliedFilters(navigation)); + } else if (currType === "quay") { + dispatch(setQuayAppliedFilters(navigation)); + } else if (currType === "telco") { + dispatch(setTelcoAppliedFilters(navigation)); + } +}; + +export const removeAppliedFilters = (key, value, navigation, currType) => { + if (currType === "cpt") { + dispatch(removeCPTAppliedFilters(key, value, navigation)); + } else if (currType === "ocp") { + dispatch(removeOCPAppliedFilters(key, value, navigation)); + } else if (currType === "quay") { + dispatch(removeQuayAppliedFilters(key, value, navigation)); + } else if (currType === "telco") { + dispatch(removeTelcoAppliedFilters(key, value, navigation)); + } +}; + +export const setDateFilter = (date, key, navigation, currType) => { + if (currType === "cpt") { + dispatch(setCPTDateFilter(date, key, navigation)); + } else if (currType === "ocp") { + dispatch(setOCPDateFilter(date, key, navigation)); + } else if (currType === "quay") { + dispatch(setQuayDateFilter(date, key, navigation)); + } else if (currType === "telco") { + dispatch(setTelcoDateFilter(date, key, navigation)); + } +}; + +export const setOtherSummaryFilter = (currType) => { + if (currType === "cpt") { + dispatch(setCPTOtherSummaryFilter()); + } else if (currType === "ocp") { + dispatch(setOCPOtherSummaryFilter()); + } else if (currType === "quay") { + dispatch(setQuayOtherSummaryFilter()); + } else if (currType === "telco") { + dispatch(setTelcoOtherSummaryFilter()); + } +}; diff --git a/frontend/src/actions/homeActions.js b/frontend/src/actions/homeActions.js index 84296a98..68109d60 100644 --- a/frontend/src/actions/homeActions.js +++ b/frontend/src/actions/homeActions.js @@ -30,16 +30,11 @@ export const fetchOCPJobsData = () => async (dispatch, getState) => { ...(end_date && { end_date }), }, }); - if (response?.data?.results?.length > 0) { + if (response.status === 200) { const startDate = response.data.startDate, endDate = response.data.endDate; //on initial load startDate and endDate are empty, so from response append to url appendDateFilter(startDate, endDate); - - dispatch({ - type: TYPES.SET_CPT_JOBS_DATA, - payload: response.data.results, - }); dispatch({ type: TYPES.SET_CPT_DATE_FILTER, payload: { @@ -47,6 +42,14 @@ export const fetchOCPJobsData = () => async (dispatch, getState) => { end_date: endDate, }, }); + } + + if (response?.data?.results?.length > 0) { + dispatch({ + type: TYPES.SET_CPT_JOBS_DATA, + payload: response.data.results, + }); + dispatch(applyFilters()); dispatch(sortTable("cpt")); dispatch(tableReCalcValues()); @@ -67,7 +70,7 @@ export const setCPTSortDir = (direction) => ({ payload: direction, }); -export const sliceTableRows = (startIdx, endIdx) => (dispatch, getState) => { +export const sliceCPTTableRows = (startIdx, endIdx) => (dispatch, getState) => { const results = [...getState().cpt.filteredResults]; dispatch({ @@ -76,7 +79,7 @@ export const sliceTableRows = (startIdx, endIdx) => (dispatch, getState) => { }); }; -export const setCatFilters = (category) => (dispatch, getState) => { +export const setCPTCatFilters = (category) => (dispatch, getState) => { const filterData = [...getState().cpt.filterData]; const options = filterData.filter((item) => item.name === category)[0].value; const list = options.map((item) => ({ name: item, value: item })); @@ -112,7 +115,7 @@ export const setSelectedFilter = }); }; -export const setAppliedFilters = (navigate) => (dispatch, getState) => { +export const setCPTAppliedFilters = (navigate) => (dispatch, getState) => { const { selectedFilters, start_date, end_date } = getState().cpt; const appliedFilterArr = selectedFilters.filter((i) => i.value.length > 0); @@ -130,7 +133,7 @@ export const setAppliedFilters = (navigate) => (dispatch, getState) => { dispatch(applyFilters()); }; -export const setOtherSummaryFilter = () => (dispatch, getState) => { +export const setCPTOtherSummaryFilter = () => (dispatch, getState) => { const filteredResults = [...getState().cpt.filteredResults]; const keyWordArr = ["success", "failure"]; const data = filteredResults.filter( @@ -142,7 +145,7 @@ export const setOtherSummaryFilter = () => (dispatch, getState) => { }); dispatch(tableReCalcValues()); }; -export const removeAppliedFilters = +export const removeCPTAppliedFilters = (filterKey, filterValue, navigate) => (dispatch, getState) => { const { start_date, end_date } = getState().cpt; @@ -184,7 +187,7 @@ export const setFilterFromURL = (searchParams) => ({ payload: searchParams, }); -export const setDateFilter = +export const setCPTDateFilter = (start_date, end_date, navigate) => (dispatch, getState) => { const appliedFilters = getState().cpt.appliedFilters; @@ -201,12 +204,12 @@ export const setDateFilter = dispatch(fetchOCPJobsData()); }; -export const setPage = (pageNo) => ({ +export const setCPTPage = (pageNo) => ({ type: TYPES.SET_PAGE, payload: pageNo, }); -export const setPageOptions = (page, perPage) => ({ +export const setCPTPageOptions = (page, perPage) => ({ type: TYPES.SET_PAGE_OPTIONS, payload: { page, perPage }, }); @@ -223,6 +226,6 @@ export const getCPTSummary = () => (dispatch, getState) => { export const tableReCalcValues = () => (dispatch) => { dispatch(getCPTSummary()); - dispatch(setPageOptions(START_PAGE, DEFAULT_PER_PAGE)); - dispatch(sliceTableRows(0, DEFAULT_PER_PAGE)); + dispatch(setCPTPageOptions(START_PAGE, DEFAULT_PER_PAGE)); + dispatch(sliceCPTTableRows(0, DEFAULT_PER_PAGE)); }; diff --git a/frontend/src/actions/ocpActions.js b/frontend/src/actions/ocpActions.js index aae00a40..9a2f14c4 100644 --- a/frontend/src/actions/ocpActions.js +++ b/frontend/src/actions/ocpActions.js @@ -30,15 +30,11 @@ export const fetchOCPJobs = () => async (dispatch, getState) => { ...(end_date && { end_date }), }, }); - if (response?.data?.results?.length > 0) { + if (response.status === 200) { const startDate = response.data.startDate, endDate = response.data.endDate; //on initial load startDate and endDate are empty, so from response append to url appendDateFilter(startDate, endDate); - dispatch({ - type: TYPES.SET_OCP_JOBS_DATA, - payload: response.data.results, - }); dispatch({ type: TYPES.SET_OCP_DATE_FILTER, payload: { @@ -46,6 +42,13 @@ export const fetchOCPJobs = () => async (dispatch, getState) => { end_date: endDate, }, }); + } + if (response?.data?.results?.length > 0) { + dispatch({ + type: TYPES.SET_OCP_JOBS_DATA, + payload: response.data.results, + }); + dispatch(applyFilters()); dispatch(sortTable("ocp")); dispatch(tableReCalcValues()); @@ -56,12 +59,12 @@ export const fetchOCPJobs = () => async (dispatch, getState) => { dispatch({ type: TYPES.COMPLETED }); }; -export const setPage = (pageNo) => ({ +export const setOCPPage = (pageNo) => ({ type: TYPES.SET_OCP_PAGE, payload: pageNo, }); -export const setPageOptions = (page, perPage) => ({ +export const setOCPPageOptions = (page, perPage) => ({ type: TYPES.SET_OCP_PAGE_OPTIONS, payload: { page, perPage }, }); @@ -141,7 +144,7 @@ export const setSelectedFilter = payload: selectedFilters, }); }; -export const setAppliedFilters = (navigate) => (dispatch, getState) => { +export const setOCPAppliedFilters = (navigate) => (dispatch, getState) => { const { start_date, end_date, selectedFilters } = getState().ocp; const appliedFilterArr = selectedFilters.filter((i) => i.value.length > 0); @@ -158,7 +161,7 @@ export const setAppliedFilters = (navigate) => (dispatch, getState) => { dispatch(applyFilters()); }; -export const removeAppliedFilters = +export const removeOCPAppliedFilters = (filterKey, filterValue, navigate) => (dispatch, getState) => { const appliedFilters = dispatch( deleteAppliedFilters(filterKey, filterValue, "ocp") @@ -172,7 +175,7 @@ export const removeAppliedFilters = dispatch(applyFilters()); }; -export const setDateFilter = +export const setOCPDateFilter = (start_date, end_date, navigate) => (dispatch, getState) => { const appliedFilters = getState().ocp.appliedFilters; @@ -194,7 +197,7 @@ export const setFilterFromURL = (searchParams) => ({ payload: searchParams, }); -export const setOtherSummaryFilter = () => (dispatch, getState) => { +export const setOCPOtherSummaryFilter = () => (dispatch, getState) => { const filteredResults = [...getState().ocp.filteredResults]; const keyWordArr = ["success", "failure"]; const data = filteredResults.filter( @@ -217,27 +220,30 @@ export const getOCPSummary = () => (dispatch, getState) => { }); }; -export const fetchGraphData = (uuid) => async (dispatch, getState) => { - try { - dispatch({ type: TYPES.GRAPH_LOADING }); - - const graphData = getState().ocp.graphData; - const hasData = graphData.filter((a) => a.uuid === uuid).length > 0; - if (!hasData) { - const response = await API.get(`${API_ROUTES.OCP_GRAPH_API_V1}/${uuid}`); - - if (response.status === 200) { - dispatch({ - type: TYPES.SET_OCP_GRAPH_DATA, - payload: { uuid, data: response.data }, - }); +export const fetchGraphData = + (uuid, nodeName) => async (dispatch, getState) => { + try { + dispatch({ type: TYPES.GRAPH_LOADING }); + + const graphData = getState().ocp.graphData; + const hasData = graphData.filter((a) => a.uuid === uuid).length > 0; + if (!hasData) { + const response = await API.get( + `${API_ROUTES.OCP_GRAPH_API_V1}/${uuid}` + ); + + if (response.status === 200) { + dispatch({ + type: TYPES.SET_OCP_GRAPH_DATA, + payload: { uuid, data: [[nodeName, response.data]] }, + }); + } } + } catch (error) { + dispatch(showFailureToast()); } - } catch (error) { - dispatch(showFailureToast()); - } - dispatch({ type: TYPES.GRAPH_COMPLETED }); -}; + dispatch({ type: TYPES.GRAPH_COMPLETED }); + }; export const setTableColumns = (key, isAdding) => (dispatch, getState) => { let tableColumns = [...getState().ocp.tableColumns]; @@ -257,6 +263,6 @@ export const setTableColumns = (key, isAdding) => (dispatch, getState) => { }; export const tableReCalcValues = () => (dispatch) => { dispatch(getOCPSummary()); - dispatch(setPageOptions(START_PAGE, DEFAULT_PER_PAGE)); + dispatch(setOCPPageOptions(START_PAGE, DEFAULT_PER_PAGE)); dispatch(sliceOCPTableRows(0, DEFAULT_PER_PAGE)); }; diff --git a/frontend/src/actions/paginationActions.js b/frontend/src/actions/paginationActions.js new file mode 100644 index 00000000..80a7dff1 --- /dev/null +++ b/frontend/src/actions/paginationActions.js @@ -0,0 +1,39 @@ +import { + setCPTPage, + setCPTPageOptions, + sliceCPTTableRows, +} from "./homeActions"; +import { setOCPPage, setOCPPageOptions, sliceOCPTableRows } from "./ocpActions"; +import { setQuayPage, setQuayPageOptions } from "./quayActions"; +import { setTelcoPage, setTelcoPageOptions } from "./telcoActions"; +export const setPage = (newPage, currType) => (dispatch) => { + if (currType === "cpt") { + dispatch(setCPTPage(newPage)); + } else if (currType === "ocp") { + dispatch(setOCPPage(newPage)); + } else if (currType === "quay") { + dispatch(setQuayPage(newPage)); + } else if (currType === "telco") { + dispatch(setTelcoPage(newPage)); + } +}; + +export const setPageOptions = (newPage, newPerPage, currType) => (dispatch) => { + if (currType === "cpt") { + dispatch(setCPTPageOptions(newPage, newPerPage)); + } else if (currType === "ocp") { + dispatch(setOCPPageOptions(newPage, newPerPage)); + } else if (currType === "quay") { + dispatch(setQuayPageOptions(newPage, newPerPage)); + } else if (currType === "telco") { + dispatch(setTelcoPageOptions(newPage, newPerPage)); + } +}; + +export const sliceTableRows = (startIdx, endIdx, currType) => (dispatch) => { + if (currType === "cpt") { + dispatch(sliceCPTTableRows(startIdx, endIdx)); + } else if (currType === "ocp") { + dispatch(sliceOCPTableRows(startIdx, endIdx)); + } +}; diff --git a/frontend/src/actions/quayActions.js b/frontend/src/actions/quayActions.js new file mode 100644 index 00000000..59795ed7 --- /dev/null +++ b/frontend/src/actions/quayActions.js @@ -0,0 +1,272 @@ +import * as API_ROUTES from "@/utils/apiConstants"; +import * as TYPES from "@/actions/types.js"; + +import { + DEFAULT_PER_PAGE, + START_PAGE, +} from "@/assets/constants/paginationConstants"; +import { appendDateFilter, appendQueryString } from "@/utils/helper.js"; +import { + buildFilterData, + calculateMetrics, + deleteAppliedFilters, + getFilteredData, + getSelectedFilter, +} from "./commonActions"; + +import API from "@/utils/axiosInstance"; +import { cloneDeep } from "lodash"; +import { showFailureToast } from "@/actions/toastActions"; + +export const fetchQuayJobsData = () => async (dispatch, getState) => { + try { + dispatch({ type: TYPES.LOADING }); + const { start_date, end_date } = getState().quay; + const response = await API.get(API_ROUTES.QUAY_JOBS_API_V1, { + params: { + pretty: true, + ...(start_date && { start_date }), + ...(end_date && { end_date }), + }, + }); + if (response.status === 200) { + const startDate = response.data.startDate, + endDate = response.data.endDate; + //on initial load startDate and endDate are empty, so from response append to url + appendDateFilter(startDate, endDate); + dispatch({ + type: TYPES.SET_QUAY_DATE_FILTER, + payload: { + start_date: startDate, + end_date: endDate, + }, + }); + } + if (response?.data?.results?.length > 0) { + dispatch({ + type: TYPES.SET_QUAY_JOBS_DATA, + payload: response.data.results, + }); + dispatch({ + type: TYPES.SET_QUAY_FILTERED_DATA, + payload: response.data.results, + }); + dispatch(applyFilters()); + dispatch(tableReCalcValues()); + } + } catch (error) { + dispatch(showFailureToast()); + } + dispatch({ type: TYPES.COMPLETED }); +}; + +export const setQuayPage = (pageNo) => ({ + type: TYPES.SET_QUAY_PAGE, + payload: pageNo, +}); + +export const setQuayPageOptions = (page, perPage) => ({ + type: TYPES.SET_QUAY_PAGE_OPTIONS, + payload: { page, perPage }, +}); +export const setQuaySortIndex = (index) => ({ + type: TYPES.SET_QUAY_SORT_INDEX, + payload: index, +}); + +export const setQuaySortDir = (direction) => ({ + type: TYPES.SET_QUAY_SORT_DIR, + payload: direction, +}); +export const sliceQuayTableRows = + (startIdx, endIdx) => (dispatch, getState) => { + const results = [...getState().quay.filteredResults]; + + dispatch({ + type: TYPES.SET_QUAY_INIT_JOBS, + payload: results.slice(startIdx, endIdx), + }); + }; + +export const setQuayCatFilters = (category) => (dispatch, getState) => { + const filterData = [...getState().quay.filterData]; + const options = filterData.filter((item) => item.name === category)[0].value; + const list = options.map((item) => ({ name: item, value: item })); + + dispatch({ + type: TYPES.SET_QUAY_CATEGORY_FILTER, + payload: category, + }); + dispatch({ + type: TYPES.SET_QUAY_FILTER_OPTIONS, + payload: list, + }); +}; +export const removeQuayAppliedFilters = + (filterKey, filterValue, navigate) => (dispatch, getState) => { + const { start_date, end_date } = getState().quay; + + const appliedFilters = dispatch( + deleteAppliedFilters(filterKey, filterValue, "quay") + ); + + dispatch({ + type: TYPES.SET_QUAY_APPLIED_FILTERS, + payload: appliedFilters, + }); + appendQueryString({ ...appliedFilters, start_date, end_date }, navigate); + dispatch(applyFilters()); + }; + +export const applyFilters = () => (dispatch, getState) => { + const { appliedFilters } = getState().quay; + + const results = [...getState().quay.results]; + + const isFilterApplied = + Object.keys(appliedFilters).length > 0 && + Object.values(appliedFilters).flat().length > 0; + + const filtered = isFilterApplied + ? getFilteredData(appliedFilters, results) + : results; + + dispatch({ + type: TYPES.SET_QUAY_FILTERED_DATA, + payload: filtered, + }); + dispatch(tableReCalcValues()); + dispatch(buildFilterData("quay")); +}; +export const setQuayAppliedFilters = (navigate) => (dispatch, getState) => { + const { selectedFilters, start_date, end_date } = getState().quay; + + const appliedFilterArr = selectedFilters.filter((i) => i.value.length > 0); + + const appliedFilters = {}; + appliedFilterArr.forEach((item) => { + appliedFilters[item["name"]] = item.value; + }); + + dispatch({ + type: TYPES.SET_QUAY_APPLIED_FILTERS, + payload: appliedFilters, + }); + appendQueryString({ ...appliedFilters, start_date, end_date }, navigate); + dispatch(applyFilters()); +}; + +export const setSelectedFilterFromUrl = (params) => (dispatch, getState) => { + const selectedFilters = cloneDeep(getState().quay.selectedFilters); + for (const key in params) { + selectedFilters.find((i) => i.name === key).value = params[key].split(","); + } + dispatch({ + type: TYPES.SET_QUAY_SELECTED_FILTERS, + payload: selectedFilters, + }); +}; + +export const setFilterFromURL = (searchParams) => ({ + type: TYPES.SET_QUAY_APPLIED_FILTERS, + payload: searchParams, +}); + +export const setSelectedFilter = + (selectedCategory, selectedOption, isFromMetrics) => (dispatch) => { + const selectedFilters = dispatch( + getSelectedFilter(selectedCategory, selectedOption, "quay", isFromMetrics) + ); + dispatch({ + type: TYPES.SET_QUAY_SELECTED_FILTERS, + payload: selectedFilters, + }); + }; +export const setQuayDateFilter = + (start_date, end_date, navigate) => (dispatch, getState) => { + const appliedFilters = getState().quay.appliedFilters; + + dispatch({ + type: TYPES.SET_QUAY_DATE_FILTER, + payload: { + start_date, + end_date, + }, + }); + + appendQueryString({ ...appliedFilters, start_date, end_date }, navigate); + + dispatch(fetchQuayJobsData()); + }; + +export const setQuayOtherSummaryFilter = () => (dispatch, getState) => { + const filteredResults = [...getState().quay.filteredResults]; + const keyWordArr = ["success", "failure"]; + const data = filteredResults.filter( + (item) => !keyWordArr.includes(item.jobStatus?.toLowerCase()) + ); + dispatch({ + type: TYPES.SET_QUAY_FILTERED_DATA, + payload: data, + }); + dispatch(tableReCalcValues()); +}; + +export const getQuaySummary = () => (dispatch, getState) => { + const results = [...getState().quay.filteredResults]; + + const countObj = calculateMetrics(results); + dispatch({ + type: TYPES.SET_QUAY_SUMMARY, + payload: countObj, + }); +}; + +export const setTableColumns = (key, isAdding) => (dispatch, getState) => { + let tableColumns = [...getState().quay.tableColumns]; + const tableFilters = getState().quay.tableFilters; + + if (isAdding) { + const filterObj = tableFilters.find((item) => item.value === key); + tableColumns.push(filterObj); + } else { + tableColumns = tableColumns.filter((item) => item.value !== key); + } + + dispatch({ + type: TYPES.SET_QUAY_COLUMNS, + payload: tableColumns, + }); +}; + +export const fetchGraphData = (uuid) => async (dispatch, getState) => { + try { + dispatch({ type: TYPES.GRAPH_LOADING }); + + const graphData = getState().ocp.graphData; + const hasData = graphData.filter((a) => a.uuid === uuid).length > 0; + if (!hasData) { + const response = await API.get(`${API_ROUTES.QUAY_GRAPH_API_V1}/${uuid}`); + + if (response.status === 200) { + const result = Object.keys(response.data).map((key) => [ + key, + response.data[key], + ]); + dispatch({ + type: TYPES.SET_QUAY_GRAPH_DATA, + payload: { uuid, data: result }, + }); + } + } + } catch (error) { + dispatch(showFailureToast()); + } + dispatch({ type: TYPES.GRAPH_COMPLETED }); +}; + +export const tableReCalcValues = () => (dispatch) => { + dispatch(getQuaySummary()); + dispatch(setQuayPageOptions(START_PAGE, DEFAULT_PER_PAGE)); + dispatch(sliceQuayTableRows(0, DEFAULT_PER_PAGE)); +}; diff --git a/frontend/src/actions/sortingActions.js b/frontend/src/actions/sortingActions.js new file mode 100644 index 00000000..742820b5 --- /dev/null +++ b/frontend/src/actions/sortingActions.js @@ -0,0 +1,34 @@ +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; +export const setActiveSortDir = (dir, currType) => { + if (currType === "cpt") { + dispatch(setCPTSortDir(dir)); + } else if (currType === "ocp") { + dispatch(setOCPSortDir(dir)); + } else if (currType === "quay") { + dispatch(setQuaySortDir(dir)); + } else if (currType === "telco") { + dispatch(setTelcoSortDir(dir)); + } +}; +export const setActiveSortIndex = (index, currType) => { + if (currType === "cpt") { + dispatch(setCPTSortIndex(index)); + } else if (currType === "ocp") { + dispatch(setOCPSortIndex(index)); + } else if (currType === "quay") { + dispatch(setQuaySortIndex(index)); + } else if (currType === "telco") { + dispatch(setTelcoSortIndex(index)); + } +}; +export const handleOnSort = (currType) => { + dispatch(sortTable(currType)); +}; diff --git a/frontend/src/actions/telcoActions.js b/frontend/src/actions/telcoActions.js new file mode 100644 index 00000000..beaf04f9 --- /dev/null +++ b/frontend/src/actions/telcoActions.js @@ -0,0 +1,291 @@ +import * as API_ROUTES from "@/utils/apiConstants"; +import * as TYPES from "@/actions/types.js"; + +import { + DEFAULT_PER_PAGE, + START_PAGE, +} from "@/assets/constants/paginationConstants"; +import { appendDateFilter, appendQueryString } from "@/utils/helper.js"; +import { + buildFilterData, + calculateMetrics, + deleteAppliedFilters, + getFilteredData, + getSelectedFilter, +} from "./commonActions"; + +import API from "@/utils/axiosInstance"; +import { cloneDeep } from "lodash"; +import { showFailureToast } from "@/actions/toastActions"; + +export const fetchTelcoJobsData = () => async (dispatch, getState) => { + try { + dispatch({ type: TYPES.LOADING }); + const { start_date, end_date } = getState().telco; + const response = await API.get(API_ROUTES.TELCO_JOBS_API_V1, { + params: { + pretty: true, + ...(start_date && { start_date }), + ...(end_date && { end_date }), + }, + }); + if (response.status === 200) { + const startDate = response.data.startDate, + endDate = response.data.endDate; + //on initial load startDate and endDate are empty, so from response append to url + appendDateFilter(startDate, endDate); + dispatch({ + type: TYPES.SET_TELCO_DATE_FILTER, + payload: { + start_date: startDate, + end_date: endDate, + }, + }); + } + if (response?.data?.results?.length > 0) { + dispatch({ + type: TYPES.SET_TELCO_JOBS_DATA, + payload: response.data.results, + }); + dispatch({ + type: TYPES.SET_TELCO_FILTERED_DATA, + payload: response.data.results, + }); + + dispatch(applyFilters()); + dispatch(tableReCalcValues()); + } + } catch (error) { + dispatch(showFailureToast()); + } + dispatch({ type: TYPES.COMPLETED }); +}; +export const setTelcoPage = (pageNo) => ({ + type: TYPES.SET_TELCO_PAGE, + payload: pageNo, +}); + +export const setTelcoPageOptions = (page, perPage) => ({ + type: TYPES.SET_TELCO_PAGE_OPTIONS, + payload: { page, perPage }, +}); +export const setTelcoSortIndex = (index) => ({ + type: TYPES.SET_TELCO_SORT_INDEX, + payload: index, +}); + +export const setTelcoSortDir = (direction) => ({ + type: TYPES.SET_TELCO_SORT_DIR, + payload: direction, +}); +export const sliceTelcoTableRows = + (startIdx, endIdx) => (dispatch, getState) => { + const results = [...getState().telco.filteredResults]; + + dispatch({ + type: TYPES.SET_TELCO_INIT_JOBS, + payload: results.slice(startIdx, endIdx), + }); + }; +export const setTelcoCatFilters = (category) => (dispatch, getState) => { + const filterData = [...getState().telco.filterData]; + const options = filterData.filter((item) => item.name === category)[0].value; + const list = options.map((item) => ({ name: item, value: item })); + + dispatch({ + type: TYPES.SET_TELCO_CATEGORY_FILTER, + payload: category, + }); + dispatch({ + type: TYPES.SET_TELCO_FILTER_OPTIONS, + payload: list, + }); +}; +export const removeTelcoAppliedFilters = + (filterKey, filterValue, navigate) => (dispatch, getState) => { + const { start_date, end_date } = getState().telco; + + const appliedFilters = dispatch( + deleteAppliedFilters(filterKey, filterValue, "telco") + ); + + dispatch({ + type: TYPES.SET_TELCO_APPLIED_FILTERS, + payload: appliedFilters, + }); + appendQueryString({ ...appliedFilters, start_date, end_date }, navigate); + dispatch(applyFilters()); + }; +export const applyFilters = () => (dispatch, getState) => { + const { appliedFilters } = getState().telco; + + const results = [...getState().telco.results]; + + const isFilterApplied = + Object.keys(appliedFilters).length > 0 && + Object.values(appliedFilters).flat().length > 0; + + const filtered = isFilterApplied + ? getFilteredData(appliedFilters, results) + : results; + + dispatch({ + type: TYPES.SET_TELCO_FILTERED_DATA, + payload: filtered, + }); + dispatch(tableReCalcValues()); + dispatch(buildFilterData("telco")); +}; +export const setTelcoAppliedFilters = (navigate) => (dispatch, getState) => { + const { selectedFilters, start_date, end_date } = getState().telco; + + const appliedFilterArr = selectedFilters.filter((i) => i.value.length > 0); + + const appliedFilters = {}; + appliedFilterArr.forEach((item) => { + appliedFilters[item["name"]] = item.value; + }); + + dispatch({ + type: TYPES.SET_TELCO_APPLIED_FILTERS, + payload: appliedFilters, + }); + appendQueryString({ ...appliedFilters, start_date, end_date }, navigate); + dispatch(applyFilters()); +}; + +export const setFilterFromURL = (searchParams) => ({ + type: TYPES.SET_TELCO_APPLIED_FILTERS, + payload: searchParams, +}); + +export const setSelectedFilterFromUrl = (params) => (dispatch, getState) => { + const selectedFilters = cloneDeep(getState().telco.selectedFilters); + for (const key in params) { + selectedFilters.find((i) => i.name === key).value = params[key].split(","); + } + dispatch({ + type: TYPES.SET_TELCO_SELECTED_FILTERS, + payload: selectedFilters, + }); +}; + +export const setSelectedFilter = + (selectedCategory, selectedOption, isFromMetrics) => (dispatch) => { + const selectedFilters = dispatch( + getSelectedFilter( + selectedCategory, + selectedOption, + "telco", + isFromMetrics + ) + ); + dispatch({ + type: TYPES.SET_TELCO_SELECTED_FILTERS, + payload: selectedFilters, + }); + }; + +export const setTelcoDateFilter = + (start_date, end_date, navigate) => (dispatch, getState) => { + const appliedFilters = getState().telco.appliedFilters; + + dispatch({ + type: TYPES.SET_TELCO_DATE_FILTER, + payload: { + start_date, + end_date, + }, + }); + + appendQueryString({ ...appliedFilters, start_date, end_date }, navigate); + + dispatch(fetchTelcoJobsData()); + }; + +export const getTelcoSummary = () => (dispatch, getState) => { + const results = [...getState().telco.filteredResults]; + + const countObj = calculateMetrics(results); + dispatch({ + type: TYPES.SET_TELCO_SUMMARY, + payload: countObj, + }); +}; + +export const setTelcoOtherSummaryFilter = () => (dispatch, getState) => { + const filteredResults = [...getState().telco.filteredResults]; + const keyWordArr = ["success", "failure"]; + const data = filteredResults.filter( + (item) => !keyWordArr.includes(item.jobStatus?.toLowerCase()) + ); + dispatch({ + type: TYPES.SET_TELCO_FILTERED_DATA, + payload: data, + }); + dispatch(tableReCalcValues()); +}; + +export const setTableColumns = (key, isAdding) => (dispatch, getState) => { + let tableColumns = [...getState().telco.tableColumns]; + const tableFilters = getState().telco.tableFilters; + + if (isAdding) { + const filterObj = tableFilters.find((item) => item.value === key); + tableColumns.push(filterObj); + } else { + tableColumns = tableColumns.filter((item) => item.value !== key); + } + + dispatch({ + type: TYPES.SET_TELCO_COLUMNS, + payload: tableColumns, + }); +}; +export const fetchGraphData = + (benchmark, uuid, encryption) => async (dispatch, getState) => { + try { + dispatch({ type: TYPES.GRAPH_LOADING }); + + const graphData = getState().ocp.graphData; + const hasData = graphData.filter((a) => a.uuid === uuid).length > 0; + if (!hasData) { + const response = await API.get( + `${API_ROUTES.TELCO_GRAPH_API_V1}/${uuid}/${encryption}` + ); + + if (response.status === 200) { + let result; + if ( + benchmark === "oslat" || + benchmark === "cyclictest" || + benchmark === "deployment" + ) { + const benchmarkData = response.data[benchmark]; + result = Object.keys(response.data[benchmark]).map((key) => [ + key, + benchmarkData[key], + ]); + } else { + result = Object.keys(response.data).map((key) => [ + key, + response.data[key], + ]); + } + + dispatch({ + type: TYPES.SET_TELCO_GRAPH_DATA, + payload: { uuid, data: result }, + }); + } + } + } catch (error) { + dispatch(showFailureToast()); + } + dispatch({ type: TYPES.GRAPH_COMPLETED }); + }; +export const tableReCalcValues = () => (dispatch) => { + dispatch(getTelcoSummary()); + dispatch(setTelcoPageOptions(START_PAGE, DEFAULT_PER_PAGE)); + dispatch(sliceTelcoTableRows(0, DEFAULT_PER_PAGE)); +}; diff --git a/frontend/src/actions/types.js b/frontend/src/actions/types.js index 77db108b..1804cf21 100644 --- a/frontend/src/actions/types.js +++ b/frontend/src/actions/types.js @@ -43,3 +43,37 @@ 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"; +/* QUAY Jobs*/ +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_PAGE = "SET_QUAY_PAGE"; +export const SET_QUAY_PAGE_OPTIONS = "SET_QUAY_PAGE_OPTIONS"; +export const SET_QUAY_INIT_JOBS = "SET_QUAY_INIT_JOBS"; +export const SET_QUAY_FILTERED_DATA = "SET_QUAY_FILTERED_DATA"; +export const SET_QUAY_CATEGORY_FILTER = "SET_QUAY_CATEGORY_FILTER"; +export const SET_QUAY_FILTER_OPTIONS = "SET_QUAY_FILTER_OPTIONS"; +export const SET_QUAY_FILTER_DATA = "SET_QUAY_FILTER_DATA"; +export const SET_QUAY_APPLIED_FILTERS = "SET_QUAY_APPLIED_FILTERS"; +export const SET_QUAY_SELECTED_FILTERS = "SET_QUAY_SELECTED_FILTERS"; +export const SET_QUAY_SUMMARY = "SET_QUAY_SUMMARY"; +export const SET_QUAY_COLUMNS = "SET_QUAY_COLUMNS"; +export const SET_QUAY_GRAPH_DATA = "SET_QUAY_GRAPH_DATA"; +/* Telco Jobs */ +export const SET_TELCO_JOBS_DATA = "SET_TELCO_JOBS_DATA"; +export const SET_TELCO_DATE_FILTER = "SET_TELCO_DATE_FILTER"; +export const SET_TELCO_SORT_INDEX = "SET_TELCO_SORT_INDEX"; +export const SET_TELCO_SORT_DIR = "SET_TELCO_SORT_DIR"; +export const SET_TELCO_PAGE = "SET_TELCO_PAGE"; +export const SET_TELCO_PAGE_OPTIONS = "SET_TELCO_PAGE_OPTIONS"; +export const SET_TELCO_INIT_JOBS = "SET_TELCO_INIT_JOBS"; +export const SET_TELCO_FILTERED_DATA = "SET_TELCO_FILTERED_DATA"; +export const SET_TELCO_CATEGORY_FILTER = "SET_TELCO_CATEGORY_FILTER"; +export const SET_TELCO_FILTER_OPTIONS = "SET_TELCO_FILTER_OPTIONS"; +export const SET_TELCO_FILTER_DATA = "SET_TELCO_FILTER_DATA"; +export const SET_TELCO_APPLIED_FILTERS = "SET_TELCO_APPLIED_FILTERS"; +export const SET_TELCO_SELECTED_FILTERS = "SET_TELCO_SELECTED_FILTERS"; +export const SET_TELCO_SUMMARY = "SET_TELCO_SUMMARY"; +export const SET_TELCO_COLUMNS = "SET_TELCO_COLUMNS"; +export const SET_TELCO_GRAPH_DATA = "SET_TELCO_GRAPH_DATA"; diff --git a/frontend/src/assets/constants/metadataConstants.js b/frontend/src/assets/constants/metadataConstants.js index e459310e..122850d7 100644 --- a/frontend/src/assets/constants/metadataConstants.js +++ b/frontend/src/assets/constants/metadataConstants.js @@ -1,3 +1,6 @@ export const CLUSTER = "CLUSTER"; export const NODE_TYPE = "NODE_TYPE"; export const NODE_COUNT = "NODE_COUNT"; +export const API_RESULTS = "Quay API Status Codes"; +export const LATENCY_RESULTS = "Quay Latencies"; +export const IMAGE_RESULTS = "Quay Image Status Codes"; diff --git a/frontend/src/assets/constants/splunkConstants.js b/frontend/src/assets/constants/splunkConstants.js new file mode 100644 index 00000000..bd00b0be --- /dev/null +++ b/frontend/src/assets/constants/splunkConstants.js @@ -0,0 +1,22 @@ +export const SPLUNK_BASE_URL = + "https://rhcorporate.splunkcloud.com/en-GB/app/search/"; + +export const BENCHMARK_URL = { + cyclictest: "cyclictest_kpis", + cpu_util: "cpu_util_kpis", + deployment: "deployment_kpis", + oslat: "oslat_kpis", + ptp: "ptp_kpis", + reboot: "reboot_kpis", + "rfc-2544": "rfc2544_", +}; + +export const THRESHOLD_VALUE = 0.03; +export const CPU_UTIL_QUERY = `form.high_cpu_treshhold=${THRESHOLD_VALUE}&form.selected_duration=*`; +export const CHART_COMPARISON_QUERY = `form.charts_comparison=ocp_version`; +export const OCP_VIEW_QUERY = `form.ocp_view=ocp_version`; +export const REBOOT_QUERY = `form.reboot_type=soft_reboot`; + +export const BUBBLE_CHART_LEGEND_QUERY = "form.bubble_chart_legend="; +export const RFC_LEGEND_VALUE = "kernel"; +export const PTP_LEGEND_VALUE = "ocp_build"; diff --git a/frontend/src/components/atoms/GrafanaLink/index.jsx b/frontend/src/components/atoms/GrafanaLink/index.jsx new file mode 100644 index 00000000..f4b01c30 --- /dev/null +++ b/frontend/src/components/atoms/GrafanaLink/index.jsx @@ -0,0 +1,62 @@ +import * as CONSTANTS from "@/assets/constants/grafanaConstants"; + +import GrafanaIcon from "@/assets/images/grafana-icon.png"; +import LinkIcon from "@/components/atoms/LinkIcon"; +import Proptypes from "prop-types"; +import { useMemo } from "react"; + +const GrafanaLink = (props) => { + const { config, startDate, endDate } = props; + const grafanaLink = useMemo(() => { + const ciSystem_lCase = config.ciSystem?.toLowerCase(); + const isProw = ciSystem_lCase === "prow"; + const discreteBenchmark = + CONSTANTS.ciSystemMap[ciSystem_lCase]?.[ciSystem_lCase?.benchmark]; + + const hasBenchmark = Object.prototype.hasOwnProperty.call( + CONSTANTS.ciSystemMap?.[ciSystem_lCase], + config.benchmark + ); + const datasource = isProw + ? CONSTANTS.PROW_DATASOURCE + : hasBenchmark + ? discreteBenchmark?.dataSource + : CONSTANTS.DEFAULT_DATASOURCE; + + const dashboardURL = + discreteBenchmark?.dashboardURL ?? CONSTANTS.DASHBOARD_KUBE_BURNER; + + const datePart = `&from=${startDate}&to=${endDate}`; + const uuidPart = `&var-uuid=${config.uuid}`; + + if (config.benchmark === CONSTANTS.QUAY_LOAD_TEST) + return `${CONSTANTS.GRAFANA_BASE_URL}${CONSTANTS.DASHBOARD_QUAY}${datePart}${uuidPart}`; + return `${CONSTANTS.GRAFANA_BASE_URL}${dashboardURL}${datasource}${datePart}&var-platform=${config.platform}"&var-workload=${config.benchmark}${uuidPart}`; + }, [ + config.benchmark, + config.ciSystem, + config.platform, + config.uuid, + endDate, + startDate, + ]); + + return ( + + ); +}; + +GrafanaLink.propTypes = { + config: Proptypes.object, + endDate: Proptypes.number, + startDate: Proptypes.number, +}; + +export default GrafanaLink; diff --git a/frontend/src/components/atoms/PlotGraph/index.jsx b/frontend/src/components/atoms/PlotGraph/index.jsx index f19c103a..182496f7 100644 --- a/frontend/src/components/atoms/PlotGraph/index.jsx +++ b/frontend/src/components/atoms/PlotGraph/index.jsx @@ -4,7 +4,7 @@ import PropTypes from "prop-types"; const PlotGraph = (props) => { return ( diff --git a/frontend/src/components/atoms/SplunkLink/index.jsx b/frontend/src/components/atoms/SplunkLink/index.jsx new file mode 100644 index 00000000..7b4b86b0 --- /dev/null +++ b/frontend/src/components/atoms/SplunkLink/index.jsx @@ -0,0 +1,87 @@ +import * as CONSTANTS from "@/assets/constants/splunkConstants"; + +import LinkIcon from "@/components/atoms/LinkIcon"; +import Proptypes from "prop-types"; +import SplunkIcon from "@/assets/images/splunk-icon.png"; +import { useMemo } from "react"; + +const CONSTANTSLink = (props) => { + const { config, startDate, endDate } = props; + const splunkLink = useMemo(() => { + const url = `${CONSTANTS.SPLUNK_BASE_URL}${ + CONSTANTS.BENCHMARK_URL[config.benchmark] + }`; + + const query = `form.global_time.earliest=${encodeURIComponent( + new Date(startDate).toISOString() + )}&form.global_time.latest=${encodeURIComponent( + new Date(endDate).toISOString() + )}&form.formal_tag=${encodeURIComponent( + config.formal + )}&form.ocp_version=${encodeURIComponent( + config.shortVersion + )}&&form.ocp_build=${encodeURIComponent( + config.ocpVersion + )}&form.node_name=${encodeURIComponent(config.nodeName)}& + &form.general_statistics=${encodeURIComponent(config.shortVersion)}`; + + const kernelQuery = `form.dashboard_kernels=${encodeURIComponent( + config.kernel + )}`; + + const histogramQuery = `form.histogram=${encodeURIComponent( + config.ocpVersion + )}`; + + switch (config.benchmark) { + case "cyclictest": { + return `${url}?${query}&${CONSTANTS.OCP_VIEW_QUERY}&${kernelQuery}`; + } + case "cpu_util": { + return `${url}?${query}&${CONSTANTS.CPU_UTIL_QUERY}&${kernelQuery}`; + } + case "deployment": { + return `${url}?${query}`; + } + case "oslat": { + return `${url}?${query}&${CONSTANTS.OCP_VIEW_QUERY}&${CONSTANTS.CHART_COMPARISON_QUERY}&${kernelQuery}`; + } + case "ptp": { + return `${url}?${query}&${CONSTANTS.BUBBLE_CHART_LEGEND_QUERY}${CONSTANTS.PTP_LEGEND_VALUE}&${kernelQuery}`; + } + case "reboot": { + return `${url}?${query}&${CONSTANTS.CHART_COMPARISON_QUERY}&${CONSTANTS.REBOOT_QUERY}&${kernelQuery}`; + } + case "rfc-2544": { + return `${url}?${query}&${CONSTANTS.BUBBLE_CHART_LEGEND_QUERY}${CONSTANTS.RFC_LEGEND_VALUE}&${histogramQuery}&${kernelQuery}`; + } + } + }, [ + config.benchmark, + config.formal, + config.kernel, + config.nodeName, + config.ocpVersion, + config.shortVersion, + endDate, + startDate, + ]); + + return ( + + ); +}; + +CONSTANTSLink.propTypes = { + config: Proptypes.object, + endDate: Proptypes.number, + startDate: Proptypes.number, +}; +export default CONSTANTSLink; diff --git a/frontend/src/components/molecules/ExpandedRow/index.jsx b/frontend/src/components/molecules/ExpandedRow/index.jsx index 0edff39e..981d5660 100644 --- a/frontend/src/components/molecules/ExpandedRow/index.jsx +++ b/frontend/src/components/molecules/ExpandedRow/index.jsx @@ -9,6 +9,7 @@ import PlotGraph from "@/components/atoms/PlotGraph"; import PropTypes from "prop-types"; import TasksInfo from "@/components/molecules/TasksInfo"; import { uid } from "@/utils/helper.js"; +import { useMemo } from "react"; import { useSelector } from "react-redux"; const RowContent = (props) => { @@ -22,13 +23,23 @@ const RowContent = (props) => { return hasData; }; - const content = [ - { heading: "Cluster config", category: CONSTANTS.CLUSTER }, - { heading: "Node Type", category: CONSTANTS.NODE_TYPE }, - { heading: "Node Count", category: CONSTANTS.NODE_COUNT }, - ]; + + const content = useMemo(() => { + return [ + { heading: "Cluster config", category: CONSTANTS.CLUSTER }, + { heading: "Node Type", category: CONSTANTS.NODE_TYPE }, + { heading: "Node Count", category: CONSTANTS.NODE_COUNT }, + ]; + }, []); const isGraphLoading = useSelector((state) => state.loading.isGraphLoading); + const graphTitle = useMemo(() => { + return { + apiResults: CONSTANTS.API_RESULTS, + latencyResults: CONSTANTS.LATENCY_RESULTS, + imageResults: CONSTANTS.IMAGE_RESULTS, + }; + }, []); return ( @@ -49,17 +60,30 @@ const RowContent = (props) => { Tasks ran - + - {isGraphLoading ? ( + {isGraphLoading && !hasGraphData(props.item.uuid) ? (
) : hasGraphData(props.item.uuid) ? ( - + getGraphData(props.item.uuid)[0]?.data.length === 0 ? ( +
No data to plot
+ ) : ( + getGraphData(props.item.uuid)[0]?.data?.map((bit) => { + return ( + <> + + {graphTitle[bit[0]] ?? bit[0]} + + + + ); + }) + ) ) : (
No data to plot
)} diff --git a/frontend/src/components/molecules/MetaDataRow/index.jsx b/frontend/src/components/molecules/MetaDataRow/index.jsx index c6d46e57..99ec7e91 100644 --- a/frontend/src/components/molecules/MetaDataRow/index.jsx +++ b/frontend/src/components/molecules/MetaDataRow/index.jsx @@ -1,9 +1,15 @@ import "./index.less"; +import { + CheckCircleIcon, + ExclamationCircleIcon, + ExclamationTriangleIcon, +} from "@patternfly/react-icons"; import { Table, Tbody, Th, Thead, Tr } from "@patternfly/react-table"; import PropTypes from "prop-types"; import { Title } from "@patternfly/react-core"; +import { formatTime } from "@/helpers/Formatters.js"; import { uid } from "@/utils/helper.js"; import { useMemo } from "react"; import { useSelector } from "react-redux"; @@ -25,7 +31,24 @@ const MetadataRow = (props) => { NODE_COUNT: nodeCount, }; }, [clusterMetaData, nodeKeys, nodeCount]); - + const defaultValue = useMemo(() => { + return { + clusterType: "SNO spoke", + networkType: "OVNKubernetes", + masterNodesCount: "1", + master_type: "Baremetal", + totalNodesCount: "1", + }; + }, []); + const icons = useMemo( + () => ({ + failed: , + failure: , + success: , + upstream_failed: , + }), + [] + ); return ( <> @@ -44,7 +67,13 @@ const MetadataRow = (props) => { {memoObj[props?.category].map((item) => ( <Tr key={uid()}> <Th>{item.name}</Th> - <Th>{props.metadata[item.value]}</Th> + <Th> + {item.value === "jobDuration" + ? formatTime(props.metadata[item.value]) + : item.value === "jobStatus" + ? icons[props.metadata[item.value]] + : props.metadata[item.value] ?? defaultValue[item.value]} + </Th> </Tr> ))} </Tbody> diff --git a/frontend/src/components/molecules/MultiSelectBox/index.jsx b/frontend/src/components/molecules/MultiSelectBox/index.jsx index 7acbf263..9f601786 100644 --- a/frontend/src/components/molecules/MultiSelectBox/index.jsx +++ b/frontend/src/components/molecules/MultiSelectBox/index.jsx @@ -16,7 +16,6 @@ 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); }; @@ -33,7 +32,6 @@ const MultiSelectBox = (props) => { setIsDirty(false); }; const toggle = (toggleRef) => { - console.log(props.selected); return ( <MenuToggle variant="typeahead" diff --git a/frontend/src/components/molecules/TableRows/index.jsx b/frontend/src/components/molecules/TableRows/index.jsx index e876b732..9a45e6d1 100644 --- a/frontend/src/components/molecules/TableRows/index.jsx +++ b/frontend/src/components/molecules/TableRows/index.jsx @@ -1,6 +1,6 @@ import "./index.less"; -import { ExpandableRowContent, Td, Tr } from "@patternfly/react-table"; +import { ExpandableRowContent, Tbody, Td, Tr } from "@patternfly/react-table"; import RowContent from "@/components/molecules/ExpandedRow"; import TableCell from "@/components/atoms/TableCell"; @@ -12,39 +12,43 @@ const TableRows = (props) => { return ( rows?.length > 0 && rows.map((item, rowIndex) => { - return ( - <> - <Tr key={uid()}> - {addExpansion && ( - <Td - expand={{ - rowIndex, - isExpanded: props?.isRunExpanded(item), - onToggle: () => - props?.setRunExpanded(item, !props?.isRunExpanded(item)), - expandId: `expandable-row${uid()}`, - }} - /> - )} + return addExpansion ? ( + <Tbody isExpanded={props?.isRunExpanded(item)} key={uid()}> + <Tr> + <Td + expand={{ + rowIndex, + isExpanded: props?.isRunExpanded(item), + onToggle: () => + props?.setRunExpanded(item, !props?.isRunExpanded(item)), + expandId: `expandable-row${uid()}`, + }} + /> {columns.map((col) => ( <TableCell key={uid()} col={col} item={item} /> ))} </Tr> - {addExpansion && ( - <Tr isExpanded={props?.isRunExpanded(item)}> - <Td colSpan={8}> - <ExpandableRowContent> - <RowContent - item={item} - graphData={props.graphData} - type={props.type} - /> - </ExpandableRowContent> - </Td> - </Tr> - )} - </> + + <Tr isExpanded={props?.isRunExpanded(item)}> + <Td colSpan={8}> + <ExpandableRowContent> + <RowContent + key={uid()} + item={item} + graphData={props.graphData} + type={props.type} + /> + </ExpandableRowContent> + </Td> + </Tr> + </Tbody> + ) : ( + <Tr> + {columns.map((col) => ( + <TableCell key={uid()} col={col} item={item} /> + ))} + </Tr> ); }) ); diff --git a/frontend/src/components/molecules/TasksInfo/index.jsx b/frontend/src/components/molecules/TasksInfo/index.jsx index 4c385ec3..8d24e995 100644 --- a/frontend/src/components/molecules/TasksInfo/index.jsx +++ b/frontend/src/components/molecules/TasksInfo/index.jsx @@ -1,18 +1,17 @@ import "./index.less"; -import * as CONSTANTS from "@/assets/constants/grafanaConstants"; - import { CheckCircleIcon, ExclamationCircleIcon, ExclamationTriangleIcon, } from "@patternfly/react-icons"; -import GrafanaIcon from "@/assets/images/grafana-icon.png"; +import GrafanaLink from "@/components/atoms/GrafanaLink"; import JenkinsIcon from "@/assets/images/jenkins-icon.svg"; import LinkIcon from "@/components/atoms/LinkIcon"; import Proptypes from "prop-types"; import ProwIcon from "@/assets/images/prow-icon.png"; +import SplunkLink from "@/components/atoms/SplunkLink"; import { formatTime } from "@/helpers/Formatters.js"; import { useMemo } from "react"; @@ -33,63 +32,36 @@ const TasksInfo = (props) => { [config.jobStatus] ); - const grafanaLink = useMemo(() => { - const ciSystem_lCase = config.ciSystem?.toLowerCase(); - const isProw = ciSystem_lCase === "prow"; - const discreteBenchmark = - CONSTANTS.ciSystemMap[ciSystem_lCase]?.[ciSystem_lCase?.benchmark]; - - const hasBenchmark = Object.prototype.hasOwnProperty.call( - CONSTANTS.ciSystemMap?.[ciSystem_lCase], - config.benchmark - ); - const datasource = isProw - ? CONSTANTS.PROW_DATASOURCE - : hasBenchmark - ? discreteBenchmark?.dataSource - : CONSTANTS.DEFAULT_DATASOURCE; - - const dashboardURL = - discreteBenchmark?.dashboardURL ?? CONSTANTS.DASHBOARD_KUBE_BURNER; - - const datePart = `&from=${startDate}&to=${endDate}`; - const uuidPart = `&var-uuid=${config.uuid}`; - - if (config.benchmark === CONSTANTS.QUAY_LOAD_TEST) - return `${CONSTANTS.GRAFANA_BASE_URL}${CONSTANTS.DASHBOARD_QUAY}${datePart}${uuidPart}`; - return `${CONSTANTS.GRAFANA_BASE_URL}${dashboardURL}${datasource}${datePart}&var-platform=${config.platform}"&var-workload=${config.benchmark}${uuidPart}`; - }, [ - config.benchmark, - config.ciSystem, - config.platform, - config.uuid, - endDate, - startDate, - ]); - const icons = useMemo( () => ({ - failed: <ExclamationCircleIcon />, - failure: <ExclamationCircleIcon />, - success: <CheckCircleIcon />, - upstream_failed: <ExclamationTriangleIcon />, + failed: <ExclamationCircleIcon fill={"#C9190B"} />, + failure: <ExclamationCircleIcon fill={"#C9190B"} />, + success: <CheckCircleIcon fill={"#3E8635"} />, + upstream_failed: <ExclamationTriangleIcon fill={"#F0AB00"} />, }), [] ); + return ( <> <div className="info-wrapper"> <div>{icons[status] ?? status.toUpperCase()}</div> <div>{config.benchmark}</div> - <div>{`(${formatTime(config?.jobDuration)})`}</div> - <LinkIcon - link={grafanaLink} - target={"_blank"} - src={GrafanaIcon} - altText={"grafana link"} - height={30} - width={30} - /> + <div> + {status !== "upstream_failed" + ? `(${formatTime(config?.jobDuration)})` + : "Skipped"} + </div> + {props.type === "ocp" && ( + <GrafanaLink + config={config} + startDate={startDate} + endDate={endDate} + /> + )} + {props.type === "telco" && ( + <SplunkLink config={config} startDate={startDate} endDate={endDate} /> + )} <LinkIcon link={config.buildUrl} target={"_blank"} @@ -104,5 +76,6 @@ const TasksInfo = (props) => { }; TasksInfo.propTypes = { config: Proptypes.object, + type: Proptypes.string, }; export default TasksInfo; diff --git a/frontend/src/components/molecules/TasksInfo/index.less b/frontend/src/components/molecules/TasksInfo/index.less index dd088b46..e51880b8 100644 --- a/frontend/src/components/molecules/TasksInfo/index.less +++ b/frontend/src/components/molecules/TasksInfo/index.less @@ -1,5 +1,5 @@ .info-wrapper { display: flex; - justify-content: space-between; - width: 50%; + justify-content: space-evenly; + width: 35%; } \ No newline at end of file diff --git a/frontend/src/components/organisms/MetricsTab/index.jsx b/frontend/src/components/organisms/MetricsTab/index.jsx index abe7bc44..83e10839 100644 --- a/frontend/src/components/organisms/MetricsTab/index.jsx +++ b/frontend/src/components/organisms/MetricsTab/index.jsx @@ -6,6 +6,11 @@ import { AccordionItem, AccordionToggle, } from "@patternfly/react-core"; +import { + removeAppliedFilters, + setAppliedFilters, + setOtherSummaryFilter, +} from "@/actions/filterActions"; import MetricCard from "@/components/molecules/MetricCard"; import PropTypes from "prop-types"; @@ -21,6 +26,30 @@ const MetricsTab = (props) => { setExpanded(id); } }; + const removeStatusFilter = () => { + if ( + Array.isArray(props.appliedFilters["jobStatus"]) && + props.appliedFilters["jobStatus"].length > 0 + ) { + props.appliedFilters["jobStatus"].forEach((element) => { + props.updateSelectedFilter("jobStatus", element, true); + removeAppliedFilters( + "jobStatus", + element, + props.navigation, + props.type + ); + }); + } + }; + const applyStatusFilter = (value) => { + props.updateSelectedFilter("jobStatus", value, true); + setAppliedFilters(props.navigation, props.type); + }; + const applyOtherFilter = () => { + removeStatusFilter(); + setOtherSummaryFilter(props.type); + }; return ( <Accordion togglePosition="start"> <AccordionItem> @@ -39,23 +68,23 @@ const MetricsTab = (props) => { > <MetricCard title={"No. of Jobs"} - clickHandler={props.removeStatusFilter} + clickHandler={removeStatusFilter} footer={totalItems} /> <MetricCard title={"Success"} - clickHandler={props.applyStatusFilter} + clickHandler={applyStatusFilter} footer={summary?.successCount} /> <MetricCard title={"Failure"} - clickHandler={props.applyStatusFilter} + clickHandler={applyStatusFilter} footer={summary?.failureCount} /> <MetricCard title={"Others"} footer={summary?.othersCount} - clickHandler={props.applyOtherFilter} + clickHandler={applyOtherFilter} /> </AccordionContent> </AccordionItem> @@ -65,8 +94,9 @@ const MetricsTab = (props) => { MetricsTab.propTypes = { totalItems: PropTypes.number, summary: PropTypes.object, - removeStatusFilter: PropTypes.func, - applyStatusFilter: PropTypes.func, - applyOtherFilter: PropTypes.func, + type: PropTypes.string, + updateSelectedFilter: PropTypes.func, + navigation: PropTypes.func, + appliedFilters: PropTypes.object, }; export default MetricsTab; diff --git a/frontend/src/components/organisms/Pagination/index.jsx b/frontend/src/components/organisms/Pagination/index.jsx index 490a801d..7b316a21 100644 --- a/frontend/src/components/organisms/Pagination/index.jsx +++ b/frontend/src/components/organisms/Pagination/index.jsx @@ -1,12 +1,38 @@ import { Pagination, PaginationVariant } from "@patternfly/react-core"; +import { + setPage, + setPageOptions, + sliceTableRows, +} from "@/actions/paginationActions"; + +import PropTypes from "prop-types"; +import { useCallback } from "react"; +import { useDispatch } from "react-redux"; const RenderPagination = (props) => { + const dispatch = useDispatch(); + const perPageOptions = [ { title: "25", value: 25 }, { title: "50", value: 50 }, { title: "100", value: 100 }, ]; + const onSetPage = useCallback( + (_evt, newPage, _perPage, startIdx, endIdx) => { + dispatch(setPage(newPage, props.type)); + dispatch(sliceTableRows(startIdx, endIdx, props.type)); + }, + [dispatch, props.type] + ); + const onPerPageSelect = useCallback( + (_evt, newPerPage, newPage, startIdx, endIdx) => { + dispatch(setPageOptions(newPage, newPerPage, props.type)); + dispatch(sliceTableRows(startIdx, endIdx, props.type)); + }, + [dispatch, props.type] + ); + return ( <Pagination itemCount={props?.items} @@ -15,10 +41,16 @@ const RenderPagination = (props) => { page={props.page} variant={PaginationVariant.bottom} perPageOptions={perPageOptions} - onSetPage={props.onSetPage} - onPerPageSelect={props.onPerPageSelect} + onSetPage={onSetPage} + onPerPageSelect={onPerPageSelect} /> ); }; +RenderPagination.propTypes = { + page: PropTypes.number, + perPage: PropTypes.number, + type: PropTypes.string, + items: PropTypes.number, +}; export default RenderPagination; diff --git a/frontend/src/components/organisms/TableFilters/index.jsx b/frontend/src/components/organisms/TableFilters/index.jsx index ddd44368..c5f5ae62 100644 --- a/frontend/src/components/organisms/TableFilters/index.jsx +++ b/frontend/src/components/organisms/TableFilters/index.jsx @@ -9,6 +9,12 @@ import { ToolbarContent, ToolbarItem, } from "@patternfly/react-core"; +import { + removeAppliedFilters, + setAppliedFilters, + setCatFilters, + setDateFilter, +} from "@/actions/filterActions.js"; import ColumnMenuFilter from "@/components/molecules/ColumnMenuFilter"; import DatePicker from "react-date-picker"; @@ -27,11 +33,7 @@ const TableFilter = (props) => { appliedFilters, start_date, end_date, - onCategoryChange, - onOptionsChange, - deleteItem, - startDateChangeHandler, - endDateChangeHandler, + navigation, type, showColumnMenu, setColumns, @@ -50,6 +52,23 @@ const TableFilter = (props) => { return filter.name; }; + const onCategoryChange = (_event, value) => { + setCatFilters(value, type); + }; + const onOptionsChange = () => { + setAppliedFilters(navigation, type); + }; + const deleteItem = (key, value) => { + removeAppliedFilters(key, value, navigation, type); + updateSelectedFilter(key, value, false); + }; + const startDateChangeHandler = (date, key) => { + setDateFilter(date, key, navigation, type); + }; + const endDateChangeHandler = (date, key) => { + setDateFilter(key, date, navigation, type); + }; + return ( <> <Toolbar id="filter-toolbar"> @@ -128,15 +147,11 @@ TableFilter.propTypes = { appliedFilters: PropTypes.object, start_date: PropTypes.string, end_date: PropTypes.string, - onCategoryChange: PropTypes.func, - onOptionsChange: PropTypes.func, - deleteItem: PropTypes.func, - startDateChangeHandler: PropTypes.func, - endDateChangeHandler: PropTypes.func, type: PropTypes.string, showColumnMenu: PropTypes.bool, setColumns: PropTypes.func, selectedFilters: PropTypes.array, updateSelectedFilter: PropTypes.func, + navigation: PropTypes.func, }; export default TableFilter; diff --git a/frontend/src/components/organisms/TableLayout/index.jsx b/frontend/src/components/organisms/TableLayout/index.jsx index 57ee8062..500c1bc9 100644 --- a/frontend/src/components/organisms/TableLayout/index.jsx +++ b/frontend/src/components/organisms/TableLayout/index.jsx @@ -1,4 +1,9 @@ import { Table, Tbody, Th, Thead, Tr } from "@patternfly/react-table"; +import { + handleOnSort, + setActiveSortDir, + setActiveSortIndex, +} from "@/actions/sortingActions"; import PropTypes from "prop-types"; import RenderPagination from "@/components/organisms/Pagination"; @@ -11,17 +16,11 @@ const TableLayout = (props) => { tableColumns, activeSortIndex, activeSortDir, - setActiveSortIndex, - setActiveSortDir, - handleOnSort, page, perPage, - setPage, - setPerPage, - onSetPage, - onPerPageSelect, totalItems, addExpansion, + type, } = props; const getSortParams = (columnIndex) => ({ @@ -31,9 +30,9 @@ const TableLayout = (props) => { defaultDirection: "asc", }, onSort: (_event, index, direction) => { - setActiveSortIndex(index); - setActiveSortDir(direction); - handleOnSort(); + setActiveSortIndex(index, type); + setActiveSortDir(direction, type); + handleOnSort(type); }, columnIndex, }); @@ -43,7 +42,7 @@ const TableLayout = (props) => { <Table isStriped> <Thead> <Tr> - {addExpansion && <Th screenReaderText="Row expansion" />} + {addExpansion && <Th />} {tableColumns?.length > 0 && tableColumns.map((col, idx) => ( @@ -53,7 +52,19 @@ const TableLayout = (props) => { ))} </Tr> </Thead> - <Tbody isExpanded={addExpansion}> + {!addExpansion ? ( + <Tbody> + <TableRows + rows={tableData} + columns={tableColumns} + addExpansion={addExpansion} + isRunExpanded={props?.isRunExpanded} + setRunExpanded={props?.setRunExpanded} + graphData={props?.graphData} + type={props.type} + /> + </Tbody> + ) : ( <TableRows rows={tableData} columns={tableColumns} @@ -63,16 +74,13 @@ const TableLayout = (props) => { graphData={props?.graphData} type={props.type} /> - </Tbody> + )} </Table> <RenderPagination items={totalItems} page={page} - setPage={setPage} perPage={perPage} - setPerPage={setPerPage} - onSetPage={onSetPage} - onPerPageSelect={onPerPageSelect} + type={props.type} /> </> ); @@ -83,16 +91,9 @@ TableLayout.propTypes = { tableColumns: PropTypes.array, activeSortIndex: PropTypes.number || PropTypes.object, activeSortDir: PropTypes.string || PropTypes.object, - setActiveSortIndex: PropTypes.func, - setActiveSortDir: PropTypes.func, - handleOnSort: PropTypes.func, totalItems: PropTypes.number, page: PropTypes.number, perPage: PropTypes.number, - onPerPageSelect: PropTypes.func, - onSetPage: PropTypes.func, - setPage: PropTypes.func, - setPerPage: PropTypes.func, addExpansion: PropTypes.bool, graphData: PropTypes.array, type: PropTypes.string, diff --git a/frontend/src/components/templates/Home/index.jsx b/frontend/src/components/templates/Home/index.jsx index 8e6d8254..02d8b2a9 100644 --- a/frontend/src/components/templates/Home/index.jsx +++ b/frontend/src/components/templates/Home/index.jsx @@ -1,27 +1,17 @@ import { fetchOCPJobsData, - removeAppliedFilters, - setAppliedFilters, - setCPTSortDir, - setCPTSortIndex, - setCatFilters, - setDateFilter, + setCPTDateFilter, setFilterFromURL, - setOtherSummaryFilter, - setPage, - setPageOptions, setSelectedFilter, setSelectedFilterFromUrl, - sliceTableRows, } from "@/actions/homeActions.js"; -import { useCallback, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate, useSearchParams } from "react-router-dom"; import MetricsTab from "@//components/organisms/MetricsTab"; import TableFilter from "@/components/organisms/TableFilters"; import TableLayout from "@/components/organisms/TableLayout"; -import { sortTable } from "@/actions/commonActions"; +import { useEffect } from "react"; const Home = () => { const dispatch = useDispatch(); @@ -62,78 +52,14 @@ const Home = () => { } dispatch(setFilterFromURL(obj)); dispatch(setSelectedFilterFromUrl(params)); - dispatch(setDateFilter(startDate, endDate, navigate)); + dispatch(setCPTDateFilter(startDate, endDate, navigate)); } }, []); useEffect(() => { dispatch(fetchOCPJobsData()); }, [dispatch]); - //Sorting - const setActiveSortDir = (dir) => { - dispatch(setCPTSortDir(dir)); - }; - const setActiveSortIndex = (index) => { - dispatch(setCPTSortIndex(index)); - }; - const handleOnSort = () => { - dispatch(sortTable("cpt")); - }; - // Sorting - - // Pagination Helper - const onSetPage = useCallback( - (_evt, newPage, _perPage, startIdx, endIdx) => { - dispatch(setPage(newPage)); - dispatch(sliceTableRows(startIdx, endIdx)); - }, - [dispatch] - ); - const onPerPageSelect = useCallback( - (_evt, newPerPage, newPage, startIdx, endIdx) => { - dispatch(setPageOptions(newPage, newPerPage)); - dispatch(sliceTableRows(startIdx, endIdx)); - }, - [dispatch] - ); - // Pagination helper - // Filter Helper - const onCategoryChange = (_event, value) => { - dispatch(setCatFilters(value)); - }; - const onOptionsChange = () => { - dispatch(setAppliedFilters(navigate)); - }; - const deleteItem = (key, value) => { - dispatch(removeAppliedFilters(key, value, navigate)); - updateSelectedFilter(key, value, false); - }; - const startDateChangeHandler = (date, key) => { - dispatch(setDateFilter(date, key, navigate)); - }; - const endDateChangeHandler = (date, key) => { - dispatch(setDateFilter(key, date, navigate)); - }; - const removeStatusFilter = () => { - 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) => { - updateSelectedFilter("jobStatus", value, true); - dispatch(setAppliedFilters(navigate)); - }; - const applyOtherFilter = () => { - removeStatusFilter(); - dispatch(setOtherSummaryFilter()); - }; const updateSelectedFilter = (category, value, isFromMetrics) => { dispatch(setSelectedFilter(category, value, isFromMetrics)); }; @@ -143,9 +69,10 @@ const Home = () => { <MetricsTab totalItems={filteredResults.length} summary={summary} - removeStatusFilter={removeStatusFilter} - applyStatusFilter={applyStatusFilter} - applyOtherFilter={applyOtherFilter} + updateSelectedFilter={updateSelectedFilter} + navigation={navigate} + type={"cpt"} + appliedFilters={appliedFilters} /> <TableFilter @@ -156,15 +83,11 @@ const Home = () => { appliedFilters={appliedFilters} start_date={start_date} end_date={end_date} - onCategoryChange={onCategoryChange} - onOptionsChange={onOptionsChange} - deleteItem={deleteItem} - startDateChangeHandler={startDateChangeHandler} - endDateChangeHandler={endDateChangeHandler} type={"cpt"} selectedFilters={selectedFilters} updateSelectedFilter={updateSelectedFilter} showColumnMenu={false} + navigation={navigate} /> <TableLayout @@ -172,16 +95,11 @@ const Home = () => { tableColumns={tableColumns} activeSortIndex={activeSortIndex} activeSortDir={activeSortDir} - setActiveSortDir={setActiveSortDir} - setActiveSortIndex={setActiveSortIndex} - handleOnSort={handleOnSort} - onPerPageSelect={onPerPageSelect} - onSetPage={onSetPage} page={page} perPage={perPage} totalItems={filteredResults.length} addExpansion={false} - state={"cpt"} + type={"cpt"} /> </> ); diff --git a/frontend/src/components/templates/OCP/index.jsx b/frontend/src/components/templates/OCP/index.jsx index 623df07f..3d5932ba 100644 --- a/frontend/src/components/templates/OCP/index.jsx +++ b/frontend/src/components/templates/OCP/index.jsx @@ -1,20 +1,11 @@ import { fetchGraphData, fetchOCPJobs, - removeAppliedFilters, - setAppliedFilters, - setDateFilter, setFilterFromURL, - setOCPCatFilters, - setOCPSortDir, - setOCPSortIndex, - setOtherSummaryFilter, - setPage, - setPageOptions, + setOCPDateFilter, setSelectedFilter, setSelectedFilterFromUrl, setTableColumns, - sliceOCPTableRows, } from "@/actions/ocpActions"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; @@ -23,7 +14,6 @@ import { useNavigate, useSearchParams } from "react-router-dom"; import MetricsTab from "@/components/organisms/MetricsTab"; import TableFilter from "@/components/organisms/TableFilters"; import TableLayout from "@/components/organisms/TableLayout"; -import { sortTable } from "@/actions/commonActions.js"; const OCP = () => { const dispatch = useDispatch(); @@ -49,13 +39,6 @@ const OCP = () => { selectedFilters, } = useSelector((state) => state.ocp); - const modifidedTableFilters = useMemo( - () => - tableFilters.filter( - (item) => item.value !== "endDate" && item.value !== "startDate" - ), - [tableFilters] - ); useEffect(() => { if (searchParams.size > 0) { // date filter is set apart @@ -71,7 +54,7 @@ const OCP = () => { } dispatch(setFilterFromURL(obj)); dispatch(setSelectedFilterFromUrl(params)); - dispatch(setDateFilter(startDate, endDate, navigate)); + dispatch(setOCPDateFilter(startDate, endDate, navigate)); } }, []); @@ -79,76 +62,18 @@ const OCP = () => { dispatch(fetchOCPJobs()); }, [dispatch]); - //Sorting - const setActiveSortDir = (dir) => { - dispatch(setOCPSortDir(dir)); - }; - const setActiveSortIndex = (index) => { - dispatch(setOCPSortIndex(index)); - }; - const handleOnSort = () => { - dispatch(sortTable("ocp")); - }; - // Sorting - - // Pagination Helper - const onSetPage = useCallback( - (_evt, newPage, _perPage, startIdx, endIdx) => { - dispatch(setPage(newPage)); - dispatch(sliceOCPTableRows(startIdx, endIdx)); - }, - [dispatch] - ); - const onPerPageSelect = useCallback( - (_evt, newPerPage, newPage, startIdx, endIdx) => { - dispatch(setPageOptions(newPage, newPerPage)); - dispatch(sliceOCPTableRows(startIdx, endIdx)); - }, - [dispatch] + //Filter Helper + const modifidedTableFilters = useMemo( + () => + tableFilters.filter( + (item) => item.value !== "endDate" && item.value !== "startDate" + ), + [tableFilters] ); - // Pagination helper - /* Summary Tab Filter*/ - const removeStatusFilter = () => { - 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) => { - updateSelectedFilter("jobStatus", value, true); - dispatch(setAppliedFilters(navigate)); - }; - const applyOtherFilter = () => { - 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 = () => { - dispatch(setAppliedFilters(navigate)); - }; - const deleteItem = (key, value) => { - dispatch(removeAppliedFilters(key, value, navigate)); - updateSelectedFilter(key, value); - }; - const startDateChangeHandler = (date, key) => { - dispatch(setDateFilter(date, key, navigate)); - }; - const endDateChangeHandler = (date, key) => { - dispatch(setDateFilter(key, date, navigate)); - }; //Row expansion const [expandedRunNames, setExpandedRunNames] = useState([]); const setRunExpanded = (run, isExpanding = true) => { @@ -159,7 +84,7 @@ const OCP = () => { : otherExpandedRunNames; }); if (isExpanding) { - dispatch(fetchGraphData(run.uuid)); + dispatch(fetchGraphData(run.uuid, run.benchmark)); } }; @@ -175,9 +100,10 @@ const OCP = () => { <MetricsTab totalItems={filteredResults.length} summary={summary} - removeStatusFilter={removeStatusFilter} - applyStatusFilter={applyStatusFilter} - applyOtherFilter={applyOtherFilter} + updateSelectedFilter={updateSelectedFilter} + navigation={navigate} + type={"ocp"} + appliedFilters={appliedFilters} /> <TableFilter @@ -188,16 +114,12 @@ const OCP = () => { appliedFilters={appliedFilters} start_date={start_date} end_date={end_date} - onCategoryChange={onCategoryChange} - onOptionsChange={onOptionsChange} - deleteItem={deleteItem} - startDateChangeHandler={startDateChangeHandler} - endDateChangeHandler={endDateChangeHandler} type={"ocp"} showColumnMenu={true} setColumns={setColumns} selectedFilters={selectedFilters} updateSelectedFilter={updateSelectedFilter} + navigation={navigate} /> <TableLayout @@ -205,11 +127,6 @@ const OCP = () => { tableColumns={tableColumns} activeSortIndex={activeSortIndex} activeSortDir={activeSortDir} - setActiveSortDir={setActiveSortDir} - setActiveSortIndex={setActiveSortIndex} - handleOnSort={handleOnSort} - onPerPageSelect={onPerPageSelect} - onSetPage={onSetPage} page={page} perPage={perPage} totalItems={filteredResults.length} diff --git a/frontend/src/components/templates/Quay/index.jsx b/frontend/src/components/templates/Quay/index.jsx index d4b55e15..2f193fdf 100644 --- a/frontend/src/components/templates/Quay/index.jsx +++ b/frontend/src/components/templates/Quay/index.jsx @@ -1,5 +1,142 @@ +import { + fetchGraphData, + fetchQuayJobsData, + setFilterFromURL, + setQuayDateFilter, + setSelectedFilter, + setSelectedFilterFromUrl, + setTableColumns, +} from "@/actions/quayActions.js"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate, useSearchParams } from "react-router-dom"; + +import MetricsTab from "@/components/organisms/MetricsTab"; +import TableFilter from "@/components/organisms/TableFilters"; +import TableLayout from "@/components/organisms/TableLayout"; + const Quay = () => { - return <>Quay</>; + const dispatch = useDispatch(); + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + + const { + tableData, + tableColumns, + activeSortIndex, + activeSortDir, + page, + perPage, + filteredResults, + tableFilters, + filterOptions, + categoryFilterValue, + filterData, + appliedFilters, + start_date, + end_date, + selectedFilters, + graphData, + summary, + } = useSelector((state) => state.quay); + + useEffect(() => { + dispatch(fetchQuayJobsData()); + }, [dispatch]); + + useEffect(() => { + if (searchParams.size > 0) { + // date filter is set apart + const startDate = searchParams.get("start_date"); + const endDate = searchParams.get("end_date"); + + searchParams.delete("start_date"); + searchParams.delete("end_date"); + const params = Object.fromEntries(searchParams); + const obj = {}; + for (const key in params) { + obj[key] = params[key].split(","); + } + dispatch(setFilterFromURL(obj)); + dispatch(setSelectedFilterFromUrl(params)); + dispatch(setQuayDateFilter(startDate, endDate, navigate)); + } + }, []); + + //Filter Helper + const modifidedTableFilters = useMemo( + () => + tableFilters.filter( + (item) => item.value !== "endDate" && item.value !== "startDate" + ), + [tableFilters] + ); + const updateSelectedFilter = (category, value, isFromMetrics) => { + dispatch(setSelectedFilter(category, value, isFromMetrics)); + }; + //Filter Helper + //Row expansion + const [expandedRunNames, setExpandedRunNames] = useState([]); + const setRunExpanded = (run, isExpanding = true) => { + setExpandedRunNames((prevExpanded) => { + const otherExpandedRunNames = prevExpanded.filter((r) => r !== run.uuid); + return isExpanding + ? [...otherExpandedRunNames, run.uuid] + : otherExpandedRunNames; + }); + if (isExpanding) { + dispatch(fetchGraphData(run.uuid)); + } + }; + + const isRunExpanded = useCallback( + (run) => expandedRunNames.includes(run.uuid), + [expandedRunNames] + ); + const setColumns = (value, isAdding) => { + dispatch(setTableColumns(value, isAdding)); + }; + return ( + <> + <MetricsTab + totalItems={filteredResults.length} + summary={summary} + updateSelectedFilter={updateSelectedFilter} + navigation={navigate} + type={"quay"} + appliedFilters={appliedFilters} + /> + <TableFilter + tableFilters={modifidedTableFilters} + filterOptions={filterOptions} + categoryFilterValue={categoryFilterValue} + filterData={filterData} + appliedFilters={appliedFilters} + start_date={start_date} + end_date={end_date} + type={"quay"} + selectedFilters={selectedFilters} + updateSelectedFilter={updateSelectedFilter} + showColumnMenu={true} + setColumns={setColumns} + navigation={navigate} + /> + <TableLayout + tableData={tableData} + tableColumns={tableColumns} + activeSortIndex={activeSortIndex} + activeSortDir={activeSortDir} + page={page} + perPage={perPage} + totalItems={filteredResults.length} + type={"quay"} + addExpansion={true} + isRunExpanded={isRunExpanded} + setRunExpanded={setRunExpanded} + graphData={graphData} + /> + </> + ); }; export default Quay; diff --git a/frontend/src/components/templates/Telco/index.jsx b/frontend/src/components/templates/Telco/index.jsx index 8b9b5741..66c2defa 100644 --- a/frontend/src/components/templates/Telco/index.jsx +++ b/frontend/src/components/templates/Telco/index.jsx @@ -1,5 +1,141 @@ +import { + fetchGraphData, + fetchTelcoJobsData, + setFilterFromURL, + setSelectedFilter, + setSelectedFilterFromUrl, + setTableColumns, + setTelcoDateFilter, +} from "@/actions/telcoActions.js"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate, useSearchParams } from "react-router-dom"; + +import MetricsTab from "@/components/organisms/MetricsTab"; +import TableFilter from "@/components/organisms/TableFilters"; +import TableLayout from "@/components/organisms/TableLayout"; + const Telco = () => { - return <>Telco</>; + const dispatch = useDispatch(); + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const { + tableData, + tableColumns, + activeSortIndex, + activeSortDir, + page, + perPage, + filteredResults, + tableFilters, + filterOptions, + categoryFilterValue, + filterData, + appliedFilters, + start_date, + end_date, + selectedFilters, + summary, + graphData, + } = useSelector((state) => state.telco); + + useEffect(() => { + dispatch(fetchTelcoJobsData()); + }, [dispatch]); + + useEffect(() => { + if (searchParams.size > 0) { + // date filter is set apart + const startDate = searchParams.get("start_date"); + const endDate = searchParams.get("end_date"); + + searchParams.delete("start_date"); + searchParams.delete("end_date"); + const params = Object.fromEntries(searchParams); + const obj = {}; + for (const key in params) { + obj[key] = params[key].split(","); + } + dispatch(setFilterFromURL(obj)); + dispatch(setSelectedFilterFromUrl(params)); + dispatch(setTelcoDateFilter(startDate, endDate, navigate)); + } + }, []); + + //Filter Helper + const modifidedTableFilters = useMemo( + () => + tableFilters.filter( + (item) => item.value !== "endDate" && item.value !== "startDate" + ), + [tableFilters] + ); + const updateSelectedFilter = (category, value, isFromMetrics) => { + dispatch(setSelectedFilter(category, value, isFromMetrics)); + }; + //Filter Helper + //Row expansion + const [expandedRunNames, setExpandedRunNames] = useState([]); + const setRunExpanded = (run, isExpanding = true) => { + setExpandedRunNames((prevExpanded) => { + const otherExpandedRunNames = prevExpanded.filter((r) => r !== run.uuid); + return isExpanding + ? [...otherExpandedRunNames, run.uuid] + : otherExpandedRunNames; + }); + if (isExpanding) { + dispatch(fetchGraphData(run.benchmark, run.uuid, run.encryptedData)); + } + }; + + const isRunExpanded = useCallback( + (run) => expandedRunNames.includes(run.uuid), + [expandedRunNames] + ); + const setColumns = (value, isAdding) => { + dispatch(setTableColumns(value, isAdding)); + }; + return ( + <> + <MetricsTab + totalItems={filteredResults.length} + summary={summary} + updateSelectedFilter={updateSelectedFilter} + navigation={navigate} + type={"telco"} + appliedFilters={appliedFilters} + /> + <TableFilter + tableFilters={modifidedTableFilters} + filterOptions={filterOptions} + categoryFilterValue={categoryFilterValue} + filterData={filterData} + appliedFilters={appliedFilters} + start_date={start_date} + end_date={end_date} + type={"telco"} + updateSelectedFilter={updateSelectedFilter} + selectedFilters={selectedFilters} + showColumnMenu={true} + setColumns={setColumns} + navigation={navigate} + /> + <TableLayout + tableData={tableData} + tableColumns={tableColumns} + activeSortIndex={activeSortIndex} + activeSortDir={activeSortDir} + page={page} + perPage={perPage} + totalItems={filteredResults.length} + type={"telco"} + addExpansion={true} + isRunExpanded={isRunExpanded} + setRunExpanded={setRunExpanded} + graphData={graphData} + /> + </> + ); }; export default Telco; diff --git a/frontend/src/reducers/index.js b/frontend/src/reducers/index.js index caaa3874..1fb4c555 100644 --- a/frontend/src/reducers/index.js +++ b/frontend/src/reducers/index.js @@ -1,7 +1,9 @@ import HomeReducer from "./homeReducer"; import LoadingReducer from "./loadingReducer"; import OCPReducer from "./ocpReducer"; +import QuayReducer from "./quayReducer"; import SideMenuReducer from "./sideMenuReducer"; +import TelcoReducer from "./telcoReducer"; import ToastReducer from "./toastReducer"; import { combineReducers } from "redux"; @@ -11,4 +13,6 @@ export default combineReducers({ sidemenu: SideMenuReducer, cpt: HomeReducer, ocp: OCPReducer, + quay: QuayReducer, + telco: TelcoReducer, }); diff --git a/frontend/src/reducers/ocpReducer.js b/frontend/src/reducers/ocpReducer.js index 99b4d160..c422ff5e 100644 --- a/frontend/src/reducers/ocpReducer.js +++ b/frontend/src/reducers/ocpReducer.js @@ -68,7 +68,7 @@ const initialState = { { name: "Control Plane Architecture", value: "controlPlaneArch" }, ], nodeKeys: [ - { name: "Master", value: "masterNodesCount" }, + { name: "Master", value: "masterNodesType" }, { name: "Worker", value: "workerNodesType" }, { name: "Infra", value: "infraNodesType" }, { name: "Workload", value: "benchmark" }, diff --git a/frontend/src/reducers/quayReducer.js b/frontend/src/reducers/quayReducer.js new file mode 100644 index 00000000..bc3ff723 --- /dev/null +++ b/frontend/src/reducers/quayReducer.js @@ -0,0 +1,118 @@ +import * as TYPES from "@/actions/types"; + +import { + DEFAULT_PER_PAGE, + START_PAGE, +} from "@/assets/constants/paginationConstants"; + +const initialState = { + results: [], + start_date: "", + end_date: "", + tableColumns: [ + { name: "Benchmark", value: "benchmark" }, + { name: "Release Stream", value: "releaseStream" }, + { name: "Platform", value: "platform" }, + { name: "Worker Count", value: "workerNodesCount" }, + { name: "Start Date", value: "startDate" }, + { name: "End Date", value: "endDate" }, + { name: "Status", value: "jobStatus" }, + ], + tableFilters: [ + { name: "Benchmark", value: "benchmark" }, + { name: "Release Stream", value: "releaseStream" }, + { name: "Platform", value: "platform" }, + { name: "Worker Count", value: "workerNodesCount" }, + { name: "Status", value: "jobStatus" }, + ], + selectedFilters: [ + { name: "benchmark", value: [] }, + { name: "releaseStream", value: [] }, + { name: "platform", value: [] }, + { name: "workerNodesCount", value: [] }, + { name: "jobStatus", value: [] }, + ], + clusterMetaData: [ + { name: "Release Binary", value: "releaseStream" }, + { name: "Cluster Name", value: "clusterName" }, + { name: "Cluster Type", value: "clusterType" }, + { name: "Network Type", value: "networkType" }, + { name: "Benchmark Status", value: "jobStatus" }, + { name: "Duration", value: "jobDuration" }, + { name: "Test ID", value: "uuid" }, + ], + nodeKeys: [ + { name: "Master", value: "masterNodesType" }, + { name: "Worker", value: "workerNodesType" }, + { name: "Infra", value: "infraNodesType" }, + { name: "Workload", value: "benchmark" }, + ], + nodeCount: [ + { name: "Master", value: "masterNodesCount" }, + { name: "Worker", value: "workerNodesCount" }, + { name: "Infra", value: "infraNodesCount" }, + { name: "Total", value: "totalNodesCount" }, + ], + filterData: [], + filteredResults: [], + filterOptions: [], + categoryFilterValue: "Benchmark", + appliedFilters: {}, + activeSortDir: null, + activeSortIndex: null, + tableData: [], + graphData: [], + page: START_PAGE, + perPage: DEFAULT_PER_PAGE, + summary: {}, +}; + +const QuayReducer = (state = initialState, action = {}) => { + const { type, payload } = action; + + switch (type) { + case TYPES.SET_QUAY_JOBS_DATA: + return { + ...state, + results: payload, + }; + case TYPES.SET_QUAY_DATE_FILTER: + return { + ...state, + start_date: payload.start_date, + end_date: payload.end_date, + }; + case TYPES.SET_QUAY_SORT_INDEX: + return { ...state, activeSortIndex: payload }; + case TYPES.SET_QUAY_SORT_DIR: + return { ...state, activeSortDir: payload }; + case TYPES.SET_QUAY_PAGE: + return { ...state, page: payload }; + case TYPES.SET_QUAY_PAGE_OPTIONS: + return { ...state, page: payload.page, perPage: payload.perPage }; + case TYPES.SET_QUAY_INIT_JOBS: + return { ...state, tableData: payload }; + case TYPES.SET_QUAY_FILTERED_DATA: + return { ...state, filteredResults: payload }; + case TYPES.SET_QUAY_FILTER_OPTIONS: + return { ...state, filterOptions: payload }; + case TYPES.SET_QUAY_CATEGORY_FILTER: + return { ...state, categoryFilterValue: payload }; + case TYPES.SET_QUAY_FILTER_DATA: + return { ...state, filterData: payload }; + case TYPES.SET_QUAY_APPLIED_FILTERS: + return { ...state, appliedFilters: payload }; + case TYPES.SET_QUAY_SELECTED_FILTERS: + return { ...state, selectedFilters: payload }; + case TYPES.SET_QUAY_SUMMARY: + return { ...state, summary: payload }; + case TYPES.SET_QUAY_COLUMNS: + return { ...state, tableColumns: payload }; + case TYPES.SET_QUAY_GRAPH_DATA: + return { ...state, graphData: [...state.graphData, payload] }; + default: + return state; + } +}; + +export default QuayReducer; diff --git a/frontend/src/reducers/telcoReducer.js b/frontend/src/reducers/telcoReducer.js new file mode 100644 index 00000000..15ab045f --- /dev/null +++ b/frontend/src/reducers/telcoReducer.js @@ -0,0 +1,115 @@ +import * as TYPES from "@/actions/types"; + +import { + DEFAULT_PER_PAGE, + START_PAGE, +} from "@/assets/constants/paginationConstants"; + +const initialState = { + results: [], + start_date: "", + end_date: "", + tableColumns: [ + { name: "Benchmark", value: "benchmark" }, + { name: "Release Stream", value: "releaseStream" }, + { name: "CPU", value: "cpu" }, + { name: "Node Name", value: "nodeName" }, + { name: "Start Date", value: "startDate" }, + { name: "End Date", value: "endDate" }, + { name: "Status", value: "jobStatus" }, + ], + tableFilters: [ + { name: "Benchmark", value: "benchmark" }, + { name: "Release Stream", value: "releaseStream" }, + { name: "Build", value: "ocpVersion" }, + { name: "CPU", value: "cpu" }, + { name: "Node Name", value: "nodeName" }, + { name: "Status", value: "jobStatus" }, + ], + selectedFilters: [ + { name: "benchmark", value: [] }, + { name: "releaseStream", value: [] }, + { name: "ocpVersion", value: [] }, + { name: "cpu", value: [] }, + { name: "nodeName", value: [] }, + { name: "jobStatus", value: [] }, + ], + clusterMetaData: [ + { name: "Release Binary", value: "releaseStream" }, + { name: "Cluster Name", value: "nodeName" }, + { name: "Cluster Type", value: "clusterType" }, + { name: "Network Type", value: "networkType" }, + { name: "Benchmark Status", value: "jobStatus" }, + { name: "Duration", value: "jobDuration" }, + ], + nodeKeys: [ + { name: "Master", value: "master_type" }, + { name: "Workload", value: "benchmark" }, + ], + nodeCount: [ + { name: "Master", value: "masterNodesCount" }, + { name: "Total", value: "totalNodesCount" }, + ], + filterData: [], + filteredResults: [], + categoryFilterValue: "Benchmark", + filterOptions: [], + appliedFilters: {}, + activeSortDir: null, + activeSortIndex: null, + tableData: [], + graphData: [], + page: START_PAGE, + perPage: DEFAULT_PER_PAGE, + summary: {}, +}; + +const TelcoReducer = (state = initialState, action = {}) => { + const { type, payload } = action; + + switch (type) { + case TYPES.SET_TELCO_JOBS_DATA: + return { + ...state, + results: payload, + }; + case TYPES.SET_TELCO_DATE_FILTER: + return { + ...state, + start_date: payload.start_date, + end_date: payload.end_date, + }; + case TYPES.SET_TELCO_SORT_INDEX: + return { ...state, activeSortIndex: payload }; + case TYPES.SET_TELCO_SORT_DIR: + return { ...state, activeSortDir: payload }; + case TYPES.SET_TELCO_PAGE: + return { ...state, page: payload }; + case TYPES.SET_TELCO_PAGE_OPTIONS: + return { ...state, page: payload.page, perPage: payload.perPage }; + case TYPES.SET_TELCO_INIT_JOBS: + return { ...state, tableData: payload }; + case TYPES.SET_TELCO_FILTERED_DATA: + return { ...state, filteredResults: payload }; + case TYPES.SET_TELCO_CATEGORY_FILTER: + return { ...state, categoryFilterValue: payload }; + case TYPES.SET_TELCO_FILTER_OPTIONS: + return { ...state, filterOptions: payload }; + case TYPES.SET_TELCO_FILTER_DATA: + return { ...state, filterData: payload }; + case TYPES.SET_TELCO_APPLIED_FILTERS: + return { ...state, appliedFilters: payload }; + case TYPES.SET_TELCO_SELECTED_FILTERS: + return { ...state, selectedFilters: payload }; + case TYPES.SET_TELCO_SUMMARY: + return { ...state, summary: payload }; + case TYPES.SET_TELCO_COLUMNS: + return { ...state, tableColumns: payload }; + case TYPES.SET_TELCO_GRAPH_DATA: + return { ...state, graphData: [...state.graphData, payload] }; + default: + return state; + } +}; + +export default TelcoReducer; diff --git a/frontend/src/utils/apiConstants.js b/frontend/src/utils/apiConstants.js index e6d68406..52576b4a 100644 --- a/frontend/src/utils/apiConstants.js +++ b/frontend/src/utils/apiConstants.js @@ -14,3 +14,6 @@ export const CPT_JOBS_API_V1 = "/api/v1/cpt/jobs"; export const QUAY_JOBS_API_V1 = "/api/v1/quay/jobs"; export const QUAY_GRAPH_API_V1 = "/api/v1/quay/graph"; + +export const TELCO_JOBS_API_V1 = "/api/v1/telco/jobs"; +export const TELCO_GRAPH_API_V1 = "/api/v1/telco/graph";