diff --git a/backoffice/backoffice/workflows/api/serializers.py b/backoffice/backoffice/workflows/api/serializers.py index cabb7cb4..0fca9029 100644 --- a/backoffice/backoffice/workflows/api/serializers.py +++ b/backoffice/backoffice/workflows/api/serializers.py @@ -4,7 +4,7 @@ from backoffice.workflows.constants import ResolutionDags, StatusChoices, WorkflowType from backoffice.workflows.documents import WorkflowDocument -from backoffice.workflows.models import Workflow, WorkflowTicket +from backoffice.workflows.models import Decision, Workflow, WorkflowTicket class WorkflowSerializer(serializers.ModelSerializer): @@ -19,6 +19,12 @@ class Meta: fields = "__all__" +class DecisionSerializer(serializers.ModelSerializer): + class Meta: + model = Decision + fields = "__all__" + + class WorkflowDocumentSerializer(DocumentSerializer): class Meta: document = WorkflowDocument diff --git a/backoffice/backoffice/workflows/api/views.py b/backoffice/backoffice/workflows/api/views.py index c3d81806..e2554df7 100644 --- a/backoffice/backoffice/workflows/api/views.py +++ b/backoffice/backoffice/workflows/api/views.py @@ -25,6 +25,7 @@ from backoffice.workflows import airflow_utils from backoffice.workflows.api.serializers import ( AuthorResolutionSerializer, + DecisionSerializer, WorkflowAuthorSerializer, WorkflowDocumentSerializer, WorkflowSerializer, @@ -37,7 +38,7 @@ WorkflowType, ) from backoffice.workflows.documents import WorkflowDocument -from backoffice.workflows.models import Workflow, WorkflowTicket +from backoffice.workflows.models import Decision, Workflow, WorkflowTicket logger = logging.getLogger(__name__) @@ -100,6 +101,43 @@ def create(self, request, *args, **kwargs): ) +class DecisionViewSet(viewsets.ViewSet): + def retrieve(self, request, *args, **kwargs): + workflow_id = kwargs.get("pk") + try: + decision = Decision.objects.get(workflow_id=workflow_id) + serializer = DecisionSerializer(decision) + return Response(serializer.data) + except Decision.DoesNotExist: + return Response( + {"error": "Workflow ticket not found."}, + status=status.HTTP_404_NOT_FOUND, + ) + + def create(self, request, *args, **kwargs): + workflow_id = request.data.get("workflow_id") + action = request.data.get("action") + user_id = request.data.get("user_id") + + if not all([workflow_id, action, user_id]): + return Response( + {"error": "Workflow_id, ticket_id and ticket_type are required."}, + status=status.HTTP_400_BAD_REQUEST, + ) + + try: + workflow = Workflow.objects.get(id=workflow_id) + decision = Decision.objects.create( + workflow_id=workflow, user_id=user_id, action=action + ) + serializer = DecisionSerializer(decision) + return Response(serializer.data, status=status.HTTP_201_CREATED) + except Exception as e: + return Response( + {"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) + + class AuthorWorkflowViewSet(viewsets.ViewSet): serializer_class = WorkflowAuthorSerializer diff --git a/backoffice/backoffice/workflows/constants.py b/backoffice/backoffice/workflows/constants.py index 273faeac..6cb4c352 100644 --- a/backoffice/backoffice/workflows/constants.py +++ b/backoffice/backoffice/workflows/constants.py @@ -33,6 +33,7 @@ class WorkflowType(models.TextChoices): class ResolutionDags(models.TextChoices): accept = "accept", "author_create_approved_dag" reject = "reject", "author_create_rejected_dag" + accept_curate = "accept_curate", "author_create_approved_dag" class AuthorCreateDags(models.TextChoices): diff --git a/backoffice/backoffice/workflows/migrations/0009_decision.py b/backoffice/backoffice/workflows/migrations/0009_decision.py new file mode 100644 index 00000000..dcefd6d3 --- /dev/null +++ b/backoffice/backoffice/workflows/migrations/0009_decision.py @@ -0,0 +1,56 @@ +# Generated by Django 4.2.6 on 2024-08-13 08:38 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("workflows", "0008_alter_workflow_status_alter_workflow_workflow_type"), + ] + + operations = [ + migrations.CreateModel( + name="Decision", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "action", + models.CharField( + choices=[ + ("accept", "author_create_approved_dag"), + ("reject", "author_create_rejected_dag"), + ("accept_curate", "author_create_approved_dag"), + ], + max_length=30, + ), + ), + ("_created_at", models.DateTimeField(auto_now_add=True)), + ("_updated_at", models.DateTimeField(auto_now=True)), + ( + "user_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "workflow_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="workflows.workflow", + ), + ), + ], + ), + ] diff --git a/backoffice/backoffice/workflows/models.py b/backoffice/backoffice/workflows/models.py index 265641cb..bf9866e8 100644 --- a/backoffice/backoffice/workflows/models.py +++ b/backoffice/backoffice/workflows/models.py @@ -2,11 +2,13 @@ from django.db import models +from backoffice.users.models import User from backoffice.workflows.constants import ( DEFAULT_STATUS_CHOICE, DEFAULT_TICKET_TYPE, DEFAULT_WORKFLOW_TYPE, TICKET_TYPES, + ResolutionDags, StatusChoices, WorkflowType, ) @@ -41,3 +43,12 @@ class WorkflowTicket(models.Model): ticket_type = models.CharField( max_length=30, choices=TICKET_TYPES, default=DEFAULT_TICKET_TYPE ) + + +class Decision(models.Model): + user_id = models.ForeignKey(User, on_delete=models.CASCADE) + workflow_id = models.ForeignKey(Workflow, on_delete=models.CASCADE) + action = models.CharField(max_length=30, choices=ResolutionDags.choices) + + _created_at = models.DateTimeField(auto_now_add=True) + _updated_at = models.DateTimeField(auto_now=True) diff --git a/backoffice/config/api_router.py b/backoffice/config/api_router.py index 179a686b..c5bbafa0 100644 --- a/backoffice/config/api_router.py +++ b/backoffice/config/api_router.py @@ -4,6 +4,7 @@ from backoffice.users.api.views import UserViewSet from backoffice.workflows.api.views import ( AuthorWorkflowViewSet, + DecisionViewSet, WorkflowTicketViewSet, WorkflowViewSet, ) @@ -20,5 +21,6 @@ ) router.register("workflows", WorkflowViewSet, basename="workflows") (router.register("workflow-ticket", WorkflowTicketViewSet, basename="workflow-ticket"),) +router.register("decision", DecisionViewSet, basename="decisions") app_name = "api" urlpatterns = router.urls diff --git a/workflows/dags/author/author_create/shared_tasks.py b/workflows/dags/author/author_create/shared_tasks.py new file mode 100644 index 00000000..6c79a3f9 --- /dev/null +++ b/workflows/dags/author/author_create/shared_tasks.py @@ -0,0 +1,12 @@ +from airflow.decorators import task +from hooks.backoffice.decision_management_hook import DecisionManagementHook + + +@task() +def create_decision_on_curation_choice(**context): + print("create_decision_on_curation_choice") + print(context["params"]) + DecisionManagementHook().create_decision_entry( + workflow_id=context["params"]["workflow_id"], + ticket_type="author_update_curation", + ) diff --git a/workflows/plugins/hooks/backoffice/decision_management_hook.py b/workflows/plugins/hooks/backoffice/decision_management_hook.py new file mode 100644 index 00000000..67c6274d --- /dev/null +++ b/workflows/plugins/hooks/backoffice/decision_management_hook.py @@ -0,0 +1,38 @@ +from hooks.backoffice.base import BackofficeHook +from requests import Response + + +class WorkflowTicketManagementHook(BackofficeHook): + """ + A hook to update the status of a workflow in the backoffice system. + + :param method: The HTTP method to use for the request (default: "GET"). + :type method: str + :param http_conn_id: The ID of the HTTP connection to use ( + default: "backoffice_conn"). + :type http_conn_id: str + """ + + def __init__( + self, + method: str = "GET", + http_conn_id: str = "backoffice_conn", + headers: dict = None, + ) -> None: + super().__init__(method, http_conn_id, headers) + self.endpoint = "api/decision/" + + def create_decision_entry( + self, workflow_id: str, user_id: str, action: str + ) -> Response: + data = { + "user_id": user_id, + "action": action, + "workflow_id": workflow_id, + } + return self.run_with_advanced_retry( + _retry_args=self.tenacity_retry_kwargs, + method="POST", + data=data, + endpoint=self.endpoint, + ) diff --git a/workflows/plugins/hooks/backoffice/workflow_ticket_management_hook.py b/workflows/plugins/hooks/backoffice/workflow_ticket_management_hook.py index 0f194588..017c2976 100644 --- a/workflows/plugins/hooks/backoffice/workflow_ticket_management_hook.py +++ b/workflows/plugins/hooks/backoffice/workflow_ticket_management_hook.py @@ -2,9 +2,10 @@ from requests import Response -class WorkflowTicketManagementHook(BackofficeHook): +class DecisionManagementHook(BackofficeHook): """ - A hook to update the status of a workflow in the backoffice system. + A hook to add entries to the decision log + for this workflow in the backoffice system. :param method: The HTTP method to use for the request (default: "GET"). :type method: str