diff --git a/dashboard/src/actions/datasetListActions.js b/dashboard/src/actions/datasetListActions.js
index 6cce71cbe7..d0d73fad97 100644
--- a/dashboard/src/actions/datasetListActions.js
+++ b/dashboard/src/actions/datasetListActions.js
@@ -1,27 +1,70 @@
+import * as CONSTANTS from "assets/constants/browsingPageConstants";
import * as TYPES from "./types";
+import { DANGER, ERROR_MSG } from "assets/constants/toastConstants";
+
import API from "../utils/axiosInstance";
+import { showToast } from "./toastActions";
import { uriTemplate } from "utils/helper";
-export const fetchPublicDatasets = () => async (dispatch, getState) => {
+export const fetchPublicDatasets = (page) => async (dispatch, getState) => {
try {
dispatch({ type: TYPES.LOADING });
const endpoints = getState().apiEndpoint.endpoints;
+ const { offset, limit, filter, searchKey, perPage } =
+ getState().datasetlist;
+ let publicData = [...getState().datasetlist.publicData];
+ const params = new URLSearchParams();
+ params.append("metadata", "dataset.uploaded");
+ params.append("access", "public");
+ params.append("offset", offset);
+ params.append("limit", limit);
+
+ if (searchKey) {
+ params.append("name", searchKey);
+ }
+ if (filter.startDate instanceof Date && !isNaN(filter.startDate)) {
+ params.append("start", filter.startDate.toUTCString());
+ }
+ if (filter.endDate instanceof Date && !isNaN(filter.endDate)) {
+ params.append("end", filter.endDate.toUTCString());
+ }
+
const response = await API.get(
uriTemplate(endpoints, "datasets_list", {}),
- { params: { metadata: "dataset.uploaded", access: "public" } }
+ { params }
);
+
if (response.status === 200 && response.data) {
+ const startIdx = (page - 1) * perPage;
+
+ if (publicData.length !== response.data.total) {
+ publicData = new Array(response.data.total);
+ }
+ publicData.splice(
+ startIdx,
+ response.data.results.length,
+ ...response.data.results
+ );
+
+ dispatch({
+ type: TYPES.UPDATE_PUBLIC_DATASETS,
+ payload: publicData,
+ });
+ // in case of last page, next_url is empty
+ const offset = response.data.next_url
+ ? new URLSearchParams(response.data.next_url).get("offset")
+ : response.data.total;
dispatch({
- type: "GET_PUBLIC_DATASETS",
- payload: response?.data?.results,
+ type: TYPES.SET_RESULT_OFFSET,
+ payload: Number(offset),
});
}
- dispatch({ type: TYPES.COMPLETED });
- dispatch(callLoading());
} catch (error) {
- return error;
+ dispatch(showToast(DANGER, ERROR_MSG));
+ dispatch({ type: TYPES.NETWORK_ERROR });
}
+ dispatch({ type: TYPES.COMPLETED });
};
export const getFavoritedDatasets = () => async (dispatch) => {
@@ -33,23 +76,39 @@ export const getFavoritedDatasets = () => async (dispatch) => {
});
};
-export const updateFavoriteRepoNames = (favorites) => async (dispatch) => {
- dispatch({
- type: TYPES.FAVORITED_DATASETS,
- payload: [...favorites],
- });
-};
+export const updateFavoriteRepoNames = (favorites) => ({
+ type: TYPES.FAVORITED_DATASETS,
+ payload: [...favorites],
+});
+
+export const setPageLimit = (newPerPage) => ({
+ type: TYPES.SET_PAGE_LIMIT,
+ payload: newPerPage,
+});
-export const updateTblData = (data) => async (dispatch) => {
+export const setFilterKeys = (startDate, endDate) => ({
+ type: TYPES.SET_DATE_RANGE,
+ payload: { startDate, endDate },
+});
+
+export const nameFilter = (value) => ({
+ type: TYPES.SET_SEARCH_KEY,
+ payload: value,
+});
+
+export const applyFilter = () => (dispatch) => {
dispatch({
type: TYPES.UPDATE_PUBLIC_DATASETS,
- payload: [...data],
+ payload: [],
});
+ dispatch({
+ type: TYPES.SET_RESULT_OFFSET,
+ payload: CONSTANTS.INITIAL_RESULT_OFFSET,
+ });
+ dispatch(fetchPublicDatasets(CONSTANTS.START_PAGE_NUMBER));
};
-export const callLoading = () => (dispatch) => {
- dispatch({ type: TYPES.LOADING });
- setTimeout(() => {
- dispatch({ type: TYPES.COMPLETED });
- }, 5000);
-};
+export const setPerPage = (value) => ({
+ type: TYPES.SET_PER_PAGE,
+ payload: value,
+});
diff --git a/dashboard/src/actions/types.js b/dashboard/src/actions/types.js
index ee559da137..001064790b 100644
--- a/dashboard/src/actions/types.js
+++ b/dashboard/src/actions/types.js
@@ -22,9 +22,13 @@ export const NAVBAR_OPEN = "NAVBAR_OPEN";
export const NAVBAR_CLOSE = "NAVBAR_CLOSE";
/* PUBLIC DATASETS */
-export const GET_PUBLIC_DATASETS = "GET_PUBLIC_DATASETS";
-export const FAVORITED_DATASETS = "GET_FAVORITE_DATASETS";
export const UPDATE_PUBLIC_DATASETS = "UPDATE_PUBLIC_DATASETS";
+export const FAVORITED_DATASETS = "GET_FAVORITE_DATASETS";
+export const SET_RESULT_OFFSET = "SET_RESULT_OFFSET";
+export const SET_PAGE_LIMIT = "SET_PAGE_LIMIT";
+export const SET_DATE_RANGE = "SET_DATE_RANGE";
+export const SET_SEARCH_KEY = "SET_SEARCH_KEY";
+export const SET_PER_PAGE = "SET_PER_PAGE";
/* DASHBOARD OVERVIEW */
export const USER_RUNS = "USER_RUNS";
diff --git a/dashboard/src/assets/constants/browsingPageConstants.js b/dashboard/src/assets/constants/browsingPageConstants.js
new file mode 100644
index 0000000000..a3777d3b04
--- /dev/null
+++ b/dashboard/src/assets/constants/browsingPageConstants.js
@@ -0,0 +1,10 @@
+export const DEFAULT_PER_PAGE = 20;
+export const INITIAL_PAGE_LIMIT = 60;
+export const INITIAL_RESULT_OFFSET = 0;
+export const OVERFETCH_FACTOR = 3;
+export const PER_PAGE_OPTIONS = [
+ { title: "10", value: 10 },
+ { title: "20", value: 20 },
+ { title: "50", value: 50 },
+];
+export const START_PAGE_NUMBER = 1;
diff --git a/dashboard/src/modules/components/DatePickerComponent/index.jsx b/dashboard/src/modules/components/DatePickerComponent/index.jsx
index ed69ea6c39..b6a72c3678 100644
--- a/dashboard/src/modules/components/DatePickerComponent/index.jsx
+++ b/dashboard/src/modules/components/DatePickerComponent/index.jsx
@@ -1,77 +1,101 @@
-import React, { useState } from "react";
+import "./index.less";
+
+import * as CONSTANTS from "assets/constants/browsingPageConstants";
+
import {
- InputGroup,
- InputGroupText,
+ Button,
DatePicker,
+ Split,
+ SplitItem,
isValidDate,
- Button,
} from "@patternfly/react-core";
-import "./index.less";
-import { filterData } from "utils/filterDataset";
-import {
- bumpToDate,
- dateFromUTCString,
- getTodayMidnightUTCDate,
-} from "utils/dateFunctions";
+import React, { useState } from "react";
+import { applyFilter, setFilterKeys } from "actions/datasetListActions";
+import { useDispatch, useSelector } from "react-redux";
+
+import { getTodayMidnightUTCDate } from "utils/dateFunctions";
+
+const DatePickerWidget = (props) => {
+ const dispatch = useDispatch();
+ const { filter } = useSelector((state) => state.datasetlist);
+
+ const [isEndDateError, setIsEndDateError] = useState(false);
+
+ const fromValidator = (date) =>
+ date <= getTodayMidnightUTCDate()
+ ? ""
+ : "The Uploaded date cannot be in the future!";
-const DatePickerWidget = ({
- dataArray,
- setPublicData,
- datasetName,
- setDateRange,
-}) => {
- const [fromDate, setFromDate] = useState({});
- const [toDate, setToDate] = useState(
- bumpToDate(getTodayMidnightUTCDate(), 1)
- );
- const [strDate, setStrDate] = useState(
- new Date().toLocaleDateString("fr-CA") // Return a YYYY-MM-DD string
- );
const toValidator = (date) =>
- date >= fromDate
+ isValidDate(filter.startDate) && date >= filter.startDate
? ""
- : "To date must be greater than or equal to from date";
+ : 'The "to" date must be after the "from" date';
- const onFromChange = (_str, date) => {
- const selectedDate = dateFromUTCString(_str);
- setFromDate(isValidDate(date) ? selectedDate : {});
+ const onFromChange = (_event, _str, date) => {
+ dispatch(setFilterKeys(date, filter.endDate));
+ if (filter.endDate) {
+ checkEndDate(date, filter.endDate);
+ } else {
+ setIsEndDateError(true);
+ }
+ };
+
+ const onToChange = (_event, _str, date) => {
if (isValidDate(date)) {
- if (date > new Date(strDate)) {
- setToDate(bumpToDate(dateFromUTCString(_str), 1));
- setStrDate(_str);
- }
+ dispatch(setFilterKeys(filter.startDate, date));
+ checkEndDate(filter.startDate, date);
+ } else {
+ setIsEndDateError(true);
}
};
+ const checkEndDate = (fromDate, toDate) =>
+ setIsEndDateError(fromDate >= toDate);
const filterByDate = () => {
- setPublicData(filterData(dataArray, fromDate, toDate, datasetName));
- setDateRange(fromDate, toDate);
+ if (filter.startDate) {
+ dispatch(applyFilter());
+ props.setPage(CONSTANTS.START_PAGE_NUMBER);
+ }
};
+
return (
-
- Filter By Date
-
- to
- {
- setStrDate(_str);
- setToDate(bumpToDate(dateFromUTCString(_str), 1));
- }}
- isDisabled={!isValidDate(fromDate)}
- rangeStart={fromDate}
- validators={[toValidator]}
- aria-label="End date"
- placeholder="YYYY-MM-DD"
- />
-
-
+ <>
+
+
+ Filter by date
+
+
+
+
+ to
+
+
+
+
+
+ >
);
};
diff --git a/dashboard/src/modules/components/DatePickerComponent/index.less b/dashboard/src/modules/components/DatePickerComponent/index.less
index db4f99a84f..e2d58a86f8 100644
--- a/dashboard/src/modules/components/DatePickerComponent/index.less
+++ b/dashboard/src/modules/components/DatePickerComponent/index.less
@@ -1,3 +1,8 @@
.filterInputGroup {
margin-left: 10px;
}
+.browsing-page-date-picker {
+ .pf-c-date-picker__helper-text {
+ color: #c9190b;
+ }
+}
diff --git a/dashboard/src/modules/components/PaginationComponent/index.jsx b/dashboard/src/modules/components/PaginationComponent/index.jsx
index 14912ab566..238bf0452c 100644
--- a/dashboard/src/modules/components/PaginationComponent/index.jsx
+++ b/dashboard/src/modules/components/PaginationComponent/index.jsx
@@ -1,28 +1,74 @@
+import * as TYPES from "actions/types";
+
import { Pagination, PaginationVariant } from "@patternfly/react-core";
+import {
+ fetchPublicDatasets,
+ setPageLimit,
+ setPerPage,
+} from "actions/datasetListActions";
+import { useDispatch, useSelector } from "react-redux";
+
+import { OVERFETCH_FACTOR } from "assets/constants/browsingPageConstants";
import React from "react";
-const TablePagination = ({
- numberOfRows,
- page,
- setPage,
- perPage,
- setPerPage,
-}) => {
+const TablePagination = ({ page, setPage }) => {
+ const dispatch = useDispatch();
+
+ const { publicData, perPage } = useSelector((state) => state.datasetlist);
const onSetPage = (_event, pageNumber) => {
setPage(pageNumber);
+
+ fetchData(_event, pageNumber, perPage);
};
- const onPerPageSelect = (_event, perPage) => {
- setPerPage(perPage);
+ const onPerPageSelect = (_event, newPerPage, newPage) => {
+ dispatch(setPageLimit(newPerPage * OVERFETCH_FACTOR));
+
+ dispatch(setPerPage(newPerPage));
+ setPage(newPage);
+
+ fetchData(_event, newPage, newPerPage);
};
+ const fetchData = (_event, newPage, newPerPage = perPage) => {
+ const startIdx = (newPage - 1) * newPerPage;
+ let left = startIdx;
+ let right = startIdx + newPerPage - 1;
+ while (left < right) {
+ if (publicData[left]) {
+ left++;
+ } else {
+ break;
+ }
+ if (publicData[right]) {
+ right--;
+ } else {
+ break;
+ }
+ }
+ if (left !== right) {
+ dispatch({
+ type: TYPES.SET_RESULT_OFFSET,
+ payload: startIdx,
+ });
+ dispatch(fetchPublicDatasets(newPage));
+ }
+ };
+
return (
(
+
+ {firstIndex} - {lastIndex} of {publicData.length}
+
+ )}
>
);
};
diff --git a/dashboard/src/modules/components/TableComponent/common-components.jsx b/dashboard/src/modules/components/TableComponent/common-components.jsx
index 805159b758..4cf2ebfa1b 100644
--- a/dashboard/src/modules/components/TableComponent/common-components.jsx
+++ b/dashboard/src/modules/components/TableComponent/common-components.jsx
@@ -1,5 +1,7 @@
import "./index.less";
+import * as CONSTANTS from "assets/constants/browsingPageConstants";
+
import {
Alert,
AlertActionCloseButton,
@@ -16,9 +18,10 @@ import {
Title,
} from "@patternfly/react-core";
import React, { useState } from "react";
+import { applyFilter, nameFilter } from "actions/datasetListActions";
import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
-import { filterData } from "utils/filterDataset";
+import { useDispatch } from "react-redux";
import { useOutletContext } from "react-router-dom";
export const LoginHint = (props) => {
@@ -66,18 +69,13 @@ export const Heading = (props) => {
);
};
-export const SearchBox = ({
- dataArray,
- setPublicData,
- startDate,
- endDate,
- setDatasetName,
-}) => {
+export const SearchBox = (props) => {
const [searchKey, setSearchKey] = useState("");
-
+ const dispatch = useDispatch();
const search = () => {
- setPublicData(filterData(dataArray, startDate, endDate, searchKey));
- setDatasetName(searchKey);
+ dispatch(nameFilter(searchKey));
+ dispatch(applyFilter());
+ props.setPage(CONSTANTS.START_PAGE_NUMBER);
};
const handleKeyPress = (e) => {
const key = e.key;
@@ -88,13 +86,19 @@ export const SearchBox = ({
return (
handleKeyPress(e)}
- onChange={(e) => setSearchKey(e)}
- >
-
diff --git a/dashboard/src/modules/components/TableComponent/index.jsx b/dashboard/src/modules/components/TableComponent/index.jsx
index e6a745d524..6dcb0cce2d 100644
--- a/dashboard/src/modules/components/TableComponent/index.jsx
+++ b/dashboard/src/modules/components/TableComponent/index.jsx
@@ -1,6 +1,7 @@
import "./index.less";
import * as APP_ROUTES from "utils/routeConstants";
+import * as CONSTANTS from "assets/constants/browsingPageConstants";
import { EmptyTable, Heading, LoginHint, SearchBox } from "./common-components";
import {
@@ -15,26 +16,21 @@ import {
} from "@patternfly/react-table";
import React, { useEffect, useState } from "react";
import { ToggleGroup, ToggleGroupItem } from "@patternfly/react-core";
-import { bumpToDate, getTodayMidnightUTCDate } from "utils/dateFunctions";
import {
fetchPublicDatasets,
getFavoritedDatasets,
updateFavoriteRepoNames,
- updateTblData,
} from "actions/datasetListActions";
import { useDispatch, useSelector } from "react-redux";
import { DATASET_UPLOADED } from "assets/constants/overviewConstants";
import DatePickerWidget from "../DatePickerComponent";
import PathBreadCrumb from "../BreadCrumbComponent";
+import { RenderPagination } from "../OverviewComponent/common-component";
import { TOC } from "assets/constants/navigationConstants";
import TablePagination from "../PaginationComponent";
-import { useNavigate } from "react-router";
import { useKeycloak } from "@react-keycloak/web";
-
-let startDate = new Date(Date.UTC(1990, 10, 4));
-let endDate = bumpToDate(getTodayMidnightUTCDate(), 1);
-let datasetName = "";
+import { useNavigate } from "react-router";
const TableWithFavorite = () => {
const columnNames = {
@@ -46,26 +42,30 @@ const TableWithFavorite = () => {
const [activeSortIndex, setActiveSortIndex] = useState(null);
const [activeSortDirection, setActiveSortDirection] = useState(null);
const [isSelected, setIsSelected] = useState("datasetListButton");
- const [page, setPage] = useState(1);
- const [perPage, setPerPage] = useState(10);
+
const [loginHintVisible, setLoginHintVisible] = useState(true);
+
+ const [favTblperPage, setfavTblPerPage] = useState(
+ CONSTANTS.DEFAULT_PER_PAGE
+ );
+ const [favPage, setFavPage] = useState(CONSTANTS.START_PAGE_NUMBER);
+ const [page, setPage] = useState(CONSTANTS.START_PAGE_NUMBER);
+
const navigate = useNavigate();
const dispatch = useDispatch();
useEffect(() => {
if (Object.keys(endpoints).length > 0) {
- dispatch(fetchPublicDatasets());
+ dispatch(fetchPublicDatasets(CONSTANTS.START_PAGE_NUMBER));
dispatch(getFavoritedDatasets());
}
}, [dispatch, endpoints]);
- const { publicData, favoriteRepoNames, tableData } = useSelector(
+ const { publicData, favoriteRepoNames, perPage } = useSelector(
(state) => state.datasetlist
);
- const setPublicData = (data) => {
- dispatch(updateTblData(data));
- };
+
const markRepoFavorited = (repo, isFavoriting = true) => {
const otherFavorites = favoriteRepoNames.filter(
(r) => r.name !== repo.name
@@ -76,13 +76,17 @@ const TableWithFavorite = () => {
saveFavorites(newFavorite);
dispatch(updateFavoriteRepoNames(newFavorite));
};
- const selectedArray =
+
+ let selectedArray =
isSelected === "datasetListButton"
? publicData?.slice((page - 1) * perPage, page * perPage)
- : favoriteRepoNames?.slice((page - 1) * perPage, page * perPage);
+ : favoriteRepoNames?.slice(
+ (favPage - 1) * favTblperPage,
+ favPage * favTblperPage
+ );
const isRepoFavorited = (repo) =>
- !!favoriteRepoNames.find((element) => element.name === repo.name);
+ !!favoriteRepoNames?.find((element) => element?.name === repo?.name);
const getSortableRowValues = (data) => {
const uploadedDate = data.metadata[DATASET_UPLOADED];
@@ -118,13 +122,7 @@ const TableWithFavorite = () => {
const id = event.currentTarget.id;
setIsSelected(id);
};
- const setDatasetName = (datasetNameValue) => {
- datasetName = datasetNameValue;
- };
- const setDateRange = (startDateValue, endDateValue) => {
- startDate = startDateValue;
- endDate = endDateValue;
- };
+
const saveFavorites = (fav) => {
localStorage.setItem("favorite_datasets", JSON.stringify(fav));
};
@@ -137,6 +135,18 @@ const TableWithFavorite = () => {
{ name: "Results", link: "" },
];
+ /* Favorite Table Pagination */
+ const onSetPage = (_evt, newPage, _perPage, startIdx, endIdx) => {
+ setFavPage(newPage);
+ selectedArray = favoriteRepoNames?.slice(startIdx, endIdx);
+ };
+
+ const onPerPageSelect = (_evt, newPerPage, newPage, startIdx, endIdx) => {
+ setfavTblPerPage(newPerPage);
+ setFavPage(newPage);
+ selectedArray = favoriteRepoNames?.slice(startIdx, endIdx);
+ };
+ /* Favorite Table Pagination*/
return (
<>
{!keycloak.authenticated && loginHintVisible && (
@@ -154,22 +164,11 @@ const TableWithFavorite = () => {
headingTitle="Results"
>
-
-
+
+
+
+
{
{selectedArray.length > 0 ? (
selectedArray.map((repo, rowIndex) => (
-
+
navigate(`${TOC}/${repo.resource_id}`)}
+ onClick={() =>
+ navigate(`${TOC}/${repo?.resource_id}`)
+ }
>
- {repo.name}
+ {repo?.name}
|
- {repo.metadata[DATASET_UPLOADED]}
+ {repo?.metadata[DATASET_UPLOADED]}
|
{
|
))
) : (
-
+
|
@@ -236,17 +237,20 @@ const TableWithFavorite = () => {
)}
-
+ {isSelected === "datasetListButton" ? (
+
+ ) : (
+
+ )}
diff --git a/dashboard/src/modules/components/TableComponent/index.less b/dashboard/src/modules/components/TableComponent/index.less
index 278bd76485..2c73f66cfe 100644
--- a/dashboard/src/modules/components/TableComponent/index.less
+++ b/dashboard/src/modules/components/TableComponent/index.less
@@ -39,3 +39,6 @@ table th {
.searchInputGroup {
width: 25vw !important;
}
+.filter-btn {
+ height: 36px;
+}
diff --git a/dashboard/src/reducers/datasetListReducer.js b/dashboard/src/reducers/datasetListReducer.js
index 33a95b3aaa..5f8540a339 100644
--- a/dashboard/src/reducers/datasetListReducer.js
+++ b/dashboard/src/reducers/datasetListReducer.js
@@ -1,33 +1,57 @@
-import {
- GET_PUBLIC_DATASETS,
- FAVORITED_DATASETS,
- UPDATE_PUBLIC_DATASETS,
-} from "../actions/types";
+import * as CONSTANTS from "assets/constants/browsingPageConstants";
+import * as TYPES from "actions/types";
const initialState = {
publicData: [],
favoriteRepoNames: [],
tableData: [],
+ offset: CONSTANTS.INITIAL_RESULT_OFFSET,
+ limit: CONSTANTS.INITIAL_PAGE_LIMIT,
+ perPage: CONSTANTS.DEFAULT_PER_PAGE,
+ searchKey: "",
+ filter: {
+ startDate: "",
+ endDate: "",
+ },
};
const DatasetListReducer = (state = initialState, action = {}) => {
const { type, payload } = action;
switch (type) {
- case GET_PUBLIC_DATASETS:
+ case TYPES.UPDATE_PUBLIC_DATASETS:
return {
...state,
publicData: [...payload],
- tableData: [...payload],
};
- case FAVORITED_DATASETS:
+ case TYPES.FAVORITED_DATASETS:
return {
...state,
favoriteRepoNames: [...payload],
};
- case UPDATE_PUBLIC_DATASETS:
+ case TYPES.SET_RESULT_OFFSET:
return {
...state,
- publicData: [...payload],
+ offset: payload,
+ };
+ case TYPES.SET_PAGE_LIMIT:
+ return {
+ ...state,
+ limit: payload,
+ };
+ case TYPES.SET_DATE_RANGE:
+ return {
+ ...state,
+ filter: payload,
+ };
+ case TYPES.SET_SEARCH_KEY:
+ return {
+ ...state,
+ searchKey: payload,
+ };
+ case TYPES.SET_PER_PAGE:
+ return {
+ ...state,
+ perPage: payload,
};
default:
return state;