Skip to content

Commit b5da245

Browse files
JakobMiesnerkpsherva
authored andcommitted
stats: raise error when loan is indexed while loan transition events index does not exits
1 parent 073a238 commit b5da245

File tree

6 files changed

+119
-70
lines changed

6 files changed

+119
-70
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (C) 2025 CERN.
4+
#
5+
# Invenio-App-Ils is free software; you can redistribute it and/or modify
6+
# it under the terms of the MIT License; see LICENSE file for more details.
7+
8+
"""Circulation exceptions."""
9+
10+
11+
class LoanTransitionEventsIndexMissingError(Exception):
12+
"""Error raised when the loan transition events index is missing."""

invenio_app_ils/circulation/indexer.py

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from invenio_pidstore.errors import PIDDeletedError
1818
from invenio_search import current_search_client
1919

20+
from invenio_app_ils.circulation.errors import LoanTransitionEventsIndexMissingError
2021
from invenio_app_ils.circulation.utils import resolve_item_from_loan
2122
from invenio_app_ils.documents.api import DOCUMENT_PID_TYPE
2223
from invenio_app_ils.indexer import ReferencedRecordsIndexer
@@ -129,44 +130,40 @@ def index_stats_fields_for_loan(loan_dict):
129130

130131
# Document availability during loan request
131132
stat_events_index_name = "events-stats-loan-transitions"
132-
if current_search_client.indices.exists(index=stat_events_index_name):
133-
loan_pid = loan_dict["pid"]
134-
search_body = {
135-
"query": {
136-
"bool": {
137-
"must": [
138-
{"term": {"trigger": "request"}},
139-
{"term": {"pid_value": loan_pid}},
140-
],
141-
}
142-
},
143-
}
144-
145-
search_result = current_search_client.search(
146-
index=stat_events_index_name, body=search_body
133+
if not current_search_client.indices.exists(index=stat_events_index_name):
134+
raise LoanTransitionEventsIndexMissingError()
135+
136+
loan_pid = loan_dict["pid"]
137+
search_body = {
138+
"query": {
139+
"bool": {
140+
"must": [
141+
{"term": {"trigger": "request"}},
142+
{"term": {"pid_value": loan_pid}},
143+
],
144+
}
145+
},
146+
}
147+
148+
search_result = current_search_client.search(
149+
index=stat_events_index_name, body=search_body
150+
)
151+
hits = search_result["hits"]["hits"]
152+
if len(hits) == 1:
153+
request_transition_event = hits[0]["_source"]
154+
available_items_during_request_count = request_transition_event[
155+
"extra_data"
156+
]["available_items_during_request_count"]
157+
stats["available_items_during_request"] = (
158+
available_items_during_request_count > 0
147159
)
148-
hits = search_result["hits"]["hits"]
149-
if len(hits) == 1:
150-
request_transition_event = hits[0]["_source"]
151-
available_items_during_request_count = request_transition_event[
152-
"extra_data"
153-
]["available_items_during_request_count"]
154-
stats["available_items_during_request"] = (
155-
available_items_during_request_count > 0
156-
)
157-
elif len(hits) > 1:
158-
raise ValueError(
159-
f"Multiple request transition events for loan {loan_pid}."
160-
"Expected zero or one."
161-
)
162-
else:
163-
current_app.logger.error(
164-
"Stats events index '{stat_events_index_name}' does not exist. "
165-
"This is normal during initial setup or if no events have been processed yet. "
166-
"No data is lost, as soon as the events are processed, " \
167-
"the loan wil lbe reindex and the the stat will be available."
160+
elif len(hits) > 1:
161+
raise ValueError(
162+
f"Multiple request transition events for loan {loan_pid}."
163+
"Expected zero or one."
168164
)
169165

166+
170167
if not "extra_data" in loan_dict:
171168
loan_dict["extra_data"] = {}
172169
loan_dict["extra_data"]["stats"] = stats

tests/api/circulation/test_loan_bulk_extend.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from invenio_app_ils.circulation.api import bulk_extend_loans
2020
from invenio_app_ils.items.api import ITEM_PID_TYPE, Item
2121
from invenio_app_ils.proxies import current_app_ils
22-
from tests.api.conftest import _create_records
22+
from tests.api.conftest import create_records
2323
from tests.helpers import user_login
2424

2525
pid_start_value = 200
@@ -108,8 +108,8 @@ def _prepare_data(db, repeated_run_number=0):
108108
loans = create_loans(new_pid_start_value, new_pid_end_value)
109109
items = create_items(new_pid_start_value, new_pid_end_value)
110110

111-
bulk_index_items = _create_records(db, items, Item, ITEM_PID_TYPE)
112-
bulk_index_loans = _create_records(db, loans, Loan, CIRCULATION_LOAN_PID_TYPE)
111+
bulk_index_items = create_records(db, items, Item, ITEM_PID_TYPE)
112+
bulk_index_loans = create_records(db, loans, Loan, CIRCULATION_LOAN_PID_TYPE)
113113

114114
for item in bulk_index_items:
115115
current_app_ils.item_indexer.index(item)

tests/api/conftest.py

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def json_headers():
7474
]
7575

7676

77-
def _create_records(db, objs, cls, pid_type):
77+
def create_records(db, objs, cls, pid_type):
7878
"""Create records and index."""
7979
recs = []
8080
for obj in objs:
@@ -89,42 +89,42 @@ def _create_records(db, objs, cls, pid_type):
8989
def testdata(app, db, search_clear, users):
9090
"""Create, index and return test data."""
9191
data = load_json_from_datadir("locations.json")
92-
locations = _create_records(db, data, Location, LOCATION_PID_TYPE)
92+
locations = create_records(db, data, Location, LOCATION_PID_TYPE)
9393

9494
data = load_json_from_datadir("internal_locations.json")
95-
int_locs = _create_records(db, data, InternalLocation, INTERNAL_LOCATION_PID_TYPE)
95+
int_locs = create_records(db, data, InternalLocation, INTERNAL_LOCATION_PID_TYPE)
9696

9797
data = load_json_from_datadir("series.json")
98-
series = _create_records(db, data, Series, SERIES_PID_TYPE)
98+
series = create_records(db, data, Series, SERIES_PID_TYPE)
9999

100100
data = load_json_from_datadir("documents.json")
101-
documents = _create_records(db, data, Document, DOCUMENT_PID_TYPE)
101+
documents = create_records(db, data, Document, DOCUMENT_PID_TYPE)
102102

103103
data = load_json_from_datadir("items.json")
104-
items = _create_records(db, data, Item, ITEM_PID_TYPE)
104+
items = create_records(db, data, Item, ITEM_PID_TYPE)
105105

106106
data = load_json_from_datadir("eitems.json")
107-
eitems = _create_records(db, data, EItem, EITEM_PID_TYPE)
107+
eitems = create_records(db, data, EItem, EITEM_PID_TYPE)
108108

109109
data = load_json_from_datadir("loans.json")
110-
loans = _create_records(db, data, Loan, CIRCULATION_LOAN_PID_TYPE)
110+
loans = create_records(db, data, Loan, CIRCULATION_LOAN_PID_TYPE)
111111

112112
data = load_json_from_datadir("acq_providers.json")
113-
acq_providers = _create_records(db, data, Provider, PROVIDER_PID_TYPE)
113+
acq_providers = create_records(db, data, Provider, PROVIDER_PID_TYPE)
114114

115115
data = load_json_from_datadir("acq_orders.json")
116-
acq_orders = _create_records(db, data, Order, ORDER_PID_TYPE)
116+
acq_orders = create_records(db, data, Order, ORDER_PID_TYPE)
117117

118118
data = load_json_from_datadir("ill_providers.json")
119-
ill_providers = _create_records(db, data, Provider, PROVIDER_PID_TYPE)
119+
ill_providers = create_records(db, data, Provider, PROVIDER_PID_TYPE)
120120

121121
data = load_json_from_datadir("ill_borrowing_requests.json")
122-
ill_brw_reqs = _create_records(
122+
ill_brw_reqs = create_records(
123123
db, data, BorrowingRequest, BORROWING_REQUEST_PID_TYPE
124124
)
125125

126126
data = load_json_from_datadir("document_requests.json")
127-
doc_reqs = _create_records(db, data, DocumentRequest, DOCUMENT_REQUEST_PID_TYPE)
127+
doc_reqs = create_records(db, data, DocumentRequest, DOCUMENT_REQUEST_PID_TYPE)
128128

129129
# index
130130
ri = RecordIndexer()
@@ -166,7 +166,7 @@ def testdata(app, db, search_clear, users):
166166
def testdata_most_loaned(db, testdata):
167167
"""Create, index and return test data for most loans tests."""
168168
most_loaned = load_json_from_datadir("loans_most_loaned.json")
169-
recs = _create_records(db, most_loaned, Loan, CIRCULATION_LOAN_PID_TYPE)
169+
recs = create_records(db, most_loaned, Loan, CIRCULATION_LOAN_PID_TYPE)
170170

171171
ri = RecordIndexer()
172172
for rec in recs:
@@ -184,23 +184,6 @@ def testdata_most_loaned(db, testdata):
184184
}
185185

186186

187-
@pytest.fixture()
188-
def testdata_loan_histogram(db, testdata):
189-
"""Create, index and return test data for loans histogram."""
190-
loans_histogram = load_json_from_datadir("loans_histogram.json")
191-
recs = _create_records(db, loans_histogram, Loan, CIRCULATION_LOAN_PID_TYPE)
192-
193-
ri = RecordIndexer()
194-
for rec in recs:
195-
ri.index(rec)
196-
197-
current_search.flush_and_refresh(index="loans")
198-
199-
testdata["loans_histogram"] = loans_histogram
200-
201-
return testdata
202-
203-
204187
@pytest.fixture()
205188
def item_record(app):
206189
"""Fixture to return an Item payload."""

tests/api/ils/stats/conftest.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,21 @@
66

77
"""Pytest fixtures and plugins for ILS stats."""
88

9+
10+
import datetime
11+
912
import pytest
13+
from invenio_circulation.api import Loan
14+
from invenio_circulation.pidstore.pids import CIRCULATION_LOAN_PID_TYPE
15+
from invenio_indexer.api import RecordIndexer
16+
from invenio_search import current_search
1017
from invenio_stats import current_stats
18+
from invenio_stats.tasks import process_events
19+
20+
from tests.api.conftest import create_records
21+
from tests.helpers import (
22+
load_json_from_datadir,
23+
)
1124

1225

1326
@pytest.fixture()
@@ -18,11 +31,53 @@ def empty_event_queues():
1831
queue.queue.declare()
1932
queue.consume()
2033

34+
2135
@pytest.fixture()
22-
def with_stats_index_extensions(app):
36+
def testdata_loan_histogram(db, testdata):
37+
"""Create, index and return test data for loans histogram."""
38+
loans_histogram = load_json_from_datadir("loans_histogram.json")
39+
recs = create_records(db, loans_histogram, Loan, CIRCULATION_LOAN_PID_TYPE)
40+
41+
ri = RecordIndexer()
42+
for rec in recs:
43+
ri.index(rec)
44+
45+
current_search.flush_and_refresh(index="loans")
46+
47+
testdata["loans_histogram"] = loans_histogram
48+
49+
return testdata
50+
51+
52+
@pytest.fixture()
53+
def with_stats_index_extensions(app, ensure_loan_transitions_index):
2354
"""Enable indices to be extended with stats data."""
2455
app.config["ILS_EXTEND_INDICES_WITH_STATS_ENABLED"] = True
2556
yield
2657
app.config["ILS_EXTEND_INDICES_WITH_STATS_ENABLED"] = False
2758

2859

60+
@pytest.fixture()
61+
def ensure_loan_transitions_index(app, empty_event_queues):
62+
"""Ensure the loan-transitions events index exists.
63+
64+
The loan indexer requires this index to be present when indexing loans
65+
with stats extensions enabled. This fixture publishes a dummy event
66+
and processes it to trigger index creation.
67+
"""
68+
index_name = "events-stats-loan-transitions"
69+
70+
# Publish a dummy loan transition event to trigger index creation
71+
dummy_event = {
72+
"timestamp": datetime.datetime.now(datetime.timezone.utc)
73+
.replace(tzinfo=None)
74+
.isoformat(),
75+
"trigger": "extend",
76+
"pid_value": "loanid-1",
77+
"unique_id": "loanid-1__extend",
78+
}
79+
current_stats.publish("loan-transitions", [dummy_event])
80+
81+
# Process events to create the index
82+
process_events(["loan-transitions"])
83+
current_search.flush_and_refresh(index=index_name)

tests/api/ils/stats/test_loan_stats.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ def test_loan_stats_histogram_group_by_document_availability(
198198
json_headers,
199199
testdata_loan_histogram,
200200
loan_params,
201+
with_stats_index_extensions,
201202
):
202203
"""Test that the availability of an item during loan request can be used for grouping loans in the histogram."""
203204

@@ -266,6 +267,7 @@ def test_loan_stats_indexed_fields(
266267
empty_event_queues,
267268
empty_search,
268269
testdata_loan_histogram,
270+
with_stats_index_extensions,
269271
):
270272
"""Test loan time ranges being indexed onto loans
271273

0 commit comments

Comments
 (0)