From 3f563d377214bcae6ef00cb472b7b2e031e226b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Rol=C3=B3n?= <37310205+Angatupyry@users.noreply.github.com> Date: Fri, 30 Jun 2023 14:22:51 +0000 Subject: [PATCH] Feature/acknowledged by showed in task logs (#721) * Create function to save acknowledged task completion in logs Signed-off-by: angatupyry * Save ack by in logs Signed-off-by: angatupyry * Remove unused import Signed-off-by: angatupyry * Use instance of Phases instead of dict in get task logs functions Signed-off-by: angatupyry * Get phases key correctly and add comments to describe changes Signed-off-by: angatupyry * Add comment to describe code changes Signed-off-by: angatupyry * Lint warning and erros fixed Signed-off-by: angatupyry * Unused import Signed-off-by: angatupyry * remove the construction of task_repo Signed-off-by: angatupyry * Revert changes Signed-off-by: angatupyry * Adding try catch to handle error Signed-off-by: angatupyry * Lint fix Signed-off-by: angatupyry * Remove instance of TaskRepository and pass it as params in Alert Signed-off-by: angatupyry * Add task repository dependency in alert repository Signed-off-by: angatupyry --------- Signed-off-by: angatupyry --- .../api_server/repositories/alerts.py | 26 ++++++-- .../api_server/repositories/tasks.py | 61 ++++++++++++++++++- .../api-server/api_server/routes/internal.py | 2 +- 3 files changed, 81 insertions(+), 8 deletions(-) diff --git a/packages/api-server/api_server/repositories/alerts.py b/packages/api-server/api_server/repositories/alerts.py index 739be4019..9cb17d928 100644 --- a/packages/api-server/api_server/repositories/alerts.py +++ b/packages/api-server/api_server/repositories/alerts.py @@ -7,11 +7,15 @@ from api_server.logger import logger from api_server.models import User from api_server.models import tortoise_models as ttm +from api_server.repositories.tasks import TaskRepository, task_repo_dep class AlertRepository: - def __init__(self, user: User): + def __init__(self, user: User, task_repo: Optional[TaskRepository]): self.user = user + self.task_repo = ( + task_repo if task_repo is not None else TaskRepository(self.user) + ) async def get_all_alerts(self) -> List[ttm.AlertPydantic]: alerts = await ttm.Alert.all() @@ -71,17 +75,31 @@ async def acknowledge_alert(self, alert_id: str) -> Optional[ttm.AlertPydantic]: # https://github.com/tortoise/tortoise-orm/pull/1131. This is a # temporary workaround. ack_alert._custom_generated_pk = True # pylint: disable=W0212 + unix_millis_acknowledged_time = round(ack_time.timestamp() * 1e3) ack_alert.update_from_dict( { "acknowledged_by": self.user.username, - "unix_millis_acknowledged_time": round(ack_time.timestamp() * 1e3), + "unix_millis_acknowledged_time": unix_millis_acknowledged_time, } ) await ack_alert.save() + + # Save in logs who was the user that acknowledged the task + try: + await self.task_repo.save_log_acknowledged_task_completion( + alert.id, self.user.username, unix_millis_acknowledged_time + ) + except Exception as e: + raise RuntimeError( + f"Error in save_log_acknowledged_task_completion {e}" + ) from e + await alert.delete() ack_alert_pydantic = await ttm.AlertPydantic.from_tortoise_orm(ack_alert) return ack_alert_pydantic -def alert_repo_dep(user: User = Depends(user_dep)): - return AlertRepository(user) +def alert_repo_dep( + user: User = Depends(user_dep), task_repo: TaskRepository = Depends(task_repo_dep) +): + return AlertRepository(user, task_repo) diff --git a/packages/api-server/api_server/repositories/tasks.py b/packages/api-server/api_server/repositories/tasks.py index 23b2b92a8..bec1d0bb9 100644 --- a/packages/api-server/api_server/repositories/tasks.py +++ b/packages/api-server/api_server/repositories/tasks.py @@ -1,3 +1,4 @@ +import sys from datetime import datetime from typing import Dict, List, Optional, Sequence, Tuple, cast @@ -18,8 +19,11 @@ User, ) from api_server.models import tortoise_models as ttm +from api_server.models.rmf_api.log_entry import Tier +from api_server.models.rmf_api.task_state import Category, Id, Phase from api_server.models.tortoise_models import TaskState as DbTaskState from api_server.query import add_pagination +from api_server.rmf_io import task_events class TaskRepository: @@ -94,12 +98,12 @@ async def get_task_log( return None phases = {} for db_phase in result.phases: - phase = {} - phase["log"] = [LogEntry(**dict(x)) for x in db_phase.log] + phase = Phases(log=None, events=None) + phase.log = [LogEntry(**dict(x)) for x in db_phase.log] events = {} for db_event in db_phase.events: events[db_event.event] = [LogEntry(**dict(x)) for x in db_event.log] - phase["events"] = events + phase.events = events phases[db_phase.phase] = phase return TaskEventLog.construct( task_id=result.task_id, @@ -153,6 +157,57 @@ async def _saveTaskLogs( text=log.text, ) + async def save_log_acknowledged_task_completion( + self, task_id: str, acknowledged_by: str, unix_millis_acknowledged_time: int + ) -> None: + async with in_transaction(): + task_logs = await self.get_task_log(task_id, (0, sys.maxsize)) + task_state = await self.get_task_state(task_id=task_id) + # A try could be used here to avoid using so many "ands" + # but the configured lint suggests comparing that no value is None + if task_logs and task_state and task_logs.phases and task_state.phases: + # The next phase key value matches in both `task_logs` and `task_state`. + # It is the same whether it is obtained from `task_logs` or from `task_state`. + # In this case, it is obtained from `task_logs` and is also used to assign the next + # phase in `task_state`. + next_phase_key = str(int(list(task_logs.phases)[-1]) + 1) + else: + raise ValueError("Phases can't be null") + + event = LogEntry( + seq=0, + tier=Tier.warning, + unix_millis_time=unix_millis_acknowledged_time, + text=f"Task completion acknowledged by {acknowledged_by}", + ) + task_logs.phases = { + **task_logs.phases, + next_phase_key: Phases(log=[], events={"0": [event]}), + } + + await self.save_task_log(task_logs) + + task_state.phases = { + **task_state.phases, + next_phase_key: Phase( + id=Id(__root__=next_phase_key), + category=Category(__root__="Task completed"), + detail=None, + unix_millis_start_time=None, + unix_millis_finish_time=None, + original_estimate_millis=None, + estimate_millis=None, + final_event_id=None, + events=None, + skip_requests=None, + ), + } + + await self.save_task_state(task_state) + # Notifies observers of the next task_state value to correctly display the title of the + # logs when acknowledged by a user without reloading the page. + task_events.task_states.on_next(task_state) + async def save_task_log(self, task_log: TaskEventLog) -> None: async with in_transaction(): db_task_log = ( diff --git a/packages/api-server/api_server/routes/internal.py b/packages/api-server/api_server/routes/internal.py index 0bb1898be..77d253a27 100644 --- a/packages/api-server/api_server/routes/internal.py +++ b/packages/api-server/api_server/routes/internal.py @@ -12,7 +12,7 @@ logger = base_logger.getChild("RmfGatewayApp") user: mdl.User = mdl.User(username="__rmf_internal__", is_admin=True) task_repo = TaskRepository(user) -alert_repo = AlertRepository(user) +alert_repo = AlertRepository(user, task_repo) def log_phase_has_error(phase: mdl.Phases) -> bool: