Skip to content

Commit

Permalink
feat: add fields to store when an event was acknowledged and by whom (#…
Browse files Browse the repository at this point in the history
…274)

- add acknowledged_ts and acknowledged_by to events table
- Dockerfile-dev: update FROM image
  • Loading branch information
Bruno Lenzi committed Aug 7, 2023
1 parent fa2624d commit 91cac34
Show file tree
Hide file tree
Showing 7 changed files with 37 additions and 6 deletions.
2 changes: 1 addition & 1 deletion src/Dockerfile-dev
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM pyroapi:python3.8-alpine3.10
FROM pyronear/pyro-api:python3.8-alpine3.10

# copy requirements file
COPY requirements-dev.txt requirements-dev.txt
Expand Down
7 changes: 6 additions & 1 deletion src/app/api/endpoints/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# This program is licensed under the Apache License 2.0.
# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.

from datetime import datetime
from typing import List, cast

from fastapi import APIRouter, Depends, Path, Security, status
Expand Down Expand Up @@ -103,7 +104,11 @@ async def acknowledge_event(
"""
requested_group_id = await get_entity_group_id(events, event_id)
await check_group_update(requester.id, cast(int, requested_group_id))
return await crud.update_entry(events, Acknowledgement(is_acknowledged=True), event_id)
return await crud.update_entry(
events,
Acknowledgement(is_acknowledged=True, acknowledged_by=requester.id, acknowledged_ts=datetime.utcnow()),
event_id,
)


@router.delete("/{event_id}/", response_model=EventOut, summary="Delete a specific event")
Expand Down
5 changes: 4 additions & 1 deletion src/app/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import enum

from sqlalchemy import Boolean, Column, DateTime, Enum, Float, Integer
from sqlalchemy import Boolean, Column, DateTime, Enum, Float, ForeignKey, Integer
from sqlalchemy.orm import RelationshipProperty, relationship
from sqlalchemy.sql import func

Expand All @@ -28,9 +28,12 @@ class Event(Base):
start_ts = Column(DateTime, default=func.now())
end_ts = Column(DateTime, default=None, nullable=True)
is_acknowledged = Column(Boolean, default=False)
acknowledged_by = Column(Integer, ForeignKey("users.id"))
acknowledged_ts = Column(DateTime, default=None, nullable=True)
created_at = Column(DateTime, default=func.now())

alerts: RelationshipProperty = relationship("Alert", back_populates="event")
acknowledger: RelationshipProperty = relationship("User", back_populates="acknowledged_events")

def __repr__(self):
return (
Expand Down
1 change: 1 addition & 0 deletions src/app/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class User(Base):

access: RelationshipProperty = relationship("Access", uselist=False, back_populates="user")
device: RelationshipProperty = relationship("Device", uselist=False, back_populates="owner")
acknowledged_events: RelationshipProperty = relationship("Event", back_populates="acknowledger")

def __repr__(self):
return f"<User(login='{self.login}', created_at='{self.created_at}'>"
7 changes: 7 additions & 0 deletions src/app/schemas/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ class EventIn(_FlatLocation):
None, description="timestamp of event end", example=datetime.utcnow().replace(tzinfo=None)
)
is_acknowledged: bool = Field(False, description="whether the event has been acknowledged")
acknowledged_by: Optional[int] = Field(None, description="id of the user who acknowledged the event")
acknowledged_ts: Optional[datetime] = Field(None, description="event acknowledgement timestamp")

_validate_start_ts = validator("start_ts", pre=True, always=True, allow_reuse=True)(validate_datetime_none)
_validate_end_ts = validator("end_ts", pre=True, always=True, allow_reuse=True)(validate_datetime_none)
_validate_ack_ts = validator("acknowledged_ts", pre=True, always=True, allow_reuse=True)(validate_datetime_none)


class EventOut(EventIn, _CreatedAt, _Id):
Expand All @@ -36,6 +39,10 @@ class EventOut(EventIn, _CreatedAt, _Id):

class Acknowledgement(BaseModel):
is_acknowledged: bool = Field(False, description="whether the event has been acknowledged")
acknowledged_by: Optional[int] = Field(None, description="id of the user who acknowledged the event")
acknowledged_ts: Optional[datetime] = Field(None, description="event acknowledgement timestamp")

_validate_ack_ts = validator("acknowledged_ts", pre=True, always=True, allow_reuse=True)(validate_datetime_none)


class AcknowledgementOut(Acknowledgement, _Id):
Expand Down
17 changes: 15 additions & 2 deletions src/tests/routes/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"start_ts": "2020-09-13T08:18:45.447773",
"end_ts": "2020-09-13T08:18:45.447773",
"is_acknowledged": True,
"acknowledged_by": 2,
"acknowledged_ts": "2020-09-13T08:18:45.447773",
"created_at": "2020-10-13T08:18:45.447773",
},
{
Expand All @@ -34,6 +36,8 @@
"start_ts": "2020-09-13T08:18:45.447773",
"end_ts": None,
"is_acknowledged": True,
"acknowledged_by": 2,
"acknowledged_ts": "2020-09-13T08:18:45.447773",
"created_at": "2020-09-13T08:18:45.447773",
},
{
Expand All @@ -44,6 +48,8 @@
"start_ts": "2021-03-13T08:18:45.447773",
"end_ts": "2021-03-13T10:18:45.447773",
"is_acknowledged": False,
"acknowledged_by": None,
"acknowledged_ts": None,
"created_at": "2020-09-13T08:18:45.447773",
},
]
Expand Down Expand Up @@ -274,10 +280,14 @@ async def test_create_event(test_app_asyncio, init_test_db, test_db, access_idx,
if response.status_code // 100 == 2:
json_response = response.json()
test_response = {"id": len(EVENT_TABLE) + 1, **payload, "end_ts": None, "is_acknowledged": False}
assert {k: v for k, v in json_response.items() if k not in ("created_at", "start_ts")} == test_response
assert {
k: v
for k, v in json_response.items()
if k not in ("created_at", "start_ts", "acknowledged_ts", "acknowledged_by")
} == test_response
new_event_in_db = await get_entry(test_db, db.events, json_response["id"])
new_event_in_db = dict(**new_event_in_db)
assert new_event_in_db["created_at"] > utc_dt and new_event_in_db["created_at"] < datetime.utcnow()
assert utc_dt < new_event_in_db["created_at"] < datetime.utcnow()


@pytest.mark.parametrize(
Expand Down Expand Up @@ -474,6 +484,7 @@ async def test_acknowledge_event(
if isinstance(access_idx, int):
auth = await pytest.get_token(ACCESS_TABLE[access_idx]["id"], ACCESS_TABLE[access_idx]["scope"].split())

utc_dt = datetime.utcnow()
response = await test_app_asyncio.put(f"/events/{event_id}/acknowledge", headers=auth)
assert response.status_code == status_code
if isinstance(status_details, str):
Expand All @@ -483,6 +494,8 @@ async def test_acknowledge_event(
updated_event = await get_entry(test_db, db.events, event_id)
updated_event = dict(**updated_event)
assert updated_event["is_acknowledged"]
assert updated_event["acknowledged_by"] == ACCESS_TABLE[access_idx]["id"]
assert utc_dt < updated_event["acknowledged_ts"] < datetime.utcnow()


@pytest.mark.parametrize(
Expand Down
4 changes: 3 additions & 1 deletion src/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

def update_only_datetime(entity_as_dict: Dict[str, Any]):
return {
k: parse_time(v) if isinstance(v, str) and k in ("created_at", "start_ts", "end_ts", "last_ping") else v
k: parse_time(v)
if isinstance(v, str) and k in ("created_at", "start_ts", "end_ts", "last_ping", "acknowledged_ts")
else v
for k, v in entity_as_dict.items()
}

Expand Down

0 comments on commit 91cac34

Please sign in to comment.