mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 15:27:17 +00:00
feat: allow filter events by timestamp
This commit is contained in:
parent
cedf18ac7a
commit
8fc4d7b4a5
@ -30,6 +30,7 @@ class EventOperations(Operations):
|
||||
"""
|
||||
|
||||
def event_get(self, event: str | EventType | None = None, object_id: str | None = None,
|
||||
from_date: int | None = None, to_date: int | None = None,
|
||||
limit: int = -1, offset: int = 0, repository_id: RepositoryId | None = None) -> list[Event]:
|
||||
"""
|
||||
get list of events with filters applied
|
||||
@ -37,6 +38,8 @@ class EventOperations(Operations):
|
||||
Args:
|
||||
event(str | EventType | None, optional): filter by event type (Default value = None)
|
||||
object_id(str | None, optional): filter by event object (Default value = None)
|
||||
from_date(int | None, optional): minimal creation date, inclusive (Default value = None)
|
||||
to_date(int | None, optional): maximal creation date, exclusive (Default value = None)
|
||||
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
||||
offset(int, optional): records offset (Default value = 0)
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
@ -55,6 +58,8 @@ class EventOperations(Operations):
|
||||
select * from auditlog
|
||||
where (:event is null or event = :event)
|
||||
and (:object_id is null or object_id = :object_id)
|
||||
and (:from_date is null or created >= :from_date)
|
||||
and (:to_date is null or created < :to_date)
|
||||
and repository = :repository
|
||||
order by created desc limit :limit offset :offset
|
||||
) order by created asc
|
||||
@ -63,6 +68,8 @@ class EventOperations(Operations):
|
||||
"event": event,
|
||||
"object_id": object_id,
|
||||
"repository": repository_id.id,
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
}
|
||||
|
@ -93,6 +93,7 @@ class Client:
|
||||
raise NotImplementedError
|
||||
|
||||
def event_get(self, event: str | EventType | None, object_id: str | None,
|
||||
from_date: int | None = None, to_date: int | None = None,
|
||||
limit: int = -1, offset: int = 0) -> list[Event]:
|
||||
"""
|
||||
retrieve list of events
|
||||
@ -100,6 +101,8 @@ class Client:
|
||||
Args:
|
||||
event(str | EventType | None): filter by event type
|
||||
object_id(str | None): filter by event object
|
||||
from_date(int | None, optional): minimal creation date, inclusive (Default value = None)
|
||||
to_date(int | None, optional): maximal creation date, exclusive (Default value = None)
|
||||
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
||||
offset(int, optional): records offset (Default value = 0)
|
||||
|
||||
|
@ -59,6 +59,7 @@ class LocalClient(Client):
|
||||
self.database.event_insert(event, self.repository_id)
|
||||
|
||||
def event_get(self, event: str | EventType | None, object_id: str | None,
|
||||
from_date: int | None = None, to_date: int | None = None,
|
||||
limit: int = -1, offset: int = 0) -> list[Event]:
|
||||
"""
|
||||
retrieve list of events
|
||||
@ -66,13 +67,15 @@ class LocalClient(Client):
|
||||
Args:
|
||||
event(str | EventType | None): filter by event type
|
||||
object_id(str | None): filter by event object
|
||||
from_date(int | None, optional): minimal creation date, inclusive (Default value = None)
|
||||
to_date(int | None, optional): maximal creation date, exclusive (Default value = None)
|
||||
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
||||
offset(int, optional): records offset (Default value = 0)
|
||||
|
||||
Returns:
|
||||
list[Event]: list of audit log events
|
||||
"""
|
||||
return self.database.event_get(event, object_id, limit, offset, self.repository_id)
|
||||
return self.database.event_get(event, object_id, from_date, to_date, limit, offset, self.repository_id)
|
||||
|
||||
def package_changes_get(self, package_base: str) -> Changes:
|
||||
"""
|
||||
|
@ -71,7 +71,7 @@ class Watcher(LazyLogging):
|
||||
|
||||
event_add: Callable[[Event], None]
|
||||
|
||||
event_get: Callable[[str | EventType | None, str | None, int, int], list[Event]]
|
||||
event_get: Callable[[str | EventType | None, str | None, int | None, int | None, int, int], list[Event]]
|
||||
|
||||
def load(self) -> None:
|
||||
"""
|
||||
|
@ -178,6 +178,7 @@ class WebClient(Client, SyncAhrimanClient):
|
||||
self.make_request("POST", self._events_url(), params=self.repository_id.query(), json=event.view())
|
||||
|
||||
def event_get(self, event: str | EventType | None, object_id: str | None,
|
||||
from_date: int | None = None, to_date: int | None = None,
|
||||
limit: int = -1, offset: int = 0) -> list[Event]:
|
||||
"""
|
||||
retrieve list of events
|
||||
@ -185,6 +186,8 @@ class WebClient(Client, SyncAhrimanClient):
|
||||
Args:
|
||||
event(str | EventType | None): filter by event type
|
||||
object_id(str | None): filter by event object
|
||||
from_date(int | None, optional): minimal creation date, inclusive (Default value = None)
|
||||
to_date(int | None, optional): maximal creation date, exclusive (Default value = None)
|
||||
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
||||
offset(int, optional): records offset (Default value = 0)
|
||||
|
||||
@ -196,6 +199,10 @@ class WebClient(Client, SyncAhrimanClient):
|
||||
query.append(("event", str(event)))
|
||||
if object_id is not None:
|
||||
query.append(("object_id", object_id))
|
||||
if from_date is not None:
|
||||
query.append(("from_date", str(from_date)))
|
||||
if to_date is not None:
|
||||
query.append(("to_date", str(to_date)))
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
response = self.make_request("GET", self._events_url(), params=query)
|
||||
|
@ -36,3 +36,11 @@ class EventSearchSchema(PaginationSchema):
|
||||
"description": "Event object identifier",
|
||||
"example": "ahriman",
|
||||
})
|
||||
from_date = fields.Integer(metadata={
|
||||
"description": "Minimal creation timestamp, inclusive",
|
||||
"example": 1680537091,
|
||||
})
|
||||
to_date = fields.Integer(metadata={
|
||||
"description": "Maximal creation timestamp, exclusive",
|
||||
"example": 1680537091,
|
||||
})
|
||||
|
@ -158,7 +158,7 @@ class BaseView(View, CorsViewMixin):
|
||||
value = extractor(key)
|
||||
if not value:
|
||||
raise KeyError(key)
|
||||
except Exception:
|
||||
except (KeyError, ValueError):
|
||||
raise KeyError(f"Key {key} is missing or empty") from None
|
||||
return value
|
||||
|
||||
@ -194,7 +194,7 @@ class BaseView(View, CorsViewMixin):
|
||||
try:
|
||||
limit = int(self.request.query.get("limit", default=-1))
|
||||
offset = int(self.request.query.get("offset", default=0))
|
||||
except Exception as ex:
|
||||
except ValueError as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
# some checks
|
||||
|
@ -64,8 +64,16 @@ class EventsView(BaseView):
|
||||
limit, offset = self.page()
|
||||
event = self.request.query.get("event") or None
|
||||
object_id = self.request.query.get("object_id") or None
|
||||
try:
|
||||
from_date = to_date = None
|
||||
if (value := self.request.query.get("from_date")) is not None:
|
||||
from_date = int(value)
|
||||
if (value := self.request.query.get("to_date")) is not None:
|
||||
to_date = int(value)
|
||||
except ValueError as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
events = self.service().event_get(event, object_id, limit, offset)
|
||||
events = self.service().event_get(event, object_id, from_date, to_date, limit, offset)
|
||||
response = [event.view() for event in events]
|
||||
|
||||
return json_response(response)
|
||||
|
@ -29,8 +29,9 @@ def test_event_get(local_client: LocalClient, package_ahriman: Package, mocker:
|
||||
must retrieve events
|
||||
"""
|
||||
event_mock = mocker.patch("ahriman.core.database.SQLite.event_get")
|
||||
local_client.event_get(EventType.PackageUpdated, package_ahriman.base, 1, 2)
|
||||
event_mock.assert_called_once_with(EventType.PackageUpdated, package_ahriman.base, 1, 2, local_client.repository_id)
|
||||
local_client.event_get(EventType.PackageUpdated, package_ahriman.base, from_date=10, to_date=20, limit=1, offset=2)
|
||||
event_mock.assert_called_once_with(EventType.PackageUpdated, package_ahriman.base, 10, 20, 1, 2,
|
||||
local_client.repository_id)
|
||||
|
||||
|
||||
def test_package_changes_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
|
@ -185,7 +185,7 @@ def test_event_get_filter(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj)
|
||||
|
||||
web_client.event_get("event", "object", 1, 2)
|
||||
web_client.event_get("event", "object", limit=1, offset=2)
|
||||
requests_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True),
|
||||
params=web_client.repository_id.query() + [
|
||||
("limit", "1"),
|
||||
@ -195,6 +195,28 @@ def test_event_get_filter(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
])
|
||||
|
||||
|
||||
def test_event_get_filter_from_to(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get events with filter by creation date
|
||||
"""
|
||||
response_obj = requests.Response()
|
||||
response_obj._content = json.dumps(Event("", "").view()).encode("utf8")
|
||||
response_obj.status_code = 200
|
||||
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj)
|
||||
|
||||
web_client.event_get("event", "object", from_date=1, to_date=2)
|
||||
requests_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True),
|
||||
params=web_client.repository_id.query() + [
|
||||
("limit", "-1"),
|
||||
("offset", "0"),
|
||||
("event", "event"),
|
||||
("object_id", "object"),
|
||||
("from_date", "1"),
|
||||
("to_date", "2"),
|
||||
])
|
||||
|
||||
|
||||
def test_event_get_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during events fetch
|
||||
|
@ -51,7 +51,6 @@ async def test_get_with_pagination(client: TestClient) -> None:
|
||||
await client.post("/api/v1/events", json=event1.view())
|
||||
await client.post("/api/v1/events", json=event2.view())
|
||||
request_schema = pytest.helpers.schema_request(EventsView.get, location="querystring")
|
||||
response_schema = pytest.helpers.schema_response(EventsView.get)
|
||||
|
||||
payload = {"limit": 1, "offset": 1}
|
||||
assert not request_schema.validate(payload)
|
||||
@ -59,8 +58,25 @@ async def test_get_with_pagination(client: TestClient) -> None:
|
||||
assert response.status == 200
|
||||
|
||||
json = await response.json()
|
||||
assert not response_schema.validate(json, many=True)
|
||||
assert [Event.from_json(event) for event in json] == [event1]
|
||||
|
||||
|
||||
async def test_get_with_filter(client: TestClient) -> None:
|
||||
"""
|
||||
must get events with filter by creation date
|
||||
"""
|
||||
event1 = Event("event1", "object1", "message", key="value", created=1)
|
||||
event2 = Event("event2", "object2", created=2)
|
||||
await client.post("/api/v1/events", json=event1.view())
|
||||
await client.post("/api/v1/events", json=event2.view())
|
||||
request_schema = pytest.helpers.schema_request(EventsView.get, location="querystring")
|
||||
|
||||
payload = {"from_date": 1, "to_date": 2}
|
||||
assert not request_schema.validate(payload)
|
||||
response = await client.get("/api/v1/events", params=payload)
|
||||
assert response.status == 200
|
||||
|
||||
json = await response.json()
|
||||
assert [Event.from_json(event) for event in json] == [event1]
|
||||
|
||||
|
||||
@ -78,6 +94,14 @@ async def test_get_bad_request(client: TestClient) -> None:
|
||||
assert response.status == 400
|
||||
assert not response_schema.validate(await response.json())
|
||||
|
||||
response = await client.get("/api/v1/events", params={"from_date": "from_date"})
|
||||
assert response.status == 400
|
||||
assert not response_schema.validate(await response.json())
|
||||
|
||||
response = await client.get("/api/v1/events", params={"to_date": "to_date"})
|
||||
assert response.status == 400
|
||||
assert not response_schema.validate(await response.json())
|
||||
|
||||
|
||||
async def test_post(client: TestClient) -> None:
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user