Skip to content
This repository has been archived by the owner on Nov 21, 2024. It is now read-only.

Commit

Permalink
author decisions: add decision table and populate it. implemented tas…
Browse files Browse the repository at this point in the history
…k in airflow that allows for this and added unit tests for all the changes

* ref: cern-sis/issues-inspire/issues/522
  • Loading branch information
DonHaul committed Aug 22, 2024
1 parent b80b0de commit 4278bf5
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 36 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test-workflows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
-v "$(pwd)"/tests:/opt/airflow/tests
-v "$(pwd)"/requirements-test.txt:/opt/airflow/requirements-test.txt
-v "$(pwd)"/data:/opt/airflow/data
-v "$(pwd)"/scripts/variables/variables.json:/opt/airflow/variables.json
-v "$(pwd)"/scripts:/opt/airflow/scripts
-e AIRFLOW__CORE__EXECUTOR=CeleryExecutor
-e AIRFLOW__DATABASE__SQL_ALCHEMY_CONN=postgresql+psycopg2://airflow:[email protected]:5432/airflow
-e AIRFLOW__CELERY__RESULT_BACKEND=db+postgresql://airflow:[email protected]:5432/airflow
Expand All @@ -61,4 +61,4 @@ jobs:
-e AIRFLOW__CORE__LOAD_EXAMPLES="false"
-e AIRFLOW__API__AUTH_BACKENDS="airflow.api.auth.backend.basic_auth,airflow.api.auth.backend.session"
registry.cern.ch/cern-sis/inspire/workflows@${{ needs.build.outputs.image-id }}
bash -c "pip install -r requirements-test.txt && airflow db init && airflow variables import /opt/airflow/variables.json && pytest /opt/airflow/tests"
bash -c "pip install -r requirements-test.txt && airflow db init && airflow connections import /opt/airflow/scripts/connections/connections.json && airflow variables import /opt/airflow/scripts/variables/variables.json && pytest /opt/airflow/tests"
79 changes: 50 additions & 29 deletions backoffice/backoffice/workflows/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django_json_widget.widgets import JSONEditorWidget

from backoffice.management.permissions import IsAdminOrCuratorUser
from backoffice.workflows.models import Workflow
from backoffice.workflows.models import Decision, Workflow


class WorkflowsAdminSite(admin.AdminSite):
Expand All @@ -30,8 +30,41 @@ def has_permission(self, request):
)


class BaseModelAdmin(admin.ModelAdmin):
def has_view_permission(self, request, obj=None):
"""
Returns True if the user has permission to view the Workflow model.
"""
permission_check = IsAdminOrCuratorUser()
return request.user.is_superuser or permission_check.has_permission(
request, self
)

def has_change_permission(self, request, obj=None):
"""
Returns True if the user has permission to change the Workflow model.
"""
permission_check = IsAdminOrCuratorUser()
return request.user.is_superuser or permission_check.has_permission(
request, self
)

def has_delete_permission(self, request, obj=None):
"""
Returns True if the user has permission to delete the Workflow model.
"""
permission_check = IsAdminOrCuratorUser()
return request.user.is_superuser or permission_check.has_permission(
request, self
)

formfield_overrides = {
JSONField: {"widget": JSONEditorWidget},
}


@admin.register(Workflow)
class WorkflowAdmin(admin.ModelAdmin):
class WorkflowAdmin(BaseModelAdmin):
"""
Admin class for Workflow model. Define get, update and delete permissions.
"""
Expand All @@ -56,33 +89,21 @@ class WorkflowAdmin(admin.ModelAdmin):
"_updated_at",
]

formfield_overrides = {
JSONField: {"widget": JSONEditorWidget},
}

def has_view_permission(self, request, obj=None):
"""
Returns True if the user has permission to view the Workflow model.
"""
permission_check = IsAdminOrCuratorUser()
return request.user.is_superuser or permission_check.has_permission(
request, self
)
@admin.register(Decision)
class DecisionAdmin(BaseModelAdmin):
"""
Admin class for Decision model. Define get, update and delete permissions.
"""

def has_change_permission(self, request, obj=None):
"""
Returns True if the user has permission to change the Workflow model.
"""
permission_check = IsAdminOrCuratorUser()
return request.user.is_superuser or permission_check.has_permission(
request, self
)
ordering = ("-_updated_at",)
search_fields = ["id", "data"]
list_display = ("id", "action_value", "user", "workflow_id")
list_filter = [
"action",
"user",
]

def has_delete_permission(self, request, obj=None):
"""
Returns True if the user has permission to delete the Workflow model.
"""
permission_check = IsAdminOrCuratorUser()
return request.user.is_superuser or permission_check.has_permission(
request, self
)
@admin.display(description="action")
def action_value(self, obj):
return obj.action
10 changes: 9 additions & 1 deletion backoffice/backoffice/workflows/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,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 WorkflowTicketSerializer(serializers.ModelSerializer):
Expand All @@ -31,6 +31,14 @@ class Meta:
fields = "__all__"


class DecisionSerializer(serializers.ModelSerializer):
workflow = serializers.PrimaryKeyRelatedField(queryset=Workflow.objects.all())

class Meta:
model = Decision
fields = "__all__"


class WorkflowDocumentSerializer(DocumentSerializer):
class Meta:
document = WorkflowDocument
Expand Down
11 changes: 11 additions & 0 deletions backoffice/backoffice/workflows/api/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from backoffice.workflows.api.serializers import DecisionSerializer


def add_decision(workflow_id, user, action):
serializer_class = DecisionSerializer
data = {"workflow": workflow_id, "user": user, "action": action}

serializer = serializer_class(data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return serializer.data
16 changes: 14 additions & 2 deletions backoffice/backoffice/workflows/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from backoffice.utils.pagination import OSStandardResultsSetPagination
from backoffice.workflows import airflow_utils
from backoffice.workflows.api import utils
from backoffice.workflows.api.serializers import (
AuthorResolutionSerializer,
WorkflowAuthorSerializer,
Expand All @@ -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__)

Expand Down Expand Up @@ -100,6 +101,16 @@ def create(self, request, *args, **kwargs):
)


class DecisionViewSet(viewsets.ModelViewSet):
queryset = Decision.objects.all()

def create(self, request, *args, **kwargs):
data = utils.add_decision(
request.data["workflow_id"], request.user, request.data["action"]
)
return Response(data, status=status.HTTP_201_CREATED)


class AuthorWorkflowViewSet(viewsets.ViewSet):
serializer_class = WorkflowAuthorSerializer

Expand Down Expand Up @@ -160,12 +171,13 @@ def resolve(self, request, pk=None):
logger.info("Resolving data: %s", request.data)
serializer = AuthorResolutionSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
extra_data = {"create_ticket": serializer.validated_data["create_ticket"]}
extra_data = serializer.validated_data
logger.info(
"Trigger Airflow DAG: %s for %s",
ResolutionDags[serializer.validated_data["value"]],
pk,
)
utils.add_decision(pk, request.user, serializer.validated_data["value"])

return airflow_utils.trigger_airflow_dag(
ResolutionDags[serializer.validated_data["value"]].label, pk, extra_data
Expand Down
4 changes: 4 additions & 0 deletions backoffice/backoffice/workflows/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ 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"


DECISION_CHOICES = ResolutionDags.choices


class AuthorCreateDags(models.TextChoices):
Expand Down
58 changes: 58 additions & 0 deletions backoffice/backoffice/workflows/migrations/0009_decision.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Generated by Django 4.2.6 on 2024-08-15 12:25

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",
models.ForeignKey(
db_column="email",
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
to_field="email",
),
),
(
"workflow",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="workflows.workflow",
),
),
],
),
]
13 changes: 13 additions & 0 deletions backoffice/backoffice/workflows/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from django.db import models

from backoffice.users.models import User
from backoffice.workflows.constants import (
DECISION_CHOICES,
DEFAULT_STATUS_CHOICE,
DEFAULT_TICKET_TYPE,
DEFAULT_WORKFLOW_TYPE,
Expand Down Expand Up @@ -43,3 +45,14 @@ class WorkflowTicket(models.Model):
ticket_type = models.CharField(
max_length=30, choices=TICKET_TYPES, default=DEFAULT_TICKET_TYPE
)


class Decision(models.Model):
user = models.ForeignKey(
User, to_field="email", db_column="email", on_delete=models.CASCADE
)
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE)
action = models.CharField(max_length=30, choices=DECISION_CHOICES)

_created_at = models.DateTimeField(auto_now_add=True)
_updated_at = models.DateTimeField(auto_now=True)
47 changes: 47 additions & 0 deletions backoffice/backoffice/workflows/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import uuid

import pytest
from django.apps import apps
from django.contrib.auth import get_user_model
from django.test import TransactionTestCase
from rest_framework.exceptions import ValidationError

from backoffice.workflows import constants
from backoffice.workflows.api import utils
from backoffice.workflows.constants import StatusChoices

User = get_user_model()
Workflow = apps.get_model(app_label="workflows", model_name="Workflow")


class TestUtils(TransactionTestCase):
reset_sequences = True
fixtures = ["backoffice/fixtures/groups.json"]

def setUp(self):
super().setUp()
self.workflow = Workflow.objects.create(
data={}, status=StatusChoices.APPROVAL, core=True, is_update=False
)
self.user = User.objects.create_user(
email="[email protected]", password="12345"
)

def test_add_decision(self):
decision_data = utils.add_decision(
self.workflow.id, self.user, constants.ResolutionDags.accept
)
import ipdb

ipdb.set_trace()

self.assertIsNotNone(decision_data)

def test_add_decision_validation_errors(self):
with pytest.raises(ValidationError):
utils.add_decision(self.workflow.id, self.user, "wrong")

with pytest.raises(ValidationError):
utils.add_decision(
uuid.UUID(int=0), self.user, constants.ResolutionDags.accept
)
Loading

0 comments on commit 4278bf5

Please sign in to comment.