From 4a5fdf1ef570d5ded2f467427970537c2c6b9b65 Mon Sep 17 00:00:00 2001 From: Mirek Simek Date: Wed, 11 Dec 2024 19:58:10 +0100 Subject: [PATCH 1/3] Added ?all=true filter that returns all requests user has access to --- oarepo_requests/invenio_patches.py | 24 ++++++++++++++++++- setup.cfg | 2 +- .../test_requests/test_param_interpreters.py | 6 +++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/oarepo_requests/invenio_patches.py b/oarepo_requests/invenio_patches.py index 0e922b0..6d2afbe 100644 --- a/oarepo_requests/invenio_patches.py +++ b/oarepo_requests/invenio_patches.py @@ -9,12 +9,13 @@ from __future__ import annotations -from functools import cached_property +from functools import cached_property, partial from typing import TYPE_CHECKING, Any, Callable from flask_resources import JSONSerializer, ResponseHandler from invenio_records_resources.resources.records.headers import etag_headers from invenio_records_resources.services.records.params import FilterParam +from invenio_records_resources.services.records.params.base import ParamInterpreter from invenio_requests.resources.events.config import RequestCommentsResourceConfig from invenio_requests.resources.requests.config import ( RequestSearchRequestArgsSchema, @@ -53,6 +54,25 @@ def apply(self, identity: Identity, search: Query, params: dict[str, str]) -> Qu return search +class RequestAllAvailableFilterParam(ParamInterpreter): + """A no-op filter that returns all requests that are readable by the current user.""" + + def __init__(self, param_name, config): + """Initialize the filter.""" + self.param_name = param_name + super().__init__(config) + + @classmethod + def factory(cls, param=None): + """Create a new filter parameter.""" + return partial(cls, param) + + def apply(self, identity, search, params): + """Apply the filter to the search - does nothing.""" + params.pop(self.param_name, None) + return search + + class RequestNotOwnerFilterParam(FilterParam): """Filter requests that are not owned by the current user. @@ -89,6 +109,7 @@ class EnhancedRequestSearchOptions(RequestSearchOptions): params_interpreters_cls = RequestSearchOptions.params_interpreters_cls + [ RequestOwnerFilterParam.factory("mine", "created_by.user"), RequestNotOwnerFilterParam.factory("assigned", "created_by.user"), + RequestAllAvailableFilterParam.factory("all"), IsClosedParam.factory("is_closed"), ] @@ -98,6 +119,7 @@ class ExtendedRequestSearchRequestArgsSchema(RequestSearchRequestArgsSchema): mine = fields.Boolean() assigned = fields.Boolean() + all = fields.Boolean() is_closed = fields.Boolean() diff --git a/setup.cfg b/setup.cfg index 94136fd..6a62c84 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = oarepo-requests -version = 2.3.9 +version = 2.3.10 description = authors = Ronald Krist readme = README.md diff --git a/tests/test_requests/test_param_interpreters.py b/tests/test_requests/test_param_interpreters.py index 94cfe4c..976dffa 100644 --- a/tests/test_requests/test_param_interpreters.py +++ b/tests/test_requests/test_param_interpreters.py @@ -5,6 +5,7 @@ # modify it under the terms of the MIT License; see LICENSE file for more # details. # +import json from tests.test_requests.utils import link2testclient @@ -77,6 +78,11 @@ def test_owner_param_interpreter( assert search_user2_only.json["hits"]["hits"][0]["created_by"] == {"user": "2"} assert search_user2_only.json["hits"]["hits"][0]["type"] == "another_topic_updating" + # mine requests should be in all=true as well + search_user1_only = user1_client.get(f'{urls["BASE_URL_REQUESTS"]}?all=true') + print(json.dumps(search_user1_only.json)) + for hit in search_user1_only.json["hits"]["hits"]: + assert hit['created_by'] == {"user": "1"} or hit['receiver'] == {"user": "1"} def test_open_param_interpreter( logged_client, From 39b7316ee0abe94681aeac301c8c1f66f8341fd4 Mon Sep 17 00:00:00 2001 From: Mirek Simek Date: Wed, 11 Dec 2024 21:19:25 +0100 Subject: [PATCH 2/3] date fixes --- oarepo_requests/resources/ui.py | 7 ++++--- oarepo_requests/services/ui_schema.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/oarepo_requests/resources/ui.py b/oarepo_requests/resources/ui.py index 33558d5..e0481b2 100644 --- a/oarepo_requests/resources/ui.py +++ b/oarepo_requests/resources/ui.py @@ -47,9 +47,10 @@ def _reference_map_from_list(obj_list: list[dict]) -> dict[str, set]: for reference_type in reference_types: if reference_type in hit: reference = hit[reference_type] - reference_map[list(reference.keys())[0]].add( - list(reference.values())[0] - ) + if reference: + reference_map[list(reference.keys())[0]].add( + list(reference.values())[0] + ) return reference_map diff --git a/oarepo_requests/services/ui_schema.py b/oarepo_requests/services/ui_schema.py index 8554398..a5008f1 100644 --- a/oarepo_requests/services/ui_schema.py +++ b/oarepo_requests/services/ui_schema.py @@ -9,6 +9,7 @@ from __future__ import annotations +import datetime from types import SimpleNamespace from typing import TYPE_CHECKING, Any, cast @@ -110,9 +111,26 @@ class UIRequestSchemaMixin: status_code = ma.fields.String() """Status code of the request.""" + @ma.pre_dump + def _convert_dates_for_localized(self, data: dict, **kwargs: Any) -> dict: + if isinstance(data.get("created"), str): + data["created"] = datetime.datetime.fromisoformat(data["created"]) + + if isinstance(data.get("updated"), str): + data["updated"] = datetime.datetime.fromisoformat(data["updated"]) + + if isinstance(data.get("expires_at"), str): + data["expires_at"] = datetime.datetime.fromisoformat(data["expires_at"]) + + return data + @ma.pre_dump def _add_type_details(self, data: dict, **kwargs: Any) -> dict: """Add details taken from the request type to the serialized request.""" + if isinstance(data.get("created"), str): + data["created"] = datetime.datetime.fromisoformat(data["created"]) + if isinstance(data.get("updated"), str): + data["updated"] = datetime.datetime.fromisoformat(data["updated"]) type = data["type"] type_obj = current_request_type_registry.lookup(type, quiet=True) if hasattr(type_obj, "description"): From abe9f28e4e547d632acfa617a654d0689dca6308 Mon Sep 17 00:00:00 2001 From: Mirek Simek Date: Wed, 11 Dec 2024 21:44:14 +0100 Subject: [PATCH 3/3] Rendering fields only if they are filled in --- .../common/components/SideRequestInfo.jsx | 84 ++++++++++--------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/SideRequestInfo.jsx b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/SideRequestInfo.jsx index 2b3350c..08db19e 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/SideRequestInfo.jsx +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/SideRequestInfo.jsx @@ -10,44 +10,48 @@ export const SideRequestInfo = ({ request }) => { const statusIcon = getRequestStatusIcon(request?.status_code); return ( - - {i18next.t("Creator")} - - - - {_has(request, "links.created_by_html") ? ( - - {request.created_by.label} - - ) : ( - request.created_by?.label - )} - - - - - {i18next.t("Receiver")} - - - - {_has(request, "links.receiver_html") ? ( - - {request?.receiver?.label} - - ) : ( - request?.receiver?.label - )} - - - + {request?.created_by && + + {i18next.t("Creator")} + + + + {_has(request, "links.created_by_html") ? ( + + {request.created_by.label} + + ) : ( + request.created_by?.label + )} + + + + } + {request?.receiver && + + {i18next.t("Receiver")} + + + + {_has(request, "links.receiver_html") ? ( + + {request?.receiver?.label} + + ) : ( + request?.receiver?.label + )} + + + + } {i18next.t("Status")} @@ -68,8 +72,8 @@ export const SideRequestInfo = ({ request }) => { {request?.topic?.label ? _truncate(request?.topic?.label, { - length: 350, - }) + length: 350, + }) : i18next.t("Request topic")}