Skip to content

Commit

Permalink
loans: date range search (#783)
Browse files Browse the repository at this point in the history
* loans: date range search

- adds filter `loans_from_date` and `loans_to_date` to filter loans
- adds UI datepickers to choose dates
- upgrades `react-searchkit` and `axios`
- closes #168
  • Loading branch information
topless authored Apr 4, 2020
1 parent f264e91 commit 76a1d1e
Show file tree
Hide file tree
Showing 28 changed files with 340 additions and 68 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ __pycache__/

# Idea software family
.idea/
.vscode/

# C extensions
*.so
Expand Down
3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

25 changes: 13 additions & 12 deletions invenio_app_ils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@
from .documents.api import DOCUMENT_PID_FETCHER, DOCUMENT_PID_MINTER, \
DOCUMENT_PID_TYPE, Document
from .documents.search import DocumentSearch
from .facets import default_value_when_missing_filter, keyed_range_filter, \
not_empty_object_or_list_filter, overdue_agg, overdue_loans_filter
from .facets import date_range_filter, default_value_when_missing_filter, \
keyed_range_filter, not_empty_object_or_list_filter, overdue_agg, \
overdue_loans_filter
from .records.views import UserInfoResource

from invenio_circulation.config import _LOANID_CONVERTER # isort:skip
Expand Down Expand Up @@ -1140,10 +1141,10 @@ def _(x):
),
),
filters=dict(
circulation=default_value_when_missing_filter("circulation.state",
"NOT_ON_LOAN"),
),
post_filters=dict(
circulation=default_value_when_missing_filter(
"circulation.state", "NOT_ON_LOAN"),
status=terms_filter("status"),
medium=terms_filter("medium"),
restrictions=terms_filter("circulation_restriction"),
Expand Down Expand Up @@ -1186,17 +1187,17 @@ def _(x):
)
),
),
filters={
"returns.end_date": overdue_loans_filter("end_date"),
},
post_filters=dict(
state=terms_filter("state"),
delivery=terms_filter("delivery.method"),
availability=keyed_range_filter(
post_filters={
"state": terms_filter("state"),
"delivery": terms_filter("delivery.method"),
"availability": keyed_range_filter(
"document.circulation.has_items_for_loan",
{"Available for loan": {"gt": 0}},
),
),
"returns.end_date": overdue_loans_filter("end_date"),
"loans_from_date": date_range_filter("start_date", "gte"),
"loans_to_date": date_range_filter("start_date", "lte"),
},
),
acq_orders=dict( # OrderSearch.Meta.index
aggs=dict(
Expand Down
16 changes: 15 additions & 1 deletion invenio_app_ils/facets.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
# it under the terms of the MIT License; see LICENSE file for more details.

"""Facets and factories for result filtering and aggregation."""
from datetime import timedelta

import arrow
from elasticsearch_dsl.query import Bool, Q, Range
Expand Down Expand Up @@ -113,3 +112,18 @@ def overdue_agg():
)
)
)


def date_range_filter(field, comparator):
"""Create a range filter.
:param field: Field name.
:param comparator: Comparison we want with the supplied date.
"""
def inner(values):
try:
input_date = str(arrow.get(values[0]).date())
except arrow.parser.ParserError as e:
raise ValueError("Input should be a date")
return Range(**{field: {comparator: input_date}})
return inner
22 changes: 19 additions & 3 deletions tests/api/test_facets.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
from datetime import timedelta

import arrow
import pytest
from elasticsearch_dsl.query import Bool, Q, Range, Terms
from flask import current_app

from invenio_app_ils.facets import default_value_when_missing_filter, \
keyed_range_filter, overdue_loans_filter
from invenio_app_ils.facets import date_range_filter, \
default_value_when_missing_filter, keyed_range_filter, \
overdue_loans_filter


def test_keyed_range_filter():
Expand All @@ -34,7 +36,6 @@ def test_keyed_range_filter():

def test_current_ranged_loans_filter(app):
"""Test ranged current loans filter."""

with app.app_context():
rfilter = overdue_loans_filter("field")

Expand All @@ -57,3 +58,18 @@ def test_default_value_when_missing_filter(app):
assert rfilter("test") == Terms(field="test")
assert rfilter("missing val") == Bool(
**{'must_not': {'exists': {'field': "field"}}})


@pytest.mark.parametrize("input_date", ["", "a string", "2020-02-02"])
def test_date_range_filter(app, input_date):
"""Test date range filter date validation and query."""
from_filter = date_range_filter("field", "gte")
to_filter = date_range_filter("field", "lte")

try:
assert from_filter([input_date]) == Range(field={"gte": input_date})
assert to_filter([input_date]) == Range(field={"lte": input_date})
except:
with pytest.raises(ValueError) as err:
from_filter([input_date])
to_filter([input_date])
20 changes: 7 additions & 13 deletions ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"format": "prettier --config ./.prettierrc --ignore-path ./.prettierignore --write \"**/*.js\""
},
"dependencies": {
"axios": "^0.19.0",
"axios": "^0.19.2",
"extract-text-webpack-plugin": "^3.0.2",
"formik": "^2.0.6",
"less": "^3.10.3",
Expand All @@ -30,7 +30,7 @@
"react-router-dom": "^5.1.2",
"react-scripts": "^3.4.0",
"react-scroll": "^1.7.16",
"react-searchkit": "^0.16.0",
"react-searchkit": "^0.18.0",
"react-show-more": "^2.0.0",
"react-tagcloud": "^2.0.0",
"redux": "^4.0.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ beforeEach(() => {
});

const searchApi = new InvenioSearchApi({
url: documentApi.searchBaseURL,
withCredentials: true,
axios: {
url: documentApi.searchBaseURL,
withCredentials: true,
},
});

describe('SearchControls tests', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,23 @@ exports[`SearchControls tests should mount SearchControls component 1`] = `
}
>
<ReactSearchKit
appName="RSK"
defaultSortByOnEmptyQuery={null}
history={null}
eventListenerEnabled={false}
searchApi={
InvenioSearchApi {
"axiosConfig": Object {
"url": "https://localhost:5000/api/documents/",
"withCredentials": true,
},
"http": [Function],
"requestInterceptor": undefined,
"requestSerializer": InvenioRequestSerializer {
"_addFilter": [Function],
"_addFilters": [Function],
"serialize": [Function],
},
"responseInterceptor": undefined,
"responseSerializer": InvenioResponseSerializer {
"serialize": [Function],
},
Expand Down Expand Up @@ -139,15 +146,17 @@ exports[`SearchControls tests should mount SearchControls component 1`] = `
}
>
<Connect(Bootstrap)
historyListen={null}
appName="RSK"
eventListenerEnabled={false}
searchOnInit={true}
>
<Bootstrap
historyListen={null}
appName="RSK"
eventListenerEnabled={false}
onAppInitialized={[Function]}
onBrowserHistoryExternallyChanged={[Function]}
searchOnInit={true}
searchOnUrlQueryStringChanged={[Function]}
updateQueryState={[Function]}
>
<SearchControls
layoutToggle={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ class OrderResponseSerializer {

export class OrderSearch extends Component {
searchApi = new InvenioSearchApi({
axios: {
url: orderApi.searchBaseURL,
withCredentials: true,
},
invenio: {
responseSerializer: OrderResponseSerializer,
},
url: orderApi.searchBaseURL,
withCredentials: true,
});

renderSearchBar = (_, queryString, onInputChange, executeSearch) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ import history from '@history';

export class VendorSearch extends Component {
searchApi = new InvenioSearchApi({
url: vendorApi.searchBaseURL,
withCredentials: true,
axios: {
url: vendorApi.searchBaseURL,
withCredentials: true,
},
});
searchConfig = getSearchConfig('vendors');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ import history from '@history';

export class DocumentSearch extends Component {
searchApi = new InvenioSearchApi({
url: documentApi.searchBaseURL,
withCredentials: true,
axios: {
url: documentApi.searchBaseURL,
withCredentials: true,
},
});
searchConfig = getSearchConfig('documents');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ import { responseRejectInterceptor } from '@api/base';

export class DocumentRequestSearch extends Component {
searchApi = new InvenioSearchApi({
url: documentRequestApi.searchBaseURL,
withCredentials: true,
axios: {
url: documentRequestApi.searchBaseURL,
withCredentials: true,
},
interceptors: {
response: { reject: responseRejectInterceptor },
},
Expand Down
6 changes: 4 additions & 2 deletions ui/src/pages/backoffice/EItem/EItemSearch/EItemSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ import history from '@history';

export class EItemSearch extends Component {
searchApi = new InvenioSearchApi({
url: eitemApi.searchBaseURL,
withCredentials: true,
axios: {
url: eitemApi.searchBaseURL,
withCredentials: true,
},
interceptors: {
response: { reject: responseRejectInterceptor },
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ import { BorrowingRequestListEntry } from './components';

export class BorrowingRequestSearch extends Component {
searchApi = new InvenioSearchApi({
url: borrowingRequestApi.searchBaseURL,
withCredentials: true,
axios: {
url: borrowingRequestApi.searchBaseURL,
withCredentials: true,
},
});

renderSearchBar = (_, queryString, onInputChange, executeSearch) => {
Expand Down
6 changes: 4 additions & 2 deletions ui/src/pages/backoffice/ILL/LibrarySearch/LibrarySearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ import { LibraryListEntry } from './components/LibraryListEntry';

export class LibrarySearch extends Component {
searchApi = new InvenioSearchApi({
url: libraryApi.searchBaseURL,
withCredentials: true,
axios: {
url: libraryApi.searchBaseURL,
withCredentials: true,
},
});
searchConfig = getSearchConfig('libraries');

Expand Down
6 changes: 4 additions & 2 deletions ui/src/pages/backoffice/Item/ItemSearch/ItemSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ import {

export class ItemSearch extends Component {
searchApi = new InvenioSearchApi({
url: itemApi.searchBaseURL,
withCredentials: true,
axios: {
url: itemApi.searchBaseURL,
withCredentials: true,
},
interceptors: {
response: { reject: responseRejectInterceptor },
},
Expand Down
6 changes: 4 additions & 2 deletions ui/src/pages/backoffice/Loan/LoanSearch/LoanSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ import {
SearchAggregationsCards,
} from '@components/SearchControls';

import { SearchDateRange } from './SearchDateRange';

export class LoanSearch extends Component {
searchApi = new InvenioSearchApi({
url: loanApi.searchBaseURL,
withCredentials: true,
axios: { url: loanApi.searchBaseURL, withCredentials: true },
interceptors: {
response: { reject: responseRejectInterceptor },
},
Expand Down Expand Up @@ -87,6 +88,7 @@ export class LoanSearch extends Component {
<Grid.Column width={3} className={'search-aggregations'}>
<Header content={'Filter by'} />
<SearchAggregationsCards modelName={'loans'} />
<SearchDateRange />
</Grid.Column>
<Grid.Column width={13}>
<SearchEmptyResults extras={this.renderEmptyResultsExtra} />
Expand Down
Loading

0 comments on commit 76a1d1e

Please sign in to comment.