-
-
}>
+
+
+
}>
-
-
- (
-
- )}
- />
-
- (
-
- )}
- />
- (
-
- )}
- />
-
-
+
+
+ } />
+ } />
+ } />
+ } />
+ {/* } /> */}
+ } />
+ } />
+
@@ -91,16 +72,12 @@ const Granules = ({ dispatch, location, queryParams, stats }) => {
};
Granules.propTypes = {
- location: PropTypes.object,
- dispatch: PropTypes.func,
- queryParams: PropTypes.object,
stats: PropTypes.object,
+ urlHelper: PropTypes.shape({
+ location: PropTypes.object,
+ filterQueryParams: PropTypes.func,
+ queryParams: PropTypes.object
+ }),
};
-export default withRouter(
- withQueryParams()(
- connect((state) => ({
- stats: state.stats,
- }))(Granules)
- )
-);
+export default withUrlHelper(Granules);
diff --git a/app/src/js/components/Granules/list.js b/app/src/js/components/Granules/list.js
index 2eda51ce1..85008e658 100644
--- a/app/src/js/components/Granules/list.js
+++ b/app/src/js/components/Granules/list.js
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
+import { useLocation, useNavigate, useParams } from 'react-router-dom';
+import { useDispatch, connect } from 'react-redux';
import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import { withRouter } from 'react-router-dom';
import { get } from 'object-path';
import {
searchGranules,
@@ -49,26 +49,31 @@ const generateBreadcrumbConfig = (view) => [
},
];
-const AllGranules = ({
- collections,
- dispatch,
- granules,
- match,
- queryParams,
- workflowOptions,
- stats,
- providers,
-}) => {
+const AllGranules = (props) => {
+ const {
+ collections,
+ granules,
+ queryParams,
+ workflowOptions,
+ stats,
+ providers,
+ } = props;
+
+ const dispatch = useDispatch();
+ const location = useLocation();
+ const navigate = useNavigate();
+ let { status } = useParams();
+ const searchParams = new URLSearchParams(location.search);
+
const [workflow, setWorkflow] = useState(workflowOptions[0]);
const [workflowMeta, setWorkflowMeta] = useState(defaultWorkflowMeta);
const [selected, setSelected] = useState([]);
+
const { dropdowns } = collections;
const { dropdowns: providerDropdowns } = providers;
const { list } = granules;
const { count, queriedAt } = list.meta;
- let {
- params: { status },
- } = match;
+
status = status === 'processing' ? 'running' : status;
const logsQuery = { granules__exists: 'true', executions__exists: 'true' };
const query = generateQuery();
@@ -96,10 +101,29 @@ const AllGranules = ({
);
}, [dispatch]);
+ useEffect(() => {
+ dispatch(listGranules(query));
+ }, [location, status, dispatch, query]);
+
function generateQuery() {
- const options = { ...queryParams };
+ const options = {};
+ Object.entries(searchParams).forEach(([key, value]) => {
+ options[key] = value;
+ });
options.status = status;
- return options;
+ return { ...options, ...queryParams };
+ }
+
+ function updateUrlParams(params) {
+ const newSearchParams = new URLSearchParams(searchParams);
+ Object.entries(params).forEach(([key, value]) => {
+ if (value === null || value === '') {
+ newSearchParams.delete(key);
+ } else {
+ newSearchParams.set(key, value);
+ }
+ });
+ navigate(`${location.pathname}?${newSearchParams.toString()}`, { replace: true });
}
function generateBulkActions() {
@@ -112,7 +136,7 @@ const AllGranules = ({
return bulkActions(granules, config, selected);
}
- function selectWorkflow(selector, selectedWorkflow) {
+ function selectWorkflow(_selector, selectedWorkflow) {
setWorkflow(selectedWorkflow);
}
@@ -172,6 +196,7 @@ const AllGranules = ({
action={listGranules}
tableColumns={status === 'failed' ? errorTableColumns : tableColumns}
query={query}
+ onUpdateUrlParams={updateUrlParams}
bulkActions={generateBulkActions()}
groupAction={groupAction}
rowId="granuleId"
@@ -242,23 +267,21 @@ const AllGranules = ({
AllGranules.propTypes = {
collections: PropTypes.object,
- dispatch: PropTypes.func,
granules: PropTypes.object,
- match: PropTypes.object,
queryParams: PropTypes.object,
workflowOptions: PropTypes.array,
stats: PropTypes.object,
providers: PropTypes.object,
};
+const mapStateToProps = (state) => ({
+ collections: state.collections,
+ granules: state.granules,
+ stats: state.stats,
+ workflowOptions: workflowOptionNames(state),
+ providers: state.providers,
+});
+
export { AllGranules };
-export default withRouter(
- connect((state) => ({
- collections: state.collections,
- granules: state.granules,
- stats: state.stats,
- workflowOptions: workflowOptionNames(state),
- providers: state.providers,
- }))(AllGranules)
-);
+export default connect(mapStateToProps)(AllGranules);
diff --git a/app/src/js/components/Granules/overview.js b/app/src/js/components/Granules/overview.js
index 0960e5894..0110bc355 100644
--- a/app/src/js/components/Granules/overview.js
+++ b/app/src/js/components/Granules/overview.js
@@ -1,9 +1,7 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
import { Helmet } from 'react-helmet';
import PropTypes from 'prop-types';
-import { withRouter } from 'react-router-dom';
-import { connect } from 'react-redux';
-import withQueryParams from 'react-router-query-params';
import { get } from 'object-path';
import isEqual from 'lodash/isEqual';
import {
@@ -12,7 +10,6 @@ import {
filterGranules,
clearGranulesFilter,
listGranules,
- listWorkflows,
applyWorkflowToGranule,
applyRecoveryWorkflowToGranule,
getOptionsCollectionName,
@@ -38,6 +35,7 @@ import Search from '../Search/search';
import Overview from '../Overview/overview';
import ListFilters from '../ListActions/ListFilters';
import Breadcrumbs from '../Breadcrumbs/Breadcrumbs';
+import { withUrlHelper } from '../../withUrlHelper';
const breadcrumbConfig = [
{
@@ -50,58 +48,50 @@ const breadcrumbConfig = [
},
];
-class GranulesOverview extends React.Component {
- constructor(props) {
- super(props);
- this.generateQuery = this.generateQuery.bind(this);
- this.generateBulkActions = this.generateBulkActions.bind(this);
- this.queryMeta = this.queryMeta.bind(this);
- this.selectWorkflow = this.selectWorkflow.bind(this);
- this.applyWorkflow = this.applyWorkflow.bind(this);
- this.getExecuteOptions = this.getExecuteOptions.bind(this);
- this.setWorkflowMeta = this.setWorkflowMeta.bind(this);
- this.applyRecoveryWorkflow = this.applyRecoveryWorkflow.bind(this);
- this.updateSelection = this.updateSelection.bind(this);
- this.state = {
- workflow: this.props.workflowOptions[0],
- workflowMeta: defaultWorkflowMeta,
- selected: [],
- };
- }
-
- componentDidMount() {
- this.queryMeta();
- }
-
- componentDidUpdate(prevProps) {
- if (!isEqual(prevProps.workflowOptions, this.props.workflowOptions)) {
- this.setState({ workflow: this.props.workflowOptions[0] });
+const GranulesOverview = ({ urlHelper }) => {
+ const dispatch = useDispatch();
+ const { queryParams } = urlHelper;
+
+ const collections = useSelector((state) => state.collections);
+ const config = useSelector((state) => state.config);
+ const granules = useSelector((state) => state.granules);
+ const workflowOptions = useSelector((state) => workflowOptionNames(state));
+ const providers = useSelector((state) => state.providers);
+
+ const [selected, setSelected] = useState([]);
+ const [workflow, setWorkflow] = useState(workflowOptions[0]);
+ const [workflowMeta, setWorkflowMeta] = useState(defaultWorkflowMeta);
+
+ const { list } = granules;
+ const { count, queriedAt } = list.meta;
+ const { dropdowns } = collections;
+ const { dropdowns: providerDropdowns } = providers;
+ const query = generateQuery();
+
+ useEffect(() => {
+ if (!isEqual(workflowOptions[0], workflowOptions)) {
+ setWorkflow(workflowOptions[0]);
}
- }
+ }, [workflowOptions, workflow]);
- queryMeta() {
- const { dispatch } = this.props;
- dispatch(listWorkflows());
- }
-
- generateQuery() {
- const { queryParams } = this.props;
- return { ...queryParams };
+ function generateQuery() {
+ return {
+ ...queryParams,
+ };
}
- generateBulkActions() {
+ function generateBulkActions() {
const actionConfig = {
execute: {
- options: this.getExecuteOptions(),
- action: this.applyWorkflow,
+ options: getExecuteOptions(),
+ action: applyWorkflow,
},
recover: {
- options: this.getExecuteOptions(),
- action: this.applyRecoveryWorkflow,
+ options: getExecuteOptions(),
+ action: applyRecoveryWorkflow,
},
};
- const { granules, config } = this.props;
- const { selected } = this.state;
+
let actions = bulkActions(granules, actionConfig, selected);
if (config.enableRecovery) {
actions = actions.concat(recoverAction(granules, actionConfig));
@@ -109,165 +99,148 @@ class GranulesOverview extends React.Component {
return actions;
}
- selectWorkflow(_selector, workflow) {
- this.setState({ workflow });
- }
-
- setWorkflowMeta(workflowMeta) {
- this.setState({ workflowMeta });
+ function selectWorkflow(selectedWorkflow) {
+ setWorkflow(selectedWorkflow);
}
- applyWorkflow(granuleId) {
- const { workflow, workflowMeta } = this.state;
+ function applyWorkflow(granuleId) {
const { meta } = JSON.parse(workflowMeta);
- this.setState({ workflowMeta: defaultWorkflowMeta });
- return applyWorkflowToGranule(granuleId, workflow, meta);
- }
-
- applyRecoveryWorkflow(granuleId) {
- return applyRecoveryWorkflowToGranule(granuleId);
+ isWorkflowMeta(defaultWorkflowMeta);
+ return dispatch(applyWorkflowToGranule(granuleId, workflow, meta));
}
- getExecuteOptions() {
+ function getExecuteOptions() {
return [
executeDialog({
- selectHandler: this.selectWorkflow,
+ selectHandler: selectWorkflow,
label: 'workflow',
- value: this.state.workflow,
- options: this.props.workflowOptions,
- initialMeta: this.state.workflowMeta,
- metaHandler: this.setWorkflowMeta,
+ value: workflow,
+ options: workflowOptions,
+ initialMeta: workflowMeta,
+ metaHandler: isWorkflowMeta,
}),
];
}
- updateSelection(selectedIds, currentSelectedRows) {
- const allSelectedRows = this.state.selected.concat(currentSelectedRows);
- const selected = selectedIds
- .map((id) => allSelectedRows.find((g) => id === g.granuleId)).filter(Boolean);
- this.setState({ selected });
+ function isWorkflowMeta(meta) {
+ setWorkflowMeta(meta);
}
- render() {
- const { collections, granules, providers } = this.props;
- const { list } = granules;
- const { dropdowns } = collections;
- const { dropdowns: providerDropdowns } = providers;
- const { count, queriedAt } = list.meta;
+ function applyRecoveryWorkflow(granuleId, workflowName) {
+ dispatch(applyRecoveryWorkflowToGranule(granuleId, workflowName));
+ }
- return (
-
-
- Granules Overview
-
-
-
-
-
- Granule Overview
-
- {lastUpdated(queriedAt)}
-
-
-
-
-
-
- {strings.granules}{' '}
-
- {count ? ` ${tally(count)}` : 0}
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
+ function updateSelection(selectedIds) {
+ const allSelectedRows = [...selected, ...list.data];
+ const updatedSelected = selectedIds
+ .map((id) => allSelectedRows.find((g) => id === g.granuleId))
+ .filter(Boolean);
+ setSelected(updatedSelected);
}
-}
+
+ // Not sure what this is supposed to work with even in previous code
+ /* const queryMeta = () => {
+ dispatch(listWorkflows());
+ };
+ */
+
+ return (
+
+
+ Granules Overview
+
+
+
+
+
+ Granule Overview
+
+ {lastUpdated(queriedAt)}
+
+
+
+
+
+
+ {strings.granules}{' '}
+ {count ? ` ${tally(count)}` : 0}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
GranulesOverview.propTypes = {
- collections: PropTypes.object,
- config: PropTypes.object,
- dispatch: PropTypes.func,
- granules: PropTypes.object,
- queryParams: PropTypes.object,
workflowOptions: PropTypes.array,
- providers: PropTypes.object,
+ urlHelper: PropTypes.shape({
+ queryParams: PropTypes.object
+ }),
};
export { GranulesOverview };
-export default withRouter(
- withQueryParams()(
- connect((state) => ({
- collections: state.collections,
- config: state.config,
- granules: state.granules,
- workflowOptions: workflowOptionNames(state),
- providers: state.providers,
- }))(GranulesOverview)
- )
-);
+export default withUrlHelper(GranulesOverview);
diff --git a/app/src/js/components/Header/header.js b/app/src/js/components/Header/header.js
index b235a5797..83870750e 100644
--- a/app/src/js/components/Header/header.js
+++ b/app/src/js/components/Header/header.js
@@ -1,7 +1,7 @@
import React, { useEffect, useCallback, useRef } from 'react';
-import c from 'classnames';
+import classNames from 'classnames';
import PropTypes from 'prop-types';
-import { withRouter, Link } from 'react-router-dom';
+import { Link, useLocation } from 'react-router-dom';
import { connect } from 'react-redux';
import get from 'lodash/get';
import {
@@ -15,6 +15,7 @@ import { window } from '../../utils/browser';
import { strings } from '../locale';
import linkToKibana from '../../utils/kibana';
import { getPersistentQueryParams } from '../../utils/url-helper';
+import withRouter from '../../withRouter';
const paths = [
[strings.collections, '/collections/all'],
@@ -32,12 +33,13 @@ const paths = [
const Header = ({
api,
dispatch,
- location,
minimal,
locationQueryParams,
}) => {
+ const location = useLocation();
const mounted = useRef(true);
+ // Logout action
const handleLogout = useCallback(() => {
dispatch(logout()).then(() => {
if (get(window, 'location.reload')) {
@@ -46,6 +48,7 @@ const Header = ({
});
}, [dispatch]);
+ // Dispatch API information to footer tooltip
useEffect(() => {
if (api.authenticated && mounted.current) {
dispatch(getApiVersion());
@@ -57,11 +60,13 @@ const Header = ({
};
}, [api.authenticated, dispatch]);
- const className = (path) => {
- const active = location.pathname?.slice(0, path.length) === path; // nav issue with router
+ // Navigation
+ const navigation = (path) => {
+ const { pathname } = location.pathname;
+ const active = pathname?.slice(0, path.length) === path;
const menuItem = path.replace('/', '');
const order = `nav__order-${!nav.order.includes(menuItem) ? 2 : nav.order.indexOf(menuItem)}`;
- return c({
+ return classNames({
active,
[order]: true,
});
@@ -100,7 +105,7 @@ const Header = ({
? (
{activePaths.map((path) => (
-
+
{linkTo(path, locationQueryParams.search[path[1]] || locationSearch)}
))}
@@ -129,7 +134,6 @@ const Header = ({
Header.propTypes = {
api: PropTypes.object,
dispatch: PropTypes.func,
- location: PropTypes.object,
minimal: PropTypes.bool,
cumulusInstance: PropTypes.object,
datepicker: PropTypes.object,
diff --git a/app/src/js/components/Operations/index.js b/app/src/js/components/Operations/index.js
index 581da1d27..eb7fec41a 100644
--- a/app/src/js/components/Operations/index.js
+++ b/app/src/js/components/Operations/index.js
@@ -1,44 +1,43 @@
import React from 'react';
-import { withRouter, Route } from 'react-router-dom';
+import { Route, Routes, Navigate } from 'react-router-dom';
+import { useDispatch } from 'react-redux';
import { Helmet } from 'react-helmet';
-import { connect } from 'react-redux';
-import withQueryParams from 'react-router-query-params';
import PropTypes from 'prop-types';
import Sidebar from '../Sidebar/sidebar';
import DatePickerHeader from '../DatePickerHeader/DatePickerHeader';
import OperationOverview from './overview';
import { listOperations } from '../../actions';
import { strings } from '../locale';
-import { filterQueryParams } from '../../utils/url-helper';
+import { withUrlHelper } from '../../withUrlHelper';
-const Operations = ({ dispatch, location, params, queryParams }) => {
- const { pathname } = location;
+const Operations = ({ urlHelper }) => {
+ const dispatch = useDispatch();
+ const { queryParams, filterQueryParams, location, params } = urlHelper;
const filteredQueryParams = filterQueryParams(queryParams);
+ const { pathname } = location;
function query() {
dispatch(listOperations(filteredQueryParams));
}
return (
-
+
Cumulus Operations
-
-
+
+
-
-
(
-
- )}
- />
+
+
+ } />
+ }
+ />
+
@@ -47,10 +46,12 @@ const Operations = ({ dispatch, location, params, queryParams }) => {
};
Operations.propTypes = {
- dispatch: PropTypes.func,
- location: PropTypes.object,
- params: PropTypes.object,
- queryParams: PropTypes.object,
+ urlHelper: PropTypes.shape({
+ location: PropTypes.object,
+ filterQueryParams: PropTypes.func,
+ params: PropTypes.object,
+ queryParams: PropTypes.object
+ }),
};
-export default withRouter(withQueryParams()(connect()(Operations)));
+export default withUrlHelper(Operations);
diff --git a/app/src/js/components/Operations/overview.js b/app/src/js/components/Operations/overview.js
index 721dd9244..d59c55717 100644
--- a/app/src/js/components/Operations/overview.js
+++ b/app/src/js/components/Operations/overview.js
@@ -1,8 +1,7 @@
import React from 'react';
+import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
-import { connect } from 'react-redux';
-import { withRouter } from 'react-router-dom';
import cloneDeep from 'lodash/cloneDeep';
import {
clearOperationsFilter,
@@ -23,6 +22,7 @@ import { tableColumns } from '../../utils/table-config/operations';
import ListFilters from '../ListActions/ListFilters';
import { operationStatus } from '../../utils/status';
import { operationTypes } from '../../utils/type';
+import withRouter from '../../withRouter';
class OperationOverview extends React.Component {
constructor(props) {
diff --git a/app/src/js/components/Overview/overview.js b/app/src/js/components/Overview/overview.js
index 81aff9225..45c186d1c 100644
--- a/app/src/js/components/Overview/overview.js
+++ b/app/src/js/components/Overview/overview.js
@@ -1,33 +1,54 @@
import React, { useEffect } from 'react';
+import { useParams, useLocation } from 'react-router';
+import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
-import withQueryParams from 'react-router-query-params';
-import { connect } from 'react-redux';
import { get } from 'object-path';
import Loading from '../LoadingIndicator/loading-indicator';
import { displayCase, numLargeTooltip } from '../../utils/format';
import { getCount } from '../../actions';
const Overview = ({
- dispatch,
inflight,
params = {},
queryParams,
- stats,
type
}) => {
+ const dispatch = useDispatch();
+ const location = useLocation();
+ const { status } = useParams();
+
+ const stats = useSelector((state) => state.stats);
const statsCount = get(stats, `count.data.${type}.count`, []);
useEffect(() => {
if (!inflight) {
- dispatch(getCount({
+ const searchParams = new URLSearchParams(location.search);
+ const urlParams = Object.fromEntries(searchParams.entries());
+
+ const countParams = {
type,
field: 'status',
...params,
- ...queryParams
- }));
+ ...queryParams,
+ ...urlParams,
+ status // include the status from URL params
+ };
+ dispatch(getCount(countParams));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [dispatch, JSON.stringify(params), JSON.stringify(queryParams), type, inflight]);
+ }, [dispatch, JSON.stringify(params), JSON.stringify(queryParams), type, inflight, location, status]);
+
+ // Loading state check
+ if (inflight) {
+ return
;
+ }
+
+ // Check for empty statsCount
+ if (!statsCount || statsCount.length === 0) {
+ console.log('No stats data available');
+ return null;
+ }
+
return (
{inflight &&
}
@@ -56,4 +77,4 @@ Overview.propTypes = {
type: PropTypes.string,
};
-export default withQueryParams()(connect((state) => ({ stats: state.stats }))(Overview));
+export default Overview;
diff --git a/app/src/js/components/Pdr/index.js b/app/src/js/components/Pdr/index.js
index d4d5565d6..2a4ed9628 100644
--- a/app/src/js/components/Pdr/index.js
+++ b/app/src/js/components/Pdr/index.js
@@ -1,10 +1,9 @@
import React, { useEffect } from 'react';
+import { Route, Routes, Navigate } from 'react-router-dom';
+import { useDispatch, useSelector } from 'react-redux';
import { Helmet } from 'react-helmet';
import PropTypes from 'prop-types';
import { get } from 'object-path';
-import { connect } from 'react-redux';
-import { withRouter, Route, Switch } from 'react-router-dom';
-import withQueryParams from 'react-router-query-params';
import Sidebar from '../Sidebar/sidebar';
import { getCount, listPdrs } from '../../actions';
import DatePickerHeader from '../DatePickerHeader/DatePickerHeader';
@@ -12,12 +11,16 @@ import Pdr from './pdr';
import PdrOverview from './overview';
import PdrList from './list';
import { strings } from '../locale';
-import { filterQueryParams } from '../../utils/url-helper';
+import { withUrlHelper } from '../../withUrlHelper';
-const Pdrs = ({ dispatch, location, queryParams, params, stats }) => {
+const Pdrs = ({ urlHelper }) => {
+ const dispatch = useDispatch();
+ const { queryParams, filterQueryParams, location, params } = urlHelper;
+ const filteredQueryParams = filterQueryParams(queryParams);
const { pathname } = location;
+
+ const stats = useSelector((state) => ({ stats: state.stats }));
const count = get(stats, 'count.sidebar.pdrs.count');
- const filteredQueryParams = filterQueryParams(queryParams);
function query() {
dispatch(listPdrs(filteredQueryParams));
@@ -43,27 +46,21 @@ const Pdrs = ({ dispatch, location, queryParams, params, stats }) => {
-
+
+ } />
(
-
- )}
+ path="all"
+ element={ }
/>
(
-
- )}
+ path="/pdr/:pdrName"
+ element={ }
/>
(
-
- )}
+ path="/:status"
+ element={ }
/>
-
+
@@ -72,17 +69,12 @@ const Pdrs = ({ dispatch, location, queryParams, params, stats }) => {
};
Pdrs.propTypes = {
- dispatch: PropTypes.func,
- location: PropTypes.object,
- params: PropTypes.object,
- queryParams: PropTypes.object,
- stats: PropTypes.object,
+ urlHelper: PropTypes.shape({
+ location: PropTypes.object,
+ filterQueryParams: PropTypes.func,
+ params: PropTypes.object,
+ queryParams: PropTypes.object
+ }),
};
-export default withRouter(
- withQueryParams()(
- connect((state) => ({
- stats: state.stats,
- }))(Pdrs)
- )
-);
+export default withUrlHelper(Pdrs);
diff --git a/app/src/js/components/Pdr/list.js b/app/src/js/components/Pdr/list.js
index 7edeeba37..2c276b44e 100644
--- a/app/src/js/components/Pdr/list.js
+++ b/app/src/js/components/Pdr/list.js
@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
-import { withRouter } from 'react-router-dom';
import {
searchPdrs,
clearPdrsSearch,
@@ -20,6 +19,7 @@ import List from '../Table/Table';
import ListFilters from '../ListActions/ListFilters';
import Breadcrumbs from '../Breadcrumbs/Breadcrumbs';
+import withRouter from '../../withRouter';
const generateBreadcrumbConfig = (view) => [
{
diff --git a/app/src/js/components/Pdr/overview.js b/app/src/js/components/Pdr/overview.js
index 2b8bbf006..bd94a240d 100644
--- a/app/src/js/components/Pdr/overview.js
+++ b/app/src/js/components/Pdr/overview.js
@@ -1,7 +1,7 @@
import React from 'react';
import { Helmet } from 'react-helmet';
import PropTypes from 'prop-types';
-import { withRouter, Link } from 'react-router-dom';
+import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { listPdrs, getCount, clearPdrsFilter, filterPdrs } from '../../actions';
import { lastUpdated, tally } from '../../utils/format';
@@ -14,6 +14,7 @@ import statusOptions from '../../utils/status';
import ListFilters from '../ListActions/ListFilters';
import Breadcrumbs from '../Breadcrumbs/Breadcrumbs';
import { getPersistentQueryParams } from '../../utils/url-helper';
+import withRouter from '../../withRouter';
const breadcrumbConfig = [
{
diff --git a/app/src/js/components/Pdr/pdr.js b/app/src/js/components/Pdr/pdr.js
index d6a45c05f..3abe40795 100644
--- a/app/src/js/components/Pdr/pdr.js
+++ b/app/src/js/components/Pdr/pdr.js
@@ -1,9 +1,9 @@
-import path from 'path';
-import React from 'react';
+import React, { useEffect } from 'react';
+import { Link, useParams, useLocation } from 'react-router-dom';
+import { connect, useDispatch } from 'react-redux';
import { Helmet } from 'react-helmet';
import PropTypes from 'prop-types';
-import { withRouter, Link } from 'react-router-dom';
-import { connect } from 'react-redux';
+import path from 'path';
import { get } from 'object-path';
import {
getPdr,
@@ -39,247 +39,238 @@ import ErrorReport from '../Errors/report';
import GranulesProgress from '../Granules/progress';
import { strings } from '../locale';
import ListFilters from '../ListActions/ListFilters';
-import { getPersistentQueryParams, historyPushWithQueryParams } from '../../utils/url-helper';
+import { withUrlHelper } from '../../withUrlHelper';
-const metaAccessors = [
- {
- label: 'Provider',
- property: 'provider',
- accessor: (d) => (
-
({
- pathname: `/providers/provider/${d}`,
- search: getPersistentQueryParams(location),
- })}
- >
- {d}
-
- ),
- },
- {
- label: strings.collection,
- property: 'collectionId',
- accessor: collectionLink,
- },
- {
- label: 'Execution',
- property: 'execution',
- accessor: (d) => (d
- ? (
-
({
- pathname: `/executions/execution/${path.basename(d)}`,
- search: getPersistentQueryParams(location),
- })}
- >
- link
-
- )
- : (
- nullValue
- )),
- },
- {
- label: 'Status',
- property: 'status',
- accessor: displayCase,
- },
- {
- label: 'Timestamp',
- property: 'timestamp',
- accessor: fullDate,
- },
- {
- label: 'Created at',
- property: 'createdAt',
- accessor: fullDate,
- },
- {
- label: 'Duration',
- property: 'duration',
- accessor: seconds,
- },
- {
- label: 'PAN Sent',
- property: 'PANSent',
- accessor: bool,
- },
- {
- label: 'PAN Message',
- property: 'PANmessage',
- },
-];
+const PDR = ({
+ collections,
+ granules,
+ logs,
+ pdrs,
+ queryParams,
+ urlHelper
+}) => {
+ const dispatch = useDispatch();
+ const location = useLocation();
+ const { historyPushWithQueryParams, getPersistentQueryParams } = urlHelper;
+ const { pdrName } = useParams();
-class PDR extends React.Component {
- constructor() {
- super();
- this.deletePdr = this.deletePdr.bind(this);
- this.generateQuery = this.generateQuery.bind(this);
- this.navigateBack = this.navigateBack.bind(this);
- this.generateBulkActions = this.generateBulkActions.bind(this);
- this.renderProgress = this.renderProgress.bind(this);
- }
-
- componentDidMount() {
- const { dispatch, match } = this.props;
- const { pdrName } = match.params;
+ useEffect(() => {
dispatch(getPdr(pdrName));
- }
+ }, [dispatch, pdrName]);
- deletePdr() {
- const { pdrName } = this.props.match.params;
- this.props.dispatch(deletePdr(pdrName));
- }
+ const handleDeletePdr = () => {
+ dispatch(deletePdr(pdrName));
+ };
- generateQuery() {
- const { queryParams } = this.props;
- const pdrName = get(this.props, ['match', 'params', 'pdrName']);
- return {
- ...queryParams,
- pdrName,
- };
- }
+ const generateQuery = () => ({
+ ...queryParams,
+ pdrName,
+ });
- navigateBack() {
+ const navigateBack = () => {
historyPushWithQueryParams('/pdrs');
- }
+ };
- generateBulkActions() {
- const { granules } = this.props;
- return granuleBulkActions(granules);
- }
+ const generateBulkActions = () => {
+ granuleBulkActions(granules);
+ };
- renderProgress(record) {
- return (
-
- {renderProgress(get(record, 'data', {}))}
-
- );
- }
+ const handleRenderProgress = (record) => {
+
+ {renderProgress(get(record, 'data', {}))}
+
;
+ };
- render() {
- const { match, granules, collections, pdrs } = this.props;
- const { pdrName } = match.params;
- const record = pdrs.map[pdrName];
- if (!record || (record.inflight && !record.data)) return
;
- const { dropdowns } = collections;
- const { list } = granules;
- const { count, queriedAt } = list.meta;
- const deleteStatus = get(pdrs.deleted, [pdrName, 'status']);
- const { error } = record;
+ const record = pdrs.map[pdrName] || {};
+ if (!record || (record.inflight && !record.data)) return
;
- const granulesCount = get(record, 'data.stats', []);
- const granuleStatus = Object.keys(granulesCount).map((key) => ({
- count: granulesCount[key],
- key: (key === 'processing') ? 'running' : key
- }));
+ const { dropdowns } = collections;
+ const { list } = granules;
+ const { count, queriedAt } = list.meta;
+ const deleteStatus = get(pdrs.deleted, [pdrName, 'status']);
+ const { error } = record;
- return (
-
-
- Cumulus PDRs
-
-
-
-
- PDR: {pdrName}
-
-
- {lastUpdated(queriedAt)}
- {this.renderProgress(record)}
- {error &&
}
-
-
+ const granulesCount = get(record, 'data.stats', []);
+ const granuleStatus = Object.keys(granulesCount).map((key) => ({
+ count: granulesCount[key],
+ key: (key === 'processing') ? 'running' : key
+ }));
-
+ const metaAccessors = [
+ {
+ label: 'Provider',
+ property: 'provider',
+ accessor: (d) => (
+
+ {d}
+
+ ),
+ },
+ {
+ label: strings.collection,
+ property: 'collectionId',
+ accessor: collectionLink,
+ },
+ {
+ label: 'Execution',
+ property: 'execution',
+ accessor: (d) => (d
+ ? (
+
+ link
+
+ )
+ : (
+ nullValue
+ )),
+ },
+ {
+ label: 'Status',
+ property: 'status',
+ accessor: displayCase,
+ },
+ {
+ label: 'Timestamp',
+ property: 'timestamp',
+ accessor: fullDate,
+ },
+ {
+ label: 'Created at',
+ property: 'createdAt',
+ accessor: fullDate,
+ },
+ {
+ label: 'Duration',
+ property: 'duration',
+ accessor: seconds,
+ },
+ {
+ label: 'PAN Sent',
+ property: 'PANSent',
+ accessor: bool,
+ },
+ {
+ label: 'PAN Message',
+ property: 'PANmessage',
+ },
+ ];
-
-
-
- {strings.granules}{' '}
-
- {!Number.isNaN(+count) ? `(${count})` : 0}
-
-
-
-
-
-
+ return (
+
+
+ Cumulus PDRs
+
+
+
+
+ PDR: {pdrName}
+
+
+ {lastUpdated(queriedAt)}
+ {handleRenderProgress(record)}
+ {error &&
}
+
+
-
-
+
+
PDR Overview
+
+
+
+
+
+
+
+ {strings.granules}{' '}
+
+ {!Number.isNaN(+count) ? `(${count})` : 0}
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
- );
- }
-}
+
+
+
+
+
+
+ );
+};
PDR.propTypes = {
collections: PropTypes.object,
- dispatch: PropTypes.func,
granules: PropTypes.object,
- history: PropTypes.object,
logs: PropTypes.object,
- match: PropTypes.object,
pdrs: PropTypes.object,
queryParams: PropTypes.object,
+ urlHelper: PropTypes.shape({
+ historyPushWithQueryParams: PropTypes.func,
+ getPersistentQueryParams: PropTypes.func
+ })
};
-export default withRouter(
+export default withUrlHelper(
connect((state) => ({
collections: state.collections,
granules: state.granules,
diff --git a/app/src/js/components/Providers/add.js b/app/src/js/components/Providers/add.js
index a94141329..0b454a45f 100644
--- a/app/src/js/components/Providers/add.js
+++ b/app/src/js/components/Providers/add.js
@@ -1,11 +1,11 @@
import React, { useState } from 'react';
import { Helmet } from 'react-helmet';
import { connect } from 'react-redux';
-import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { createProvider } from '../../actions';
import AddRecord from '../Add/add';
import { isValidProvider } from '../../utils/validate';
+import withRouter from '../../withRouter';
const SCHEMA_KEY = 'provider';
diff --git a/app/src/js/components/Providers/edit.js b/app/src/js/components/Providers/edit.js
index 58e6b4e58..8066ae161 100644
--- a/app/src/js/components/Providers/edit.js
+++ b/app/src/js/components/Providers/edit.js
@@ -2,7 +2,6 @@ import React from 'react';
import { Helmet } from 'react-helmet';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
-import { withRouter } from 'react-router-dom';
import {
getProvider,
updateProvider,
@@ -10,6 +9,7 @@ import {
} from '../../actions';
import EditRecord from '../Edit/edit';
import { isValidProvider } from '../../utils/validate';
+import withRouter from '../../withRouter';
const SCHEMA_KEY = 'provider';
diff --git a/app/src/js/components/Providers/index.js b/app/src/js/components/Providers/index.js
index 6617c675a..64c6ddc55 100644
--- a/app/src/js/components/Providers/index.js
+++ b/app/src/js/components/Providers/index.js
@@ -1,23 +1,20 @@
import React from 'react';
+import { Route, Routes, Navigate } from 'react-router-dom';
import { Helmet } from 'react-helmet';
-import { withRouter, Route, Switch } from 'react-router-dom';
-import withQueryParams from 'react-router-query-params';
import PropTypes from 'prop-types';
import Sidebar from '../Sidebar/sidebar';
import AddProvider from './add';
import EditProvider from './edit';
import ProvidersOverview from './overview';
import ProviderOverview from './provider';
-import { filterQueryParams } from '../../utils/url-helper';
+import { withUrlHelper } from '../../withUrlHelper';
-const Providers = ({
- location,
- params,
- queryParams,
-}) => {
+const Providers = ({ urlHelper }) => {
+ const { queryParams, filterQueryParams, location, params } = urlHelper;
const { pathname } = location;
const showSidebar = pathname !== '/providers/add';
const filteredQueryParams = filterQueryParams(queryParams);
+
return (
@@ -36,24 +33,22 @@ const Providers = ({
showSidebar ? 'page__content--shortened' : 'page__content'
}
>
-
+
+ } />
(
-
- )}
+ path="all"
+ element={ }
/>
-
+ } />
}
/>
}
/>
-
+
@@ -62,9 +57,12 @@ const Providers = ({
};
Providers.propTypes = {
- location: PropTypes.object,
- params: PropTypes.object,
- queryParams: PropTypes.object,
+ urlHelper: PropTypes.shape({
+ location: PropTypes.object,
+ filterQueryParams: PropTypes.func,
+ params: PropTypes.object,
+ queryParams: PropTypes.object
+ }),
};
-export default withRouter(withQueryParams()(Providers));
+export default withUrlHelper(Providers);
diff --git a/app/src/js/components/Providers/overview.js b/app/src/js/components/Providers/overview.js
index 65b6263b9..3eaef8aaa 100644
--- a/app/src/js/components/Providers/overview.js
+++ b/app/src/js/components/Providers/overview.js
@@ -1,7 +1,7 @@
-import React from 'react';
+import React, { useEffect } from 'react';
+import { Link } from 'react-router-dom';
+import { useDispatch, useSelector } from 'react-redux';
import { Helmet } from 'react-helmet';
-import { connect } from 'react-redux';
-import { withRouter, Link } from 'react-router-dom';
import { get } from 'object-path';
import cloneDeep from 'lodash/cloneDeep';
import PropTypes from 'prop-types';
@@ -14,111 +14,95 @@ import {
import { lastUpdated } from '../../utils/format';
import { tableColumns } from '../../utils/table-config/providers';
import List from '../Table/Table';
-import { getPersistentQueryParams } from '../../utils/url-helper';
+import { withUrlHelper } from '../../withUrlHelper';
-class ProvidersOverview extends React.Component {
- constructor() {
- super();
- this.queryStats = this.queryStats.bind(this);
- this.generateQuery = this.generateQuery.bind(this);
- }
+const ProvidersOverview = ({ urlHelper }) => {
+ const dispatch = useDispatch();
+ const { location, queryParams, getPersistentQueryParams } = urlHelper;
- componentDidMount() {
- this.queryStats();
- }
+ const providers = useSelector((state) => state.providers);
+ const stats = useSelector((state) => state.stats);
- queryStats() {
- this.props.dispatch(
- getCount({
- type: 'collections',
- field: 'providers',
- })
- );
- }
-
- generateQuery() {
- const { queryParams } = this.props;
- return { ...queryParams };
- }
+ useEffect(() => {
+ dispatch(getCount({
+ type: 'collections',
+ field: 'providers',
+ }));
+ }, [dispatch]);
- render() {
- const { providers, stats } = this.props;
- const { list } = providers;
- const { count, queriedAt } = list.meta;
+ const generateQuery = () => ({ ...queryParams });
- const bulkActions = [
- {
- Component: (
-
({
- pathname: '/providers/add',
- search: getPersistentQueryParams(location),
- })}
- >
- Add Provider
-
- ),
- },
- ];
+ const { list } = providers;
+ const { count, queriedAt } = list.meta;
- // Incorporate the collection counts into the `list`
- const mutableList = cloneDeep(list);
- const collectionCounts = get(stats.count, 'data.collections.count', []);
+ const mutableList = cloneDeep(list);
+ const collectionCounts = get(stats.count, 'data.collections.count', []);
- mutableList.data.forEach((d) => {
- d.collections = get(
- collectionCounts.find((c) => c.key === d.name),
- 'count',
- 0
- );
- });
- return (
-
-
- Provider
-
-
-
- Provider Overview
-
- {lastUpdated(queriedAt)}
-
-
-
-
- Ingesting Providers
- {count ? `${count}` : 0}
-
-
-
-
-
+ mutableList.data.forEach((d) => {
+ d.collections = get(
+ collectionCounts.find((c) => c.key === d.name),
+ 'count',
+ 0
);
- }
-}
+ });
+
+ const bulkActions = [
+ {
+ Component: (
+
+ Add Provider
+
+ ),
+ },
+ ];
+
+ return (
+
+
+ Provider
+
+
+
+ Provider Overview
+
+ {lastUpdated(queriedAt)}
+
+
+
+
+ Ingesting Providers
+ {count ? `${count}` : 0}
+
+
+
+
+
+ );
+};
ProvidersOverview.propTypes = {
- dispatch: PropTypes.func,
- providers: PropTypes.object,
- queryParams: PropTypes.object,
- stats: PropTypes.object,
+ urlHelper: PropTypes.shape({
+ location: PropTypes.object,
+ queryParams: PropTypes.object,
+ getPersistentQueryParams: PropTypes.func
+ }),
};
-export default withRouter(
- connect((state) => ({
- providers: state.providers,
- stats: state.stats,
- }))(ProvidersOverview)
-);
+export default withUrlHelper(ProvidersOverview);
diff --git a/app/src/js/components/Providers/provider.js b/app/src/js/components/Providers/provider.js
index 4c6196290..6244da623 100644
--- a/app/src/js/components/Providers/provider.js
+++ b/app/src/js/components/Providers/provider.js
@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
+import { Link, useParams } from 'react-router-dom';
+import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import { withRouter, Link } from 'react-router-dom';
import { get } from 'object-path';
import AsyncCommand from '../AsyncCommands/AsyncCommands';
import { getProvider, deleteProvider, listCollections } from '../../actions';
@@ -10,14 +10,17 @@ import Loading from '../LoadingIndicator/loading-indicator';
import LogViewer from '../Logs/viewer';
import ErrorReport from '../Errors/report';
import Metadata from '../Table/Metadata';
-import {
- getPersistentQueryParams,
- historyPushWithQueryParams,
-} from '../../utils/url-helper';
import { metaAccessors } from '../../utils/table-config/providers';
+import { withUrlHelper } from '../../withUrlHelper';
+
+const ProviderOverview = ({ urlHelper }) => {
+ const dispatch = useDispatch();
+ const { providerId } = useParams();
+ const { location, getPersistentQueryParams, historyPushWithQueryParams } = urlHelper;
+
+ const providers = useSelector((state) => state.providers);
+ // const logs = useSelector((state) => state.logs); -- logs was declared in previous class component but not used
-const ProviderOverview = ({ dispatch, match, providers }) => {
- const { providerId } = match.params;
const record = providers.map[providerId];
useEffect(() => {
@@ -110,10 +113,10 @@ const ProviderOverview = ({ dispatch, match, providers }) => {
({
+ to={{
pathname: `/providers/edit/${providerId}`,
search: getPersistentQueryParams(location),
- })}
+ }}
>
Edit
@@ -144,15 +147,13 @@ const ProviderOverview = ({ dispatch, match, providers }) => {
};
ProviderOverview.propTypes = {
- match: PropTypes.object,
- dispatch: PropTypes.func,
providers: PropTypes.object,
- logs: PropTypes.object,
+ // logs: PropTypes.object,
+ urlHelper: PropTypes.shape({
+ location: PropTypes.object,
+ historyPushWithQueryParams: PropTypes.func,
+ getPersistentQueryParams: PropTypes.func
+ }),
};
-export default withRouter(
- connect((state) => ({
- providers: state.providers,
- logs: state.logs,
- }))(ProviderOverview)
-);
+export default withUrlHelper(ProviderOverview);
diff --git a/app/src/js/components/ReconciliationReports/CreateNewReportButton.js b/app/src/js/components/ReconciliationReports/CreateNewReportButton.js
new file mode 100644
index 000000000..adb923704
--- /dev/null
+++ b/app/src/js/components/ReconciliationReports/CreateNewReportButton.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import PropTypes from 'prop-types';
+import LoadingEllipsis from '../LoadingEllipsis/loading-ellipsis';
+
+const CreateNewReportButton = ({ createReportInflight, getPersistentQueryParams }) => {
+ const navigate = useNavigate();
+
+ const handleClick = () => {
+ const search = getPersistentQueryParams(window.location);
+ navigate(`/reconciliation-reports/create${search}`);
+ };
+
+ return (
+
+ {createReportInflight ? : 'Create New Report'}
+
+ );
+};
+
+CreateNewReportButton.propTypes = {
+ createReportInflight: PropTypes.bool,
+ getPersistentQueryParams: PropTypes.func
+};
+
+export default CreateNewReportButton;
diff --git a/app/src/js/components/ReconciliationReports/backup-report.js b/app/src/js/components/ReconciliationReports/backup-report.js
index 88a8e3ca1..1e7028e09 100644
--- a/app/src/js/components/ReconciliationReports/backup-report.js
+++ b/app/src/js/components/ReconciliationReports/backup-report.js
@@ -14,7 +14,7 @@ const BackupReport = ({
filterString,
legend,
onSelect,
- recordData,
+ recordData = {},
reportName,
reportType,
reportUrl
@@ -23,12 +23,14 @@ const BackupReport = ({
reportStartTime = null,
reportEndTime = null,
error = null,
- granules: {
- withConflicts = [],
- onlyInCumulus = [],
- onlyInOrca = []
- }
- } = recordData || {};
+ granules = {}
+ } = recordData;
+
+ const {
+ withConflicts = [],
+ onlyInCumulus = [],
+ onlyInOrca = []
+ } = granules;
let records = withConflicts.map((g) => ({ ...g, conflictType: 'withConflicts' })).concat(
onlyInCumulus.map((g) => ({ ...g, conflictType: 'onlyInCumulus' })),
diff --git a/app/src/js/components/ReconciliationReports/create.js b/app/src/js/components/ReconciliationReports/create.js
index 988372f64..4cb765a2a 100644
--- a/app/src/js/components/ReconciliationReports/create.js
+++ b/app/src/js/components/ReconciliationReports/create.js
@@ -1,10 +1,9 @@
import React, { useEffect } from 'react';
-import PropTypes from 'prop-types';
-import moment from 'moment';
+import { useDispatch, useSelector } from 'react-redux';
import { Helmet } from 'react-helmet';
-import { connect } from 'react-redux';
-import { withRouter } from 'react-router-dom';
import { Form, Field } from 'react-final-form';
+import PropTypes from 'prop-types';
+import moment from 'moment';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import {
@@ -13,7 +12,6 @@ import {
listProviders,
listGranules,
} from '../../actions';
-import { historyPushWithQueryParams } from '../../utils/url-helper';
import { reconciliationReportTypes } from '../../utils/type';
import { dateTimeFormat } from '../../utils/datepicker';
import SimpleDropdown from '../DropDown/simple-dropdown';
@@ -21,6 +19,7 @@ import Tooltip from '../Tooltip/tooltip';
import Datepicker from '../Datepicker/Datepicker';
import TextForm from '../TextAreaForm/text';
import { displayCase, getCollectionId } from '../../utils/format';
+import { withUrlHelper } from '../../withUrlHelper';
const baseRoute = '/reconciliation-reports';
const defaultReportType = 'Inventory';
@@ -65,11 +64,15 @@ function getTooltipInfoFromType(reportType) {
}
const CreateReconciliationReport = ({
- collections,
- dispatch,
- granules,
- providers,
+ urlHelper
}) => {
+ const dispatch = useDispatch();
+ const { historyPushWithQueryParams } = urlHelper;
+
+ const collections = useSelector((state) => state.collections);
+ const granules = useSelector((state) => state.granules);
+ const providers = useSelector((state) => state.providers);
+
const {
list: { data: collectionData },
} = collections;
@@ -353,16 +356,9 @@ const CreateReconciliationReport = ({
};
CreateReconciliationReport.propTypes = {
- collections: PropTypes.object,
- dispatch: PropTypes.func,
- granules: PropTypes.object,
- providers: PropTypes.object,
+ urlHelper: PropTypes.shape({
+ historyPushWithQueryParams: PropTypes.func
+ }),
};
-export default withRouter(
- connect((state) => ({
- collections: state.collections,
- granules: state.granules,
- providers: state.providers,
- }))(CreateReconciliationReport)
-);
+export default withUrlHelper(CreateReconciliationReport);
diff --git a/app/src/js/components/ReconciliationReports/index.js b/app/src/js/components/ReconciliationReports/index.js
index e1d0619af..02893a73f 100644
--- a/app/src/js/components/ReconciliationReports/index.js
+++ b/app/src/js/components/ReconciliationReports/index.js
@@ -1,9 +1,8 @@
import React from 'react';
+import { Route, Routes, Navigate } from 'react-router-dom';
+import { useDispatch } from 'react-redux';
import { Helmet } from 'react-helmet';
import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import { withRouter, Route, Switch } from 'react-router-dom';
-import withQueryParams from 'react-router-query-params';
import Sidebar from '../Sidebar/sidebar';
import { strings } from '../locale';
import { getCount, listReconciliationReports } from '../../actions';
@@ -12,17 +11,14 @@ import ReconciliationReportList from './list';
import ReconciliationReport from './reconciliation-report';
import BackupReportGranuleDetails from './backup-report-granule-details';
import DatePickerHeader from '../DatePickerHeader/DatePickerHeader';
-import { filterQueryParams } from '../../utils/url-helper';
+import { withUrlHelper } from '../../withUrlHelper';
-const ReconciliationReports = ({
- dispatch,
- location,
- params,
- queryParams,
-}) => {
+const ReconciliationReports = ({ urlHelper }) => {
+ const dispatch = useDispatch();
+ const { queryParams, filterQueryParams, location, params } = urlHelper;
+ const filteredQueryParams = filterQueryParams(queryParams);
const { pathname } = location;
const showSidebar = pathname !== '/reconciliation-reports/create';
- const filteredQueryParams = filterQueryParams(queryParams);
function query() {
dispatch(listReconciliationReports(filteredQueryParams));
@@ -48,12 +44,13 @@ const ReconciliationReports = ({
showSidebar ? 'page__content--shortened' : 'page__content'
}
>
-
- } />
-
-
-
-
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
@@ -62,12 +59,14 @@ const ReconciliationReports = ({
};
ReconciliationReports.propTypes = {
- dispatch: PropTypes.func,
- location: PropTypes.object,
- params: PropTypes.object,
- queryParams: PropTypes.object,
+ urlHelper: PropTypes.shape({
+ location: PropTypes.object,
+ filterQueryParams: PropTypes.func,
+ params: PropTypes.object,
+ queryParams: PropTypes.object
+ }),
};
ReconciliationReports.displayName = 'Reconciliation Reports';
-export default withRouter(withQueryParams()(connect()(ReconciliationReports)));
+export default withUrlHelper(ReconciliationReports);
diff --git a/app/src/js/components/ReconciliationReports/list.js b/app/src/js/components/ReconciliationReports/list.js
index ccfb35799..e5dc64383 100644
--- a/app/src/js/components/ReconciliationReports/list.js
+++ b/app/src/js/components/ReconciliationReports/list.js
@@ -1,7 +1,6 @@
import React from 'react';
+import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import { Link, withRouter } from 'react-router-dom';
import {
searchReconciliationReports,
clearReconciliationReportSearch,
@@ -12,17 +11,17 @@ import {
import { lastUpdated } from '../../utils/format';
import { reconciliationReportStatus as statusOptions } from '../../utils/status';
import { reconciliationReportTypes as reportTypeOptions } from '../../utils/type';
-import { getPersistentQueryParams } from '../../utils/url-helper';
import {
tableColumns,
bulkActions,
} from '../../utils/table-config/reconciliation-reports';
-import LoadingEllipsis from '../LoadingEllipsis/loading-ellipsis';
import Dropdown from '../DropDown/dropdown';
import Search from '../Search/search';
import List from '../Table/Table';
import ListFilters from '../ListActions/ListFilters';
import Breadcrumbs from '../Breadcrumbs/Breadcrumbs';
+import { withUrlHelper } from '../../withUrlHelper';
+import CreateNewReportButton from './CreateNewReportButton';
const breadcrumbConfig = [
{
@@ -50,18 +49,17 @@ const granuleBreadcrumbConfig = [
},
];
-const ReconciliationReportList = ({
- dispatch,
- location,
- queryParams,
- reconciliationReports,
-}) => {
+const ReconciliationReportList = ({ urlHelper }) => {
+ const dispatch = useDispatch();
+ const { getPersistentQueryParams, location, queryParams } = urlHelper;
const { pathname } = location;
const isGranules = pathname.includes('granules');
+ const reconciliationReports = useSelector((state) => state.reconciliationReports);
const { list } = reconciliationReports;
const { queriedAt, count } = list.meta;
const query = generateQuery();
const tableColumnsArray = tableColumns({ dispatch, isGranules, query });
+ const { createReportInflight } = reconciliationReports;
function generateQuery() {
return {
@@ -87,21 +85,10 @@ const ReconciliationReportList = ({
{isGranules ? 'Lists' : 'Reconciliation Reports Overview'}
{!isGranules && (
- ({
- pathname: '/reconciliation-reports/create',
- search: getPersistentQueryParams(routerLocation),
- })}
- >
- {reconciliationReports.createReportInflight
- ? (
-
- )
- : (
- 'Create New Report'
- )}
-
+
)}
{lastUpdated(queriedAt)}
@@ -166,14 +153,11 @@ const ReconciliationReportList = ({
};
ReconciliationReportList.propTypes = {
- dispatch: PropTypes.func,
- location: PropTypes.object,
- queryParams: PropTypes.object,
- reconciliationReports: PropTypes.object,
+ urlHelper: PropTypes.shape({
+ location: PropTypes.object,
+ getPersistentQueryParams: PropTypes.func,
+ queryParams: PropTypes.object
+ }),
};
-export default withRouter(
- connect((state) => ({
- reconciliationReports: state.reconciliationReports,
- }))(ReconciliationReportList)
-);
+export default withUrlHelper(ReconciliationReportList);
diff --git a/app/src/js/components/ReconciliationReports/reconciliation-report.js b/app/src/js/components/ReconciliationReports/reconciliation-report.js
index f31de5ebd..dfee6012d 100644
--- a/app/src/js/components/ReconciliationReports/reconciliation-report.js
+++ b/app/src/js/components/ReconciliationReports/reconciliation-report.js
@@ -1,7 +1,7 @@
-import PropTypes from 'prop-types';
import React, { useEffect } from 'react';
-import { connect } from 'react-redux';
-import { withRouter } from 'react-router-dom';
+import { connect, useDispatch } from 'react-redux';
+import { useParams } from 'react-router-dom';
+import PropTypes from 'prop-types';
import get from 'lodash/get';
import {
getReconciliationReport,
@@ -14,11 +14,10 @@ import Legend from './legend';
import BackupReport from './backup-report';
const ReconciliationReport = ({
- dispatch = {},
- match,
reconciliationReports = [],
}) => {
- const { reconciliationReportName: encodedReportName } = match.params;
+ const dispatch = useDispatch();
+ const { reconciliationReportName: encodedReportName } = useParams();
const reconciliationReportName = decodeURIComponent(encodedReportName);
const { list, map, searchString: filterString } = reconciliationReports;
const record = map[reconciliationReportName];
@@ -84,14 +83,13 @@ const ReconciliationReport = ({
};
ReconciliationReport.propTypes = {
- dispatch: PropTypes.func,
- match: PropTypes.object,
reconciliationReports: PropTypes.object,
};
+const mapStateToProps = (state) => ({
+ reconciliationReports: state.reconciliationReports,
+});
+
export { ReconciliationReport };
-export default withRouter(
- connect((state) => ({
- reconciliationReports: state.reconciliationReports,
- }))(ReconciliationReport)
-);
+
+export default connect(mapStateToProps)(ReconciliationReport);
diff --git a/app/src/js/components/Rules/add.js b/app/src/js/components/Rules/add.js
index ad603862b..50b15fd68 100644
--- a/app/src/js/components/Rules/add.js
+++ b/app/src/js/components/Rules/add.js
@@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
-import { withRouter } from 'react-router-dom';
import {
createRule,
listWorkflows,
@@ -11,6 +10,7 @@ import {
} from '../../actions';
import AddRecord from '../Add/add';
import { getCollectionId, nullValue, collectionNameVersion } from '../../utils/format';
+import withRouter from '../../withRouter';
/**
* Converts the Collection ID string associated with the `collection` property
diff --git a/app/src/js/components/Rules/edit.js b/app/src/js/components/Rules/edit.js
index 5ea85c691..983958b9e 100644
--- a/app/src/js/components/Rules/edit.js
+++ b/app/src/js/components/Rules/edit.js
@@ -1,8 +1,8 @@
import React from 'react';
+import { useSelector } from 'react-redux';
import { Helmet } from 'react-helmet';
import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import { withRouter } from 'react-router-dom';
+
import {
getRule,
updateRule,
@@ -10,14 +10,15 @@ import {
} from '../../actions';
import EditRaw from '../EditRaw/edit-raw';
+import { withRouter } from '../../withRouter';
const SCHEMA_KEY = 'rule';
const EditRule = ({
- match,
- rules
+ router
}) => {
- const { params: { ruleName } } = match;
+ const { params: { ruleName } } = router;
+ const rules = useSelector((state) => state.rules);
return (
@@ -31,7 +32,7 @@ const EditRule = ({
state={rules}
getRecord={() => getRule(ruleName)}
updateRecord={updateRule}
- backRoute={`/rules/rule/${ruleName}`}
+ backRoute={`rule/${ruleName}`}
clearRecordUpdate={clearUpdateRule}
hasModal={true}
/>
@@ -40,10 +41,11 @@ const EditRule = ({
};
EditRule.propTypes = {
- match: PropTypes.object,
- rules: PropTypes.object
+ router: PropTypes.shape({
+ rules: PropTypes.object,
+ params: PropTypes.object,
+ }),
+
};
-export default withRouter(connect((state) => ({
- rules: state.rules
-}))(EditRule));
+export default withRouter(EditRule);
diff --git a/app/src/js/components/Rules/index.js b/app/src/js/components/Rules/index.js
index 56c1adb41..0c160c261 100644
--- a/app/src/js/components/Rules/index.js
+++ b/app/src/js/components/Rules/index.js
@@ -1,23 +1,20 @@
import React from 'react';
+import { Route, Routes, Navigate } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import PropTypes from 'prop-types';
-import { withRouter, Route, Switch } from 'react-router-dom';
-import withQueryParams from 'react-router-query-params';
import Sidebar from '../Sidebar/sidebar';
import RulesOverview from './overview';
import Rule from './rule';
import EditRule from './edit';
import AddRule from './add';
-import { filterQueryParams } from '../../utils/url-helper';
+import { withUrlHelper } from '../../withUrlHelper';
-const Rules = ({
- location,
- params,
- queryParams,
-}) => {
+const Rules = ({ urlHelper }) => {
+ const { queryParams, filterQueryParams, location, params } = urlHelper;
+ const filteredQueryParams = filterQueryParams(queryParams);
const { pathname } = location;
const showSidebar = pathname !== '/rules/add';
- const filteredQueryParams = filterQueryParams(queryParams);
+
return (
@@ -37,12 +34,13 @@ const Rules = ({
/>
)}
-
- } />
-
-
-
-
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
@@ -51,9 +49,12 @@ const Rules = ({
};
Rules.propTypes = {
- location: PropTypes.object,
- params: PropTypes.object,
- queryParams: PropTypes.object,
+ urlHelper: PropTypes.shape({
+ location: PropTypes.object,
+ filterQueryParams: PropTypes.func,
+ params: PropTypes.object,
+ queryParams: PropTypes.object
+ }),
};
-export default withRouter(withQueryParams()(Rules));
+export default withUrlHelper(Rules);
diff --git a/app/src/js/components/Rules/overview.js b/app/src/js/components/Rules/overview.js
index 912a8aa4e..73a661e88 100644
--- a/app/src/js/components/Rules/overview.js
+++ b/app/src/js/components/Rules/overview.js
@@ -1,20 +1,30 @@
-import React from 'react';
+import React, { useEffect, useCallback } from 'react';
+import { Link, useLocation } from 'react-router-dom';
+import { useSelector, useDispatch } from 'react-redux';
import { Helmet } from 'react-helmet';
import PropTypes from 'prop-types';
-import { withRouter } from 'react-router-dom';
-import { connect } from 'react-redux';
import {
+ enableRule,
+ disableRule,
+ deleteRule,
listRules,
searchRules,
clearRulesSearch,
filterRules,
clearRulesFilter,
} from '../../actions';
-import { lastUpdated, tally } from '../../utils/format';
+import {
+ lastUpdated,
+ tally,
+ enableRules,
+ disableRules,
+ deleteRules
+} from '../../utils/format';
import List from '../Table/Table';
import Search from '../Search/search';
-import { tableColumns, bulkActions } from '../../utils/table-config/rules';
+import { tableColumns } from '../../utils/table-config/rules';
import Breadcrumbs from '../Breadcrumbs/Breadcrumbs';
+import { withUrlHelper } from '../../withUrlHelper';
const breadcrumbConfig = [
{
@@ -27,86 +37,128 @@ const breadcrumbConfig = [
},
];
-class RulesOverview extends React.Component {
- constructor() {
- super();
- this.generateBulkActions = this.generateBulkActions.bind(this);
- }
+const RulesOverview = ({ urlHelper }) => {
+ const dispatch = useDispatch();
+ const location = useLocation();
+ const { queryParams, getPersistentQueryParams } = urlHelper;
+
+ const rules = useSelector((state) => state.rules);
+ const { list } = rules || {};
+ const { count, queriedAt } = list?.meta || {};
+
+ const removeEsFields = (data) => {
+ const { queriedAt: _, timestamp, stats, ...nonEsFields } = data;
+ return nonEsFields;
+ };
- componentDidMount() {
- this.props.dispatch(listRules);
- }
+ useEffect(() => {
+ dispatch(listRules());
+ }, [dispatch]);
- generateBulkActions() {
- const { rules } = this.props;
- return bulkActions(rules);
- }
+ const generateBulkActions = useCallback(() => {
+ if (!rules || !rules.list || !rules.list.data) {
+ return [];
+ }
- render() {
- const { queryParams, rules } = this.props;
- const { list } = rules;
- const { count, queriedAt } = list.meta;
- return (
-
-
- Rules Overview
-
-
-
-
-
- Rules Overview
-
- {lastUpdated(queriedAt)}
-
-
-
-
-
- All Rules
-
- {count ? ` ${tally(count)}` : 0}
-
-
-
+ const rulesOps = {
+ list: rules.list,
+ enabled: rules.enabled || {},
+ disabled: rules.disabled || {},
+ deleted: rules.deleted || {}
+ };
-
-
-
-
-
- );
- }
-}
+ return [
+ {
+ text: 'Enable Rule',
+ action: (ruleName) => {
+ const rule = rules.list.data.find((ruleData) => ruleData.name === ruleName);
+ const filteredRule = removeEsFields(rule);
+ return enableRule(filteredRule);
+ },
+ state: rulesOps.enabled,
+ confirm: (d) => enableRules(d),
+ className: 'button button--green button--enable button--small form-group__element'
+ }, {
+ text: 'Disable Rule',
+ action: (ruleName) => {
+ const rule = rules.list.data.find((ruleData) => ruleData.name === ruleName);
+ const filteredRule = removeEsFields(rule);
+ return disableRule(filteredRule);
+ },
+ state: rulesOps.disabled,
+ confirm: (d) => disableRules(d),
+ className: 'button button--green button--disable button--small form-group__element'
+ },
+ {
+ Component:
Add Rule
+ },
+ {
+ text: 'Delete Rule',
+ action: deleteRule,
+ state: rulesOps.deleted,
+ confirm: (d) => deleteRules(d),
+ className: 'button button--delete button--small form-group__element'
+ }
+ ];
+ }, [rules, location, getPersistentQueryParams]);
+
+ return (
+
+
+ Rules Overview
+
+
+
+
+
+ Rules Overview
+
+ {lastUpdated(queriedAt)}
+
+
+
+
+
+ All Rules
+
+ {count ? ` ${tally(count)}` : 0}
+
+
+
+
+
+
+
+
+
+ );
+};
RulesOverview.propTypes = {
- dispatch: PropTypes.func,
- queryParams: PropTypes.object,
- rules: PropTypes.object,
+ urlHelper: PropTypes.shape({
+ queryParams: PropTypes.object,
+ getPersistentQueryParams: PropTypes.func
+ }),
};
-export default withRouter(
- connect((state) => ({
- rules: state.rules,
- }))(RulesOverview)
-);
+export default withUrlHelper(RulesOverview);
diff --git a/app/src/js/components/Rules/rule.js b/app/src/js/components/Rules/rule.js
index 43d8f4136..5532fa2ec 100644
--- a/app/src/js/components/Rules/rule.js
+++ b/app/src/js/components/Rules/rule.js
@@ -1,7 +1,7 @@
-import React from 'react';
-import { connect } from 'react-redux';
+import React, { useEffect, useMemo, useCallback } from 'react';
+import { Link, useParams, useLocation } from 'react-router-dom';
+import { useSelector, useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
-import { withRouter, Link } from 'react-router-dom';
import { get } from 'object-path';
import {
displayCase,
@@ -14,6 +14,8 @@ import {
disableText,
deleteText,
rerunText,
+ collectionLink,
+ getCollectionId,
} from '../../utils/format';
import {
getRule,
@@ -27,7 +29,7 @@ import Metadata from '../Table/Metadata';
import DropdownAsync from '../DropDown/dropdown-async-command';
import ErrorReport from '../Errors/report';
import Breadcrumbs from '../Breadcrumbs/Breadcrumbs';
-import { getPersistentQueryParams, historyPushWithQueryParams } from '../../utils/url-helper';
+import { withUrlHelper } from '../../withUrlHelper';
const breadcrumbConfig = [
{
@@ -44,190 +46,166 @@ const breadcrumbConfig = [
}
];
-const metaAccessors = [
- { label: 'Rule Name', property: 'name' },
- { label: 'Timestamp', property: 'updatedAt', accessor: fullDate },
- { label: 'Workflow', property: 'workflow' },
- { label: 'Provider', property: 'provider', accessor: providerLink },
- { label: 'Provider Path', property: 'provider_path' },
- { label: 'Rule Type', property: 'rule.type' },
- /* Why was this commented out? */
- // PGC { label: 'Collection', property: 'collection', accessor: d => collectionLink(getCollectionId(d)) },
-];
-
-class Rule extends React.Component {
- constructor () {
- super();
- this.load = this.load.bind(this);
- this.delete = this.delete.bind(this);
- this.enable = this.enable.bind(this);
- this.disable = this.disable.bind(this);
- this.rerun = this.rerun.bind(this);
- this.navigateBack = this.navigateBack.bind(this);
- this.reload = this.reload.bind(this);
- this.errors = this.errors.bind(this);
- }
+const Rule = ({ urlHelper }) => {
+ const dispatch = useDispatch();
+ const location = useLocation();
+ const { getPersistentQueryParams, historyPushWithQueryParams } = urlHelper;
+ const { ruleName } = useParams();
- componentDidMount () {
- this.load(this.props.match.params.ruleName);
- }
+ const rules = useSelector((state) => state.rules);
- componentDidUpdate (prevProps) {
- if (this.props.match.params.ruleName !== prevProps.match.params.ruleName) {
- this.load(this.props.match.params.ruleName);
- }
- }
+ const load = useCallback(() => {
+ dispatch(getRule(ruleName));
+ }, [dispatch, ruleName]);
- load (ruleName) {
- this.props.dispatch(getRule(ruleName));
- }
+ useEffect(() => {
+ load();
+ }, [ruleName, dispatch, load]);
- delete () {
- const { ruleName } = this.props.match.params;
- this.props.dispatch(deleteRule(ruleName));
- }
+ const deletes = () => {
+ dispatch(deleteRule(ruleName));
+ };
- enable () {
- const { ruleName } = this.props.match.params;
- this.props.dispatch(enableRule(this.props.rules.map[ruleName].data));
- }
+ const enable = () => {
+ dispatch(enableRule(rules.map[ruleName].data));
+ };
- disable () {
- const { ruleName } = this.props.match.params;
- this.props.dispatch(disableRule(this.props.rules.map[ruleName].data));
- }
+ const disable = () => {
+ dispatch(disableRule(rules.map[ruleName].data));
+ };
- rerun () {
- const { ruleName } = this.props.match.params;
- this.props.dispatch(rerunRule(this.props.rules.map[ruleName].data));
- }
+ const rerun = () => {
+ dispatch(rerunRule(rules.map[ruleName].data));
+ };
- navigateBack () {
+ const navigateBack = () => {
historyPushWithQueryParams('/rules');
- }
+ };
- reload () {
- const { ruleName } = this.props.match.params;
- this.load(ruleName);
- }
+ const reload = () => {
+ load(ruleName);
+ };
- errors () {
- const { ruleName } = this.props.match.params;
- const { rules } = this.props;
- return [
- get(rules.map, [ruleName, 'error']),
- get(rules.deleted, [ruleName, 'error']),
- get(rules.enabled, [ruleName, 'error']),
- get(rules.disabled, [ruleName, 'error']),
- get(rules.rerun, [ruleName, 'error'])
- ].filter(Boolean);
- }
+ const errors = useMemo(() => [
+ get(rules.map, [ruleName, 'error']),
+ get(rules.deleted, [ruleName, 'error']),
+ get(rules.enabled, [ruleName, 'error']),
+ get(rules.disabled, [ruleName, 'error']),
+ get(rules.rerun, [ruleName, 'error'])
+ ].filter(Boolean), [rules, ruleName]);
+
+ const record = rules.map[ruleName];
- render () {
- const { match: { params: { ruleName } }, rules } = this.props;
- const record = rules.map[ruleName];
-
- if (!record || (record.inflight && !record.data)) {
- return
;
- }
- const data = get(record, 'data', {});
-
- const deleteStatus = get(rules, `deleted.${ruleName}.status`);
- const enabledStatus = get(rules, `enabled.${ruleName}.status`);
- const disabledStatus = get(rules, `disabled.${ruleName}.status`);
- const rerunStatus = get(rules, `rerun.${ruleName}.status`);
- const dropdownConfig = [{
- text: 'Enable',
- action: this.enable,
- disabled: data.type === 'onetime',
- status: enabledStatus,
- confirmAction: true,
- confirmText: enableText(ruleName),
- postActionModal: true,
- postActionText: enableConfirm(ruleName),
- success: this.reload
- }, {
- text: 'Disable',
- action: this.disable,
- disabled: data.type === 'onetime',
- status: disabledStatus,
- confirmAction: true,
- postActionModal: true,
- confirmText: disableText(ruleName),
- postActionText: disableConfirm(ruleName),
- success: this.reload
- }, {
- text: 'Delete',
- action: this.delete,
- status: deleteStatus,
- success: this.navigateBack,
- confirmAction: true,
- confirmText: deleteText(ruleName)
- }, {
- text: 'Rerun',
- action: this.rerun,
- status: rerunStatus,
- success: this.reload,
- confirmAction: true,
- confirmText: rerunText(ruleName)
- }];
-
- const errors = this.errors();
- return (
-
-
-
-
-
Rule: {ruleName}
-
-
- ({
- pathname: '/rules/add',
- search: getPersistentQueryParams(location),
- state: {
- name: ruleName
- }
- })}>Copy Rule
- ({ pathname: `/rules/edit/${ruleName}`, search: getPersistentQueryParams(location) })}>Edit Rule
- {lastUpdated(data.timestamp || data.updatedAt)}
-
-
-
- {errors.length > 0 && }
-
-
Rule Overview
-
-
- {data.state && (
-
- State:
- {displayCase(data.state)}
-
- )}
-
-
-
-
-
-
- );
+ if (!record || (record.inflight && !record.data)) {
+ return
;
}
-}
+
+ const data = get(record, 'data', {});
+
+ const deleteStatus = get(rules, `deleted.${ruleName}.status`);
+ const enabledStatus = get(rules, `enabled.${ruleName}.status`);
+ const disabledStatus = get(rules, `disabled.${ruleName}.status`);
+ const rerunStatus = get(rules, `rerun.${ruleName}.status`);
+
+ const dropdownConfig = [{
+ text: 'Enable',
+ action: enable,
+ disabled: data.type === 'onetime',
+ status: enabledStatus,
+ confirmAction: true,
+ confirmText: enableText(ruleName),
+ postActionModal: true,
+ postActionText: enableConfirm(ruleName),
+ success: reload
+ }, {
+ text: 'Disable',
+ action: disable,
+ disabled: data.type === 'onetime',
+ status: disabledStatus,
+ confirmAction: true,
+ postActionModal: true,
+ confirmText: disableText(ruleName),
+ postActionText: disableConfirm(ruleName),
+ success: reload
+ }, {
+ text: 'Delete',
+ action: deletes,
+ status: deleteStatus,
+ success: navigateBack,
+ confirmAction: true,
+ confirmText: deleteText(ruleName)
+ }, {
+ text: 'Rerun',
+ action: rerun,
+ status: rerunStatus,
+ success: reload,
+ confirmAction: true,
+ confirmText: rerunText(ruleName)
+ }];
+
+ const metaAccessors = [
+ { label: 'Rule Name', property: 'name' },
+ { label: 'Timestamp', property: 'updatedAt', accessor: fullDate },
+ { label: 'Workflow', property: 'workflow' },
+ { label: 'Provider', property: 'provider', accessor: providerLink },
+ { label: 'Provider Path', property: 'provider_path' },
+ { label: 'Rule Type', property: 'rule.type' },
+ { label: 'Collection', property: 'collection', accessor: (value) => collectionLink(getCollectionId(value)) }
+ ];
+
+ return (
+
+
+
+
+
Rule: {ruleName}
+
+
+ Copy Rule
+ Edit Rule
+ {lastUpdated(data.timestamp || data.updatedAt)}
+
+
+
+ {errors.length > 0 && }
+
+
Rule Overview
+
+
+ {data.state && (
+
+ State:
+ {displayCase(data.state)}
+
+ )}
+
+
+
+
+
+
+ );
+};
Rule.propTypes = {
- match: PropTypes.object,
- dispatch: PropTypes.func,
- rules: PropTypes.object
+ urlHelper: PropTypes.shape({
+ getPersistentQueryParams: PropTypes.func,
+ historyPushWithQueryParams: PropTypes.func,
+ }),
};
-export default withRouter(connect((state) => ({
- rules: state.rules
-}))(Rule));
+export default withUrlHelper(Rule);
diff --git a/app/src/js/components/Search/search.js b/app/src/js/components/Search/search.js
index 2c455fde1..2bde71698 100644
--- a/app/src/js/components/Search/search.js
+++ b/app/src/js/components/Search/search.js
@@ -1,15 +1,13 @@
-import React, { createRef, useCallback, useEffect, useRef } from 'react';
-import { connect } from 'react-redux';
+import React, { useCallback, useEffect, useRef } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
-import withQueryParams from 'react-router-query-params';
-import { withRouter } from 'react-router-dom';
import { AsyncTypeahead } from 'react-bootstrap-typeahead';
import { get } from 'object-path';
-import { getInitialValueFromLocation } from '../../utils/url-helper';
import {
renderSearchInput,
renderSearchMenu,
} from '../../utils/typeahead-helpers';
+import { withUrlHelper } from '../../withUrlHelper';
/**
* Search
@@ -23,29 +21,30 @@ import {
const Search = ({
action,
clear,
- dispatch,
inputProps = {
className: 'search',
},
label,
labelKey,
- location,
options,
paramKey = 'search',
placeholder,
- queryParams,
searchKey = '',
+ urlHelper,
setQueryParams,
...rest
}) => {
- const searchRef = createRef();
- const formID = `form-${label}-${paramKey}`;
- const initialValueRef = useRef(getInitialValueFromLocation({
+ const dispatch = useDispatch();
+ const searchRef = useRef();
+ const {
location,
- paramKey,
queryParams,
- }));
- const searchList = get(rest[searchKey], 'list');
+ getInitialValueFromLocation,
+ historyPushWithQueryParams,
+ } = urlHelper;
+ const formID = `form-${label}-${paramKey}`;
+ const initialValueRef = getInitialValueFromLocation(paramKey);
+ const searchList = useSelector((state) => get(state, `${searchKey}.list`));
const { data: searchOptions, inflight = false } = searchList || {};
useEffect(() => {
@@ -56,30 +55,43 @@ const Search = ({
if (initialValueRef.current) {
dispatch(action(initialValueRef.current));
}
- }, [action, dispatch]);
+ }, [action, dispatch, initialValueRef]);
+
+ // Handle location changes (browser back/forward)
+ useEffect(() => {
+ const searchValue = queryParams[paramKey];
+ if (searchValue) {
+ dispatch(action(searchValue));
+ } else {
+ dispatch(clear());
+ }
+ }, [location, queryParams, paramKey, action, clear, dispatch]);
- const handleSearch = useCallback((query) => {
- if (query) dispatch(action(query));
- else dispatch(clear);
- }, [action, clear, dispatch]);
+ const handleSearch = useCallback(
+ (query) => {
+ if (query) dispatch(action(query));
+ else dispatch(clear);
+ },
+ [action, clear, dispatch]
+ );
function handleChange(selections) {
if (selections && selections.length > 0) {
const query = selections[0][labelKey];
dispatch(action(query));
- setQueryParams({ [paramKey]: query });
+ historyPushWithQueryParams({ [paramKey]: query });
} else {
dispatch(clear());
- setQueryParams({ [paramKey]: undefined });
+ historyPushWithQueryParams({ [paramKey]: undefined });
}
}
function handleInputChange(text) {
if (text) {
- setQueryParams({ [paramKey]: text });
+ historyPushWithQueryParams({ [paramKey]: text });
} else {
dispatch(clear());
- setQueryParams({ [paramKey]: undefined });
+ historyPushWithQueryParams({ [paramKey]: undefined });
}
}
@@ -95,17 +107,17 @@ const Search = ({
}
return (
-
+
{label && (
-
+
{label}
)}
-
@@ -133,13 +146,17 @@ Search.propTypes = {
paramKey: PropTypes.string,
label: PropTypes.any,
labelKey: PropTypes.string,
- location: PropTypes.object,
options: PropTypes.array,
query: PropTypes.object,
- queryParams: PropTypes.object,
searchKey: PropTypes.string,
setQueryParams: PropTypes.func,
placeholder: PropTypes.string,
+ urlHelper: PropTypes.shape({
+ location: PropTypes.object,
+ queryParams: PropTypes.object,
+ getInitialValueFromLocation: PropTypes.func,
+ historyPushWithQueryParams: PropTypes.func,
+ }),
};
-export default withRouter(withQueryParams()(connect((state) => state)(Search)));
+export default withUrlHelper(Search);
diff --git a/app/src/js/components/Sidebar/sidebar.js b/app/src/js/components/Sidebar/sidebar.js
index 2a93d7209..f6bab8f60 100644
--- a/app/src/js/components/Sidebar/sidebar.js
+++ b/app/src/js/components/Sidebar/sidebar.js
@@ -1,26 +1,31 @@
import React, { Fragment } from 'react';
+import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
+import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import { Link } from 'react-router-dom';
import { resolve } from 'path';
import sections from '../../paths';
-import { getPersistentQueryParams } from '../../utils/url-helper';
import { toggleSidebar } from '../../actions';
import Tooltip from '../Tooltip/tooltip';
+import { withUrlHelper } from '../../withUrlHelper';
const currentPathClass = 'sidebar__nav--selected';
const Sidebar = ({
count,
currentPath,
- dispatch,
- location,
- logs,
match,
- params,
- sidebar,
- locationQueryParams
+ urlHelper
}) => {
+ const dispatch = useDispatch();
+ const location = useLocation();
+ const navigate = useNavigate();
+ const params = useParams();
+ const { getPersistentQueryParams } = urlHelper;
+ const { logs, sidebar, locationQueryParams } = useSelector((state) => ({
+ logs: state.logs,
+ sidebar: state.sidebar,
+ locationQueryParams: state.locationQueryParams
+ }));
const { open: sidebarOpen } = sidebar;
const { metricsNotConfigured } = logs;
@@ -59,10 +64,14 @@ const Sidebar = ({
{d[0] &&
({
- pathname: path,
- search: locationQueryParams.search[path] || getPersistentQueryParams(routeLocation),
- })}
+ to={location}
+ onClick={(e) => {
+ e.preventDefault();
+ navigate({
+ pathname: path,
+ search: locationQueryParams.search[path] || getPersistentQueryParams(location),
+ });
+ }}
>
{d[0]}
@@ -102,19 +111,15 @@ const Sidebar = ({
Sidebar.propTypes = {
count: PropTypes.array,
currentPath: PropTypes.string,
- dispatch: PropTypes.func,
- location: PropTypes.object,
logs: PropTypes.object,
match: PropTypes.object,
- params: PropTypes.object,
sidebar: PropTypes.shape({
open: PropTypes.bool,
}),
locationQueryParams: PropTypes.object,
+ urlHelper: PropTypes.shape({
+ getPersistentQueryParams: PropTypes.func
+ }),
};
-export default connect((state) => ({
- logs: state.logs,
- sidebar: state.sidebar,
- locationQueryParams: state.locationQueryParams
-}))(Sidebar);
+export default withUrlHelper(Sidebar);
diff --git a/app/src/js/components/SortableTable/SortableTable.js b/app/src/js/components/SortableTable/SortableTable.js
index 6eddbe164..dea86051c 100644
--- a/app/src/js/components/SortableTable/SortableTable.js
+++ b/app/src/js/components/SortableTable/SortableTable.js
@@ -6,16 +6,20 @@ import React, {
useState,
createRef
} from 'react';
+import { Link } from 'react-router-dom';
+import { useDispatch } from 'react-redux';
+import { useTable, useResizeColumns, useFlexLayout, useSortBy, useRowSelect, usePagination, useExpanded } from 'react-table';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import omit from 'lodash/omit';
-import { useTable, useResizeColumns, useFlexLayout, useSortBy, useRowSelect, usePagination, useExpanded } from 'react-table';
-import { useDispatch } from 'react-redux';
+import isEqual from 'lodash/isEqual';
import TableHeader from '../TableHeader/table-header';
import SimplePagination from '../Pagination/simple-pagination';
import TableFilters from '../Table/TableFilters';
import ListFilters from '../ListActions/ListFilters';
import { sortPersist } from '../../actions/index';
+import { withUrlHelper } from '../../withUrlHelper';
+import { CopyCellPopover, fromNowWithTooltip } from '../../utils/format';
const getColumnWidth = (rows, accessor, headerText, originalWidth) => {
const maxWidth = 400;
@@ -73,7 +77,18 @@ const SortableTable = ({
renderRowSubComponent,
tableId,
initialSortBy = [],
+ urlHelper,
}) => {
+ let rightScrollInterval;
+ let leftScrollInterval;
+ let sortByState;
+
+ // State
+ const [fitColumn, setFitColumn] = useState({});
+ const [leftScrollButtonVisibility, setLeftScrollButtonVisibility] = useState({ display: 'none', opacity: 0 });
+ const [rightScrollButtonVisibility, setRightScrollButtonVisibility] = useState({ display: 'none', opacity: 0 });
+
+ // Memoized functions
const defaultColumn = useMemo(
() => ({
Cell: ({ value = '' }) => value,
@@ -83,10 +98,19 @@ const SortableTable = ({
}),
[]
);
- const [fitColumn, setFitColumn] = useState({});
- const [leftScrollButtonVisibility, setLeftScrollButtonVisibility] = useState({ display: 'none', opacity: 0 });
- const [rightScrollButtonVisibility, setRightScrollButtonVisibility] = useState({ display: 'none', opacity: 0 });
- let sortByState;
+
+ const memoizedTableColumns = useMemo(() => (
+ Array.isArray(tableColumns)
+ ? tableColumns.map((column) => ({
+ ...defaultColumn,
+ ...column,
+ }))
+ : [defaultColumn] // Provide a default column if tableColumns is not an array
+ ), [tableColumns, defaultColumn]);
+
+ // Memoize data
+ const memoizedData = useMemo(() => data, [data]);
+
if (initialSortBy?.length > 0) {
sortByState = initialSortBy;
} else if (initialSortId) {
@@ -94,8 +118,11 @@ const SortableTable = ({
} else {
sortByState = [];
}
- let rightScrollInterval;
- let leftScrollInterval;
+
+ const initialState = useMemo(() => ({
+ hiddenColumns: initialHiddenColumns,
+ sortBy: sortByState,
+ }), [initialHiddenColumns, sortByState]);
const {
getTableProps,
@@ -118,11 +145,11 @@ const SortableTable = ({
gotoPage,
nextPage,
previousPage,
- setHiddenColumns
+ setHiddenColumns,
} = useTable(
{
- data,
- columns: tableColumns,
+ data: memoizedData,
+ columns: memoizedTableColumns,
defaultColumn,
getRowId: (row, relativeIndex) => (typeof rowId === 'function' ? rowId(row) : row[rowId] || relativeIndex),
autoResetSelectedRows: false,
@@ -130,10 +157,7 @@ const SortableTable = ({
manualSortBy: shouldManualSort,
// if we want to use the pagination hook, then pagination should not be manual
manualPagination: !shouldUsePagination,
- initialState: {
- hiddenColumns: initialHiddenColumns,
- sortBy: sortByState,
- },
+ initialState
},
useFlexLayout, // this allows table to have dynamic layouts outside of standard table markup
useResizeColumns, // this allows for resizing columns
@@ -161,6 +185,7 @@ const SortableTable = ({
}
);
const dispatch = useDispatch();
+ const { getPersistentQueryParams } = urlHelper;
const tableRows = page || rows;
// We only include filters if not wrapped in list component and hideFilter is false
const wrappedInList = typeof getToggleColumnOptions === 'function';
@@ -170,12 +195,14 @@ const SortableTable = ({
const scrollLeftButton = createRef();
const scrollRightButton = createRef();
+ // Reset selection
useEffect(() => {
if (canSelect && clearSelected) {
toggleAllRowsSelected(false);
}
}, [canSelect, clearSelected, toggleAllRowsSelected]);
+ // Handle selection changes using react-table functionality
useEffect(() => {
const selectedIds = Object.keys(selectedRowIds).reduce((ids, key) => {
if (selectedRowIds[key]) {
@@ -198,6 +225,7 @@ const SortableTable = ({
}
}, [dispatch, tableId, sortBy]);
+ // Handle sort changes
useEffect(() => {
if (typeof changeSortProps === 'function') {
changeSortProps(sortBy);
@@ -464,12 +492,81 @@ const SortableTable = ({
onMouseEnter={(e) => handleTableColumnMouseEnter(e)}
onMouseLeave={(e) => handleTableColumnMouseLeave(e)}
>
- {cell.render('Cell')}
+ {(() => {
+ // Check for custom Cell renderer first
+ if (typeof cell.column === 'function') {
+ return cell.column({ cell, row: cell.row, value: cell.value });
+ }
+
+ // Then check for isLink
+ if (cell.column.isLink) {
+ // Use default link rendering for columns with isLink: true but no custom Cell function
+ let linkTo;
+ if (typeof cell.column.linkTo === 'function') {
+ linkTo = cell.column.linkTo(cell.row.original);
+ } else if (typeof cell.column.linkTo === 'string') {
+ linkTo = cell.column.linkTo;
+ } else {
+ return cell.value;
+ }
+
+ const pathname = linkTo;
+ const search = getPersistentQueryParams() || '';
+
+ const content = (
+
+ {cell.value}
+
+ );
+
+ // Check if the column uses CopyCellPopover
+ if (cell.column.useCopyCellPopover) {
+ return (
+
+ );
+ }
+
+ return content;
+ }
+
+ // Handle external links
+ if (cell.column.openInNewTab && cell.value) {
+ return (
+
+ {cell.value}
+
+ );
+ }
+
+ // Check for non-link cells that use CopyCellPopover
+ if (cell.column.useCopyCellPopover) {
+ return (
+
+ );
+ }
+
+ // Check for non-link cells that use Tooltip
+ if (cell.column.useTooltip) {
+ return fromNowWithTooltip(cell.value);
+ }
+
+ // Default cell rendering
+ return cell.render('Cell');
+ })()}
);
})}
-
{renderRowSubComponent &&
<>{renderRowSubComponent(row)}>
}
@@ -540,10 +637,21 @@ SortableTable.propTypes = {
rowId: PropTypes.any,
shouldManualSort: PropTypes.bool,
shouldUsePagination: PropTypes.bool,
- tableColumns: PropTypes.array,
+ tableColumns: PropTypes.arrayOf(PropTypes.object),
renderRowSubComponent: PropTypes.func,
tableId: PropTypes.string,
initialSortBy: PropTypes.array,
+ urlHelper: PropTypes.shape({
+ getPersistentQueryParams: PropTypes.func,
+ })
};
-export default SortableTable;
+SortableTable.defaultProps = {
+ tableColumns: [],
+};
+
+export default withUrlHelper(
+ React.memo(SortableTable, (prevProps, nextProps) => {
+ isEqual(prevProps, nextProps);
+ })
+);
diff --git a/app/src/js/components/Table/Table.js b/app/src/js/components/Table/Table.js
index 731cbfc4e..53d1bd825 100644
--- a/app/src/js/components/Table/Table.js
+++ b/app/src/js/components/Table/Table.js
@@ -1,7 +1,6 @@
-import React, { useState, useEffect, lazy, Suspense, useCallback, useRef } from 'react';
+import React, { useState, useEffect, useMemo, lazy, Suspense, useCallback, useRef } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import withQueryParams from 'react-router-query-params';
import isNil from 'lodash/isNil';
import isEqual from 'lodash/isEqual';
import omitBy from 'lodash/omitBy';
@@ -15,6 +14,8 @@ import TableHeader from '../TableHeader/table-header';
import ListFilters from '../ListActions/ListFilters';
import TableFilters from './TableFilters';
+import { withUrlHelper } from '../../withUrlHelper';
+
const SortableTable = lazy(() => import('../SortableTable/SortableTable'));
function buildSortKey(sortProps) {
@@ -28,7 +29,6 @@ const List = ({
bulkActions,
children,
data,
- dispatch,
filterAction,
filterClear,
groupAction,
@@ -39,15 +39,30 @@ const List = ({
list = {},
onSelect,
query = {},
- queryParams,
renderRowSubComponent,
rowId,
- sorts,
tableColumns,
tableId,
toggleColumnOptionsAction,
useSimplePagination = false,
+ urlHelper,
}) => {
+ const dispatch = useDispatch();
+ const sorts = useSelector((state) => state.sorts);
+ const sortBy = tableId ? sorts[tableId] : null;
+ const initialInfix = useRef(null);
+ const { historyPushWithQueryParams, location, queryParams } = urlHelper;
+
+ const queryFilters = useMemo(() => {
+ const {
+ limit: limitQueryParam,
+ page: pageQueryParam,
+ search: searchQueryParam,
+ ...filters
+ } = queryParams;
+ return filters;
+ }, [queryParams]);
+
const {
data: listData,
error: listError,
@@ -55,51 +70,84 @@ const List = ({
meta,
} = list;
const { count, limit } = meta || {};
- const tableData = data || listData || [];
+
+ const tableData = useMemo(() => data || listData || [], [data, listData]);
const [selected, setSelected] = useState([]);
const [clearSelected, setClearSelected] = useState(false);
const [page, setPage] = useState(1);
- const sortBy = tableId ? sorts[tableId] : null;
- const initialInfix = useRef(queryParams.search);
-
- const [queryConfig, setQueryConfig] = useState({
- page: 1,
- sort_key: buildSortKey(sortBy || [{ id: initialSortId, desc: true }]),
- ...initialInfix.current ? { infix: initialInfix.current } : {},
- ...query,
- });
- const [params, setParams] = useState({});
const [bulkActionMeta, setBulkActionMeta] = useState({
completedBulkActions: 0,
bulkActionError: null,
});
+ const [params, setParams] = useState({});
const [toggleColumnOptions, setToggleColumnOptions] = useState({
hiddenColumns: initialHiddenColumns,
setHiddenColumns: noop,
});
- const { bulkActionError, completedBulkActions } = bulkActionMeta;
- const {
- limit: limitQueryParam,
- page: pageQueryParam,
- search: searchQueryParam,
- ...queryFilters
- } = queryParams;
- const hasActions = Array.isArray(bulkActions) && bulkActions.length > 0;
+ // Initialize queryConfig with sortBy from Redux
+ const [queryConfig, setQueryConfig] = useState({
+ page: 1,
+ sort_key: buildSortKey(sortBy || [{ id: initialSortId, desc: true }]),
+ ...initialInfix.current ? { infix: initialInfix.current } : {},
+ ...query,
+ });
+
+ const memoizedQuery = useMemo(() => query, [query]);
+
+ const memoizedQueryFilters = useMemo(() => omitBy(queryFilters, isNil), [queryFilters]);
+
+ // Get query configuration: Remove empty keys so as not to mess up the query
+ const getQueryConfig = useCallback((config = {}) => omitBy({
+ ...queryConfig,
+ ...queryFilters,
+ page,
+ sort_key: sortBy ? buildSortKey(sortBy) : queryConfig.sort_key,
+ ...config
+ }, isNil), [queryConfig, queryFilters, sortBy, page]);
+ // Memoize getQueryConfig result to prevent infinite loops
+ const currentQueryConfig = useMemo(
+ () => getQueryConfig({}),
+ [getQueryConfig]
+ );
+
+ // Effect for query changes
+ useEffect(() => {
+ const newQueryConfig = currentQueryConfig;
+ if (!isEqual(newQueryConfig, queryConfig)) {
+ setQueryConfig(newQueryConfig);
+ }
+ }, [currentQueryConfig, queryConfig]);
+
+ // Effect for params changes
+ useEffect(() => {
+ const newParams = omitBy({
+ ...params,
+ ...queryFilters,
+ }, isNil);
+
+ if (!isEqual(newParams, params)) {
+ setParams(newParams);
+ // Don't update queryConfig here as it will cause a loop
+ }
+ }, [queryFilters, params]);
+
+ // useEffects for query management
useEffect(() => {
setQueryConfig((prevQueryConfig) => ({
...prevQueryConfig,
...getQueryConfig({}),
}));
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [JSON.stringify(query)]);
+ }, [memoizedQuery, getQueryConfig]);
useEffect(() => {
// Remove parameters with null or undefined values
- const newParams = omitBy(list.params, isNil);
-
+ const newParams = {
+ ...params,
+ ...memoizedQueryFilters,
+ };
if (!isEqual(newParams, params)) {
setParams(newParams);
setQueryConfig((prevQueryConfig) => ({
@@ -107,44 +155,63 @@ const List = ({
...getQueryConfig({}),
}));
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [JSON.stringify(list.params), JSON.stringify(params)]);
+ }, [params, memoizedQueryFilters, getQueryConfig]);
useEffect(() => {
setClearSelected(true);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [JSON.stringify(queryFilters)]);
+ }, [memoizedQueryFilters]);
useEffect(() => {
- if (typeof toggleColumnOptionsAction === 'function') {
+ if (typeof toggleColumnOptionsAction === 'function') { // replaces noop with modern JS
const allColumns = tableColumns.map(
(column) => column.id || column.accessor
);
dispatch(
- toggleColumnOptionsAction(toggleColumnOptions.hiddenColumns, allColumns)
+ toggleColumnOptionsAction(initialHiddenColumns, allColumns)
);
}
- }, [
- dispatch,
- tableColumns,
- toggleColumnOptions.hiddenColumns,
- toggleColumnOptionsAction,
- ]);
-
- function queryNewPage(newPage) {
+ }, [dispatch, toggleColumnOptionsAction, initialHiddenColumns, tableColumns]);
+
+ // Update page handling
+ const queryNewPage = useCallback((newPage) => {
setPage(newPage);
- }
- function queryNewSort(sortProps) {
+ // Update URL with new page number
+ if (historyPushWithQueryParams) {
+ const currentPath = location.pathname;
+ const currentSearch = new URLSearchParams(location.search);
+ currentSearch.set('page', newPage.toString());
+ historyPushWithQueryParams(`${currentPath}?${currentSearch.toString()}`);
+ }
+
+ // Dispatch action to fetch new page data
+ const newQueryConfig = getQueryConfig();
+ newQueryConfig.page = newPage;
+ dispatch(action(newQueryConfig));
+ }, [getQueryConfig, historyPushWithQueryParams, location, dispatch, action]);
+
+ useEffect(() => {
+ if (queryConfig.page !== page) {
+ setQueryConfig((prev) => ({
+ ...prev,
+ page
+ }));
+ }
+ }, [page, queryConfig.page]);
+
+ // Update sort handling
+ const queryNewSort = useCallback((sortProps) => {
const newQueryConfig = getQueryConfig({
sort_key: buildSortKey(sortProps),
});
if (!isEqual(queryConfig, newQueryConfig)) {
setQueryConfig(newQueryConfig);
+ dispatch(action(newQueryConfig));
}
- }
+ }, [getQueryConfig, queryConfig, dispatch, action]);
- function updateSelection(selectedIds, currentSelectedRows) {
+ // Update selection handling if needed
+ const updateSelection = useCallback((selectedIds, currentSelectedRows) => {
if (!isEqual(selected, selectedIds)) {
setSelected(selectedIds);
setClearSelected(false);
@@ -153,49 +220,36 @@ const List = ({
onSelect(selectedIds, currentSelectedRows);
}
}
- }
-
- function onBulkActionSuccess(results, error) {
- // not-elegant way to trigger a re-fresh in the timer
- setBulkActionMeta((prevBulkActionMeta) => {
- const {
- completedBulkActions: prevCompletedBulkActions,
- bulkActionError: prevBulkActionError,
- } = prevBulkActionMeta;
- return {
- completedBulkActions: prevCompletedBulkActions + 1,
- bulkActionError: error ? prevBulkActionError : null,
- };
- });
+ }, [selected, onSelect]);
+
+ const hasActions = useMemo(
+ () => Array.isArray(bulkActions) && bulkActions.length > 0,
+ [bulkActions]
+ );
+
+ const { completedBulkActions } = bulkActionMeta;
+
+ const onBulkActionSuccess = useCallback((results) => {
+ setBulkActionMeta((prevState) => ({
+ ...prevState,
+ completedBulkActions: prevState.completedBulkActions + 1,
+ bulkActionsError: null,
+ }));
setClearSelected(true);
- }
+ }, []);
- function onBulkActionError(error) {
+ const onBulkActionError = useCallback((error) => {
const newBulkActionError =
error.id && error.error
? `Could not process ${error.id}, ${error.error}`
: error;
- setBulkActionMeta((prevBulkActionMeta) => ({
- ...prevBulkActionMeta,
+ setBulkActionMeta((prevState) => ({
+ ...prevState,
bulkActionError: newBulkActionError,
}));
setClearSelected(true);
- }
-
- function getQueryConfig(config = {}) {
- // Remove empty keys so as not to mess up the query
- return omitBy(
- {
- page,
- sort_key: queryConfig.sort_key,
- ...params,
- ...config,
- ...query,
- },
- isNil
- );
- }
+ }, []);
const getToggleColumnOptions = useCallback((newOptions) => {
setToggleColumnOptions(newOptions);
@@ -227,7 +281,7 @@ const List = ({