Skip to content

Commit

Permalink
Migrate to react-query and updated endpoints (#188)
Browse files Browse the repository at this point in the history
* Install react-query and migrate search page

* fix tests for search page

* Upgrade dataset pages to use react-query. Remove resource reducer and deprecated code

* Remove unused code and migrate last axios call to react-query

* Update loading states

* Update version and remove unused code

* Fixes for tests

* restore resource_reducer and manually control certain aspects of the table

* Restore column ordering to resource_reducer and fix tests
  • Loading branch information
brdunfield authored Jan 10, 2024
1 parent 45ccc1d commit 96a40a9
Show file tree
Hide file tree
Showing 36 changed files with 670 additions and 1,345 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ lib
dist

.parcel-cache/*
.DS_Store
.DS_Store
.vscode
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@civicactions/data-catalog-components",
"version": "1.17.1",
"version": "1.18.0",
"description": "React Components for Open Data Catalogs.",
"main": "dist/index.js",
"source": "src/index.ts",
Expand All @@ -26,7 +26,6 @@
"@fortawesome/free-solid-svg-icons": "^5.11.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@tanstack/react-table": "^8.10.7",
"axios": "^1.6.2",
"bootstrap": "^4.2.1",
"excerpts": "0.0.3",
"html-to-react": "^1.7.0",
Expand Down Expand Up @@ -63,7 +62,8 @@
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"@tanstack/react-query": "^5.14.1"
},
"files": [
"lib",
Expand Down
24 changes: 15 additions & 9 deletions src/components/ColumnFilter/index.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import React from "react";
import React, {useState, useEffect} from "react";

function ColumnFilter({
column: { getFilterValue, setFilterValue, Header },
resourceState
column,
count
}) {
const [inputValue, setInputValue] = useState(column.getFilterValue() || '')

useEffect(() => {
const delayedInputTimeout = setTimeout(() => {
column.setFilterValue(inputValue || '');
}, 500)
return () => clearTimeout(delayedInputTimeout);
}, [inputValue, 500]);

return (
<input
aria-label={Header}
value={getFilterValue() || ''}
aria-label={column.columnDef.header}
value={inputValue}
onChange={(e) => {
e.preventDefault();
e.stopPropagation();
setFilterValue(e.target.value || undefined); // Set undefined to remove the filter entirely
setInputValue(e.target.value || '')
}}
placeholder={`Search ${resourceState.count} records...`}
placeholder={`Search ${count} records...`}
/>
);
};
Expand Down
11 changes: 6 additions & 5 deletions src/components/DataTable/DataTablePageResults/index.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React from 'react';
import React, { useContext } from 'react';
import { ResourceDispatch } from '../../../services/resource/resource_defaults';
import PropTypes from 'prop-types';

const DataTablePageResults = ({
total,
pageSize,
currentPage,
className,
viewing = false
}) => {
const { resourceState } = useContext(ResourceDispatch);
const pageSize = resourceState.pageSize;
const currentPage = resourceState.currentPage

// Add one to offset the 0 array index.
const page = currentPage + 1;
let displayTotal = total;
Expand Down Expand Up @@ -48,8 +51,6 @@ DataTablePageResults.defaultProps = {
DataTablePageResults.propTypes = {
className: PropTypes.string,
total: PropTypes.number.isRequired,
pageSize: PropTypes.number.isRequired,
currentPage: PropTypes.number.isRequired,
};

export default DataTablePageResults;
39 changes: 23 additions & 16 deletions src/components/DataTable/DataTablePageResults/index.test.jsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
import React from 'react';
import { render } from '@testing-library/react';
import DataTablePageResults from '.';
import { ResourceDispatch, defaultResourceState } from '../../../services/resource/resource_defaults';
import { getByTextContent } from '../../../tests/utils';

describe('<DataTablePageResults />', () => {
const dispatch = jest.fn();
it('renders correct initial results', () => {
const resourceState = {
pageSize: 10,
currentPage: 0
}
render(
<DataTablePageResults
total={100}
pageSize={10}
currentPage={0}
/>,
<ResourceDispatch.Provider value={{dispatch, resourceState}}>
<DataTablePageResults total={100} />
</ResourceDispatch.Provider>
);
expect(getByTextContent('1 - 10 of 100 rows')).toBeInTheDocument();
});

it('renders correct results on subsequent pages', () => {
const resourceState = {
pageSize: 10,
currentPage: 4
}
render(
<DataTablePageResults
total={100}
pageSize={10}
currentPage={4}
/>,
<ResourceDispatch.Provider value={{dispatch, resourceState}}>
<DataTablePageResults total={100} />
</ResourceDispatch.Provider>
);
expect(getByTextContent('41 - 50 of 100 rows')).toBeInTheDocument();
});

it('Correctly displays appended viewing to results list', () => {
const resourceState = {
pageSize: 10,
currentPage: 4
}
render(
<DataTablePageResults
total={100}
pageSize={10}
currentPage={4}
viewing
/>,
<ResourceDispatch.Provider value={{dispatch, resourceState}}>
<DataTablePageResults viewing total={100} />
</ResourceDispatch.Provider>
);
expect(getByTextContent('Viewing 41 - 50 of 100 rows')).toBeInTheDocument();
})
Expand Down
22 changes: 11 additions & 11 deletions src/components/DataTable/DataTablePageSizer/index.jsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import React from 'react';
import React, {useContext} from 'react';
import PropTypes from 'prop-types';
import { ResourceDispatch } from '../../../services/resource/resource_defaults';

const DataTablePageSizer = ({
label,
pageSizeChange,
initSize,
options,
className,
selectClassName,
id,
}) => {
const [selValue, setSelValue] = React.useState(initSize);
React.useEffect(() => {
pageSizeChange(Number(selValue));
}, [selValue]);
const {dispatch, resourceState} = useContext(ResourceDispatch);

return (
<div className={className}>
<label htmlFor={`dc-${id}-pagesize`}>{label}</label>
<select
value={selValue}
value={resourceState.pageSize}
className={selectClassName}
onChange={(e) => setSelValue(e.target.value)}
onChange={(e) => dispatch({
type: 'UPDATE_PAGE_SIZE',
data: {
pageSize: e.target.value
}
})}
type="select"
name={`dc-${id}-pagesize`}
id={`dc-${id}-pagesize`}
Expand Down Expand Up @@ -49,14 +51,12 @@ DataTablePageSizer.propTypes = {
label: PropTypes.string,
className: PropTypes.string,
selectClassName: PropTypes.string,
pageSizeChange: PropTypes.func.isRequired,
options: PropTypes.arrayOf(PropTypes.shape({
defaultChecked: PropTypes.bool,
label: PropTypes.string,
value: PropTypes.string,
})),
id: PropTypes.string.isRequired,
initSize: PropTypes.number,
};

export default DataTablePageSizer;
40 changes: 25 additions & 15 deletions src/components/DataTable/DataTablePageSizer/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,38 @@ import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import DataTablePageSizer from './index';
import { ResourceDispatch, defaultResourceState } from '../../../services/resource/resource_defaults';

describe('<DataTablePageSizer />', () => {
const resourceState = defaultResourceState;
const dispatch = jest.fn();
test('renders default', () => {
render(<DataTablePageSizer pageSizeChange={() => () => true} id="1234" />);
render(
<ResourceDispatch.Provider value={{dispatch, resourceState}}>
<DataTablePageSizer pageSizeChange={() => () => true} id="1234" />
</ResourceDispatch.Provider>
);
expect(screen.getByLabelText('Rows per page')).toBeInTheDocument();
});
test('renders custom', () => {
render(
<DataTablePageSizer
pageSizeChange={() => () => true}
id="1234"
label="Foobar"
currentOption="150"
options={[
{ label: '20', value: '20' },
{ label: '50', value: '50' },
{ label: '100', value: '100' },
{ defaultChecked: true, label: '150', value: '150' },
{ label: '200', value: '200' },
{ label: '250', value: '250' },
]}
/>,
<ResourceDispatch.Provider value={{dispatch, resourceState}}>
<DataTablePageSizer
pageSizeChange={() => () => true}
id="1234"
label="Foobar"
currentOption="150"
options={[
{ label: '20', value: '20' },
{ label: '50', value: '50' },
{ label: '100', value: '100' },
{ defaultChecked: true, label: '150', value: '150' },
{ label: '200', value: '200' },
{ label: '250', value: '250' },
]}
/>
</ResourceDispatch.Provider>

);
expect(screen.getByLabelText('Foobar')).toBeInTheDocument();
});
Expand Down
5 changes: 1 addition & 4 deletions src/components/DataTable/ManageColumns/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import PropTypes from 'prop-types';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import Card from './Card';

import { ResourceDispatch } from '../../../services/resource/resource_defaults';
import Modal from '../../Modal';

const defaultCard = (card, index, moveCard) => (
Expand All @@ -30,8 +28,8 @@ const defaultCard = (card, index, moveCard) => (

const ManageColumns = ({
renderCard,
reactTable
}) => {
const { reactTable } = useContext(ResourceDispatch);
const [cards, setCards] = useState(null);
React.useEffect(() => {
if (reactTable.getAllColumns().length && cards === null) {
Expand All @@ -41,7 +39,6 @@ const ManageColumns = ({
const moveCard = React.useCallback(
(dragIndex, hoverIndex) => {
const newCards = cards.toSpliced(hoverIndex, 0, cards.splice(dragIndex,1)[0]);

setCards(newCards);
},
[cards, reactTable.getAllColumns()],
Expand Down
47 changes: 21 additions & 26 deletions src/components/Organization/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,30 @@ import React, { useState, useEffect } from 'react';
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import PublisherDatasetCountByName from "../PublisherDatasetCountByName";
import axios from 'axios';

function Organization(props) {
const { name,
description,
imageUrl,
searchUrl,
alignment,
organizationEndpoint} = props;
import { useQuery } from '@tanstack/react-query';

const Organization = ({
name,
description,
imageUrl,
searchUrl,
alignment,
organizationEndpoint
}) => {
const image = <img alt={name || 'Organization Image'} src={imageUrl} />;
const link = searchUrl ? searchUrl : `/search/?publisher__name=${name}`;
const [dataObj, setDataObj] = useState();
const endpoint = organizationEndpoint ? organizationEndpoint.replace("api/1", "data.json") : null;

const fetchData = async () => {
const endpoint = organizationEndpoint ? organizationEndpoint.replace("api/1", "data.json") : null;
if (endpoint) {
axios.get(endpoint)
.then(res => (setDataObj(res.data)))
.catch(err => (console.log("Error, check URL/Cors.", err)));
} else {
console.log("No search endpoint defined for Organization/s, so no dataset info available.");
const {data} = (endpoint) ? useQuery({
queryKey: ['organization'],
queryFn: () => {
return fetch(endpoint).then(
(res) => res.json(),
)
}
};

useEffect(() => {
fetchData();
}, []);

}) : {data: undefined};
if (!endpoint) console.log("No search endpoint defined for Organization/s, so no dataset info available.");

return (
<div className="dc-org-block" style={{ textAlign: alignment }}>
<Link to={link} className="dc-org-image" alt="Organization Logo">
Expand All @@ -45,11 +40,11 @@ function Organization(props) {
</div>
)}

{dataObj && dataObj.dataset !== 'undefined' ?
{data && data.dataset !== 'undefined' ?
<PublisherDatasetCountByName
name={name}
datasetCount={
countDatasetsByName(name, dataObj.dataset)
countDatasetsByName(name, data.dataset)
} /> :
<PublisherDatasetCountByName name={name} />
}
Expand Down
Loading

0 comments on commit 96a40a9

Please sign in to comment.