Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERM-3332, Add documents filter to agreement line search #704

Merged
merged 21 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
78c03a7
Add translations
MonireRasouli Oct 9, 2024
bd1658e
ERM-3332
MonireRasouli Oct 21, 2024
b3d0534
feat: DocumentFilter labelling
EthanFreestone Oct 21, 2024
620caab
feat: Pass filterName (defaulted to docs) all the way down
EthanFreestone Oct 21, 2024
abb83ee
style: Rename atTypeValues to categoryValues
EthanFreestone Oct 21, 2024
fe9c533
Refactor change orders of props and imports
EthanFreestone Oct 21, 2024
311639d
fix: Category value field not shifting to Select
EthanFreestone Oct 21, 2024
941712c
lint
MonireRasouli Oct 22, 2024
3d822ad
Add test coverage to DocumentFilters
MonireRasouli Oct 22, 2024
aa07a9c
lint
MonireRasouli Oct 22, 2024
15339a1
fix: Render OrganizationSelection placeholder (Was never rendering wh…
EthanFreestone Oct 23, 2024
a180460
fix render Internal contacts placeholder, tweak tests
MonireRasouli Oct 23, 2024
abb93c4
test: WIP! Broken the tests to demonstrate a progression of jest test…
EthanFreestone Oct 23, 2024
7dfb7ee
refactor: Move a bunch of logic down a level to avoid weird componen…
EthanFreestone Oct 23, 2024
5189468
test: Fixed FocumentFilter test (Leaving some for cleanup later)
EthanFreestone Oct 29, 2024
c969a8b
test: Added documentFilterParsing to combined testResource file, so t…
EthanFreestone Oct 29, 2024
50697df
test: Add test comments for DocumentFilterFieldArray
EthanFreestone Oct 29, 2024
6b0e747
test: DocumentFilterField test (Leaving rules testing to M)
EthanFreestone Oct 29, 2024
a937cc5
test: Testing DocumentFilterRule (With comments to expand on this later
EthanFreestone Oct 29, 2024
1e2b780
chore: Linting
EthanFreestone Oct 29, 2024
71582dd
chore: Prop validation
EthanFreestone Oct 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export { default as CustomMetaSection } from './lib/CustomMetaSection';
export { default as DateFilter } from './lib/DateFilter';
export { default as DocumentCard } from './lib/DocumentCard';
export { default as DocumentFilter } from './lib/DocumentFilter';
export { default as DocumentFilterForm } from './lib/DocumentFilter/DocumentFilterForm';
export { default as DocumentFilterFieldArray } from './lib/DocumentFilter/DocumentFilterFieldArray';
export { default as DocumentsFieldArray } from './lib/DocumentsFieldArray';
export { default as DuplicateModal } from './lib/DuplicateModal';
export { default as EditCard } from './lib/EditCard';
Expand Down
86 changes: 31 additions & 55 deletions lib/DocumentFilter/DocumentFilter.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
import { useState } from 'react';

import {
Accordion,
FilterAccordionHeader,
Expand All @@ -8,18 +8,26 @@ import {
import { FormattedMessage } from 'react-intl';

import {
deparseKiwtQueryFilters,
parseKiwtQueryFilters,
} from '@k-int/stripes-kint-components';

import DocumentFilterForm from './DocumentFilterForm';

const DocumentFilter = ({ activeFilters, atTypeValues = [], filterHandlers }) => {
// atTypeValues are only passed for SupplementaryDocumentFilter
const filterType = atTypeValues.length > 0 ? 'supplementaryDocuments' : 'documents';
const [editingFilters, setEditingFilters] = useState(false);
const openEditModal = () => setEditingFilters(true);
const closeEditModal = () => setEditingFilters(false);
const DocumentFilter = ({
activeFilters,
atTypeValues = [], // DEPRECATED
categoryValues = [], // Use this instead of atTypeValues
filterHandlers,
filterLabel,
filterModalProps = {},
filterName = 'docs'
}) => {
let categoryValuesToUse = categoryValues;
if (categoryValuesToUse.length === 0 && atTypeValues.length !== 0) {
// eslint-disable-next-line no-console
console.error('atTypeValues have been deprecated from Ramsons. Use categoryValues instead');
categoryValuesToUse = atTypeValues;
}

// Due to how filters are handled within SearchAndSortQuery the filter string needs to be parsed back into a usual object
const parseQueryString = (filterArray) => {
Expand Down Expand Up @@ -55,52 +63,16 @@ const DocumentFilter = ({ activeFilters, atTypeValues = [], filterHandlers }) =>
return [];
};

const parsedFilterData = parseQueryString(activeFilters?.[filterType] || []);

const handleSubmit = (values) => {
// In order to convert the form values into the shape for them to be deparsed we do the inverse of the above
// Adding a || operator between all elements of the filters array and a && operator between all elements of the nested arrays
// With special logic to ensure that operators are not added infront of the first elements of any arrays, to ensure no grouping errors
const kiwtQueryShape = values?.filters?.reduce((acc, curr, index) => {
let newAcc = [...acc];

if (index !== 0) {
newAcc = [...newAcc, '||'];
}

newAcc = [
...newAcc,
curr.rules.reduce((a, c, i) => {
return [
...a,
i !== 0 ? '&&' : null, // Don't group on first entry
c,
].filter(Boolean);
}, []),
];

return newAcc;
}, []);

filterHandlers.state({
...activeFilters,
[filterType]: [
// Currently the deparse function returns a query string containing whitespace which leads to grouping errors
// This regex removes all whitespace from the querystring
deparseKiwtQueryFilters(kiwtQueryShape),
],
});
setEditingFilters(false);
};
const parsedFilterData = parseQueryString(activeFilters?.[filterName] || []);

return (
<Accordion
closedByDefault
displayClearButton={!!parsedFilterData?.length}
header={FilterAccordionHeader}
id={`clickable-agreement-${filterType}-filter`}
label={<FormattedMessage id={`stripes-erm-components.documentFilter.${filterType}`} />}
onClearFilter={() => filterHandlers.state({ ...activeFilters, [filterType]: [] })
id={`clickable-agreement-${filterName}-filter`}
label={filterLabel ?? <FormattedMessage id="stripes-erm-components.documentFilter.documents" />}
onClearFilter={() => filterHandlers.state({ ...activeFilters, [filterName]: [] })
}
separator={false}
>
Expand All @@ -113,14 +85,12 @@ const DocumentFilter = ({ activeFilters, atTypeValues = [], filterHandlers }) =>
</Layout>
)}
<DocumentFilterForm
atTypeValues={atTypeValues}
editingFilters={editingFilters}
activeFilters={activeFilters}
categoryValues={categoryValuesToUse}
filterHandlers={filterHandlers}
filterModalProps={filterModalProps}
filterName={filterName}
filters={parsedFilterData}
handlers={{
closeEditModal,
openEditModal,
}}
onSubmit={handleSubmit}
/>
</Accordion>
);
Expand All @@ -129,7 +99,13 @@ const DocumentFilter = ({ activeFilters, atTypeValues = [], filterHandlers }) =>
DocumentFilter.propTypes = {
activeFilters: PropTypes.object,
atTypeValues: PropTypes.arrayOf(PropTypes.object),
categoryValues: PropTypes.arrayOf(PropTypes.object),
filterHandlers: PropTypes.object,
filterLabel: PropTypes.string,
filterName: PropTypes.string,
filterModalProps: PropTypes.shape({
label: PropTypes.string,
}),
};

export default DocumentFilter;
143 changes: 143 additions & 0 deletions lib/DocumentFilter/DocumentFilter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { MemoryRouter } from 'react-router-dom';
import {
Button,
Accordion,
renderWithIntl,
} from '@folio/stripes-erm-testing';

import { Button as MockStripesButton } from '@folio/stripes/components';

import { waitFor } from '@folio/jest-config-stripes/testing-library/react';
import { translationsProperties } from '../../test/jest/helpers';
import DocumentFilter from './DocumentFilter';

import { documentFilterParsing } from './testResources';

const mockPFD = jest.fn();

jest.mock('./DocumentFilterForm', () => ({
filters,
}) => {
return (
<div>
<MockStripesButton onClick={() => mockPFD(filters)}>
Test parsed filter data
</MockStripesButton>
DocumentFilterForm
</div>
);
});

const stateMock = jest.fn();
const filterHandlers = {
state: stateMock,
checkbox: () => {},
clear: () => {},
clearGroup: () => {},
reset: () => {},
};


// Todo these should be centralised
const categoryValues = [
{
'id': '2c9180a09262108601926219be050022',
'value': 'consortium_negotiation_document',
'label': 'Consortium negotiation document',
},
{
'id': '2c9180a09262108601926219bdfc0020',
'value': 'license',
'label': 'License',
},
{
'id': '2c9180a09262108601926219be010021',
'value': 'misc',
'label': 'Misc',
},
];


let renderComponent;

describe('DocumentFilter', () => {
describe.each([
[
'with active filters',
documentFilterParsing.find(dfp => dfp.name === 'simple').deparsed,
'1 document filters applied',
documentFilterParsing.find(dfp => dfp.name === 'simple').parsed,
],
[
'without active filters',
{ docs: [] },
null,
[]
],
[
'with multiple active filters',
documentFilterParsing.find(dfp => dfp.name === 'complex').deparsed,
'2 document filters applied',
documentFilterParsing.find(dfp => dfp.name === 'complex').parsed,
],
])('ActiveFilter tests', (
activeFilterTitle,
activeFilterState,
expectedLayoutText,
expectedParsedFilterData
) => {
describe(activeFilterTitle, () => {
beforeEach(() => {
renderComponent = renderWithIntl(
<MemoryRouter>
<DocumentFilter
activeFilters={activeFilterState}
categoryValues={categoryValues}
filterHandlers={filterHandlers}
/>,
</MemoryRouter>,
translationsProperties
);
});

test('renders the document filters accordion', async () => {
await Accordion('Documents').exists();
});

test('renders expected filters text', async () => {
const baseLayoutText = /document filters applied/;
const { queryByText } = renderComponent;
if (expectedLayoutText != null) {
expect(queryByText(expectedLayoutText)).toBeInTheDocument();
} else {
expect(queryByText(baseLayoutText)).not.toBeInTheDocument();
}
});

it('renders DocumentFilterForm component', () => {
const { getByText } = renderComponent;
expect(getByText('DocumentFilterForm')).toBeInTheDocument();
});

// TODO
// Add test for clearing accordion, whether clear button exists etc (mock filterHandlers.state)
// and check the right value are passed when it's clicked

describe('testing parsedFilterData', () => {
beforeEach(async () => {
mockPFD.mockClear();
await waitFor(async () => {
await Button('Test parsed filter data').click();
});
});

test('parsed filter data is as expected', () => {
expect(mockPFD.mock.calls[0][0]).toEqual(expectedParsedFilterData);
});
});
});
});

// TODO
// Add test for passing in/not passing in labels
});
13 changes: 10 additions & 3 deletions lib/DocumentFilter/DocumentFilterField.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { FieldArray } from 'react-final-form-arrays';
import { FormattedMessage } from 'react-intl';
import DocumentFilterRule from './DocumentFilterRule';

const DocumentFilterField = ({ atTypeValues, index, name }) => {
const DocumentFilterField = ({
categoryValues,
filterName,
index,
name
}) => {
const {
mutators: { push },
} = useForm();
Expand All @@ -15,7 +20,8 @@ const DocumentFilterField = ({ atTypeValues, index, name }) => {
<DocumentFilterRule
key={ruleFieldName}
ariaLabelledby={`selected-document-item-name-${index}`}
atTypeValues={atTypeValues}
categoryValues={categoryValues}
filterName={filterName}
index={ruleFieldIndex}
name={ruleFieldName}
onDelete={() => ruleFields.remove(ruleFieldIndex)}
Expand Down Expand Up @@ -63,9 +69,10 @@ const DocumentFilterField = ({ atTypeValues, index, name }) => {
};

DocumentFilterField.propTypes = {
atTypeValues: PropTypes.arrayOf(PropTypes.object),
categoryValues: PropTypes.arrayOf(PropTypes.object),
index: PropTypes.number,
name: PropTypes.string,
filterName: PropTypes.string,
};

export default DocumentFilterField;
Loading