Skip to content

Commit

Permalink
Merge pull request #1440 from the-deep/features/assignment-rest-graphql
Browse files Browse the repository at this point in the history
implement graphql query and mutation node for assignment
  • Loading branch information
subinasr authored May 7, 2024
2 parents 0c1dc33 + 94ba125 commit 84fae41
Show file tree
Hide file tree
Showing 12 changed files with 528 additions and 18 deletions.
11 changes: 10 additions & 1 deletion apps/entry/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

from gallery.factories import FileFactory

from .models import Entry, Attribute
from .models import (
Entry,
Attribute,
EntryComment,
)


class EntryFactory(DjangoModelFactory):
Expand Down Expand Up @@ -39,3 +43,8 @@ class EntryAttributeFactory(DjangoModelFactory):

class Meta:
model = Attribute


class EntryCommentFactory(DjangoModelFactory):
class Meta:
model = EntryComment
77 changes: 77 additions & 0 deletions apps/notification/dataloaders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from django.utils.functional import cached_property
from django.db import models

from promise import Promise

from notification.models import Assignment
from lead.models import Lead
from quality_assurance.models import EntryReviewComment

from utils.graphene.dataloaders import DataLoaderWithContext, WithContextMixin


def get_model_name(model: models.Model) -> str:
return model._meta.model_name


class AssignmentLoader(DataLoaderWithContext):
def batch_load_fn(self, keys):
assignment_qs = list(
Assignment.objects
.filter(id__in=keys)
.values_list('id', 'content_type__model', 'object_id')
)

leads_id = []
entry_review_comment_id = []

for _, content_type, object_id in assignment_qs:
if content_type == get_model_name(Lead):
leads_id.append(object_id)
elif content_type == get_model_name(EntryReviewComment):
entry_review_comment_id.append(object_id)

_lead_id_map = {}

for _id, title in Lead.objects.filter(id__in=leads_id).values_list('id', 'title'):
_lead_id_map[_id] = dict(
id=_id,
title=title
)

_entry_review_comment_id_map = {}

for _id, entry_id, lead_id in EntryReviewComment.objects.filter(
id__in=entry_review_comment_id).values_list(
'id',
'entry__id',
'entry__lead_id'
):
_entry_review_comment_id_map[_id] = dict(
id=_id,
entry_id=entry_id,
lead_id=lead_id
)

_result = {
_id: {
'content_type': content_type,
'lead': (
_lead_id_map.get(object_id)
if content_type == get_model_name(Lead) else None
),
'entry_review_comment': (
_entry_review_comment_id_map.get(object_id)
if content_type == get_model_name(EntryReviewComment) else None
),
}
for _id, content_type, object_id in assignment_qs
}

return Promise.resolve([_result[key] for key in keys])


class DataLoaders(WithContextMixin):
@cached_property
def assignment(self):
return AssignmentLoader(context=self.context)
10 changes: 9 additions & 1 deletion apps/notification/enums.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import graphene

from utils.graphene.enums import (
convert_enum_to_graphene_enum,
get_enum_name_from_django_field,
)

from lead.models import Lead
from quality_assurance.models import EntryReviewComment
from .models import Notification

NotificationTypeEnum = convert_enum_to_graphene_enum(Notification.Type, name='NotificationTypeEnum')
Expand All @@ -15,3 +18,8 @@
(Notification.status, NotificationStatusEnum),
)
}


class AssignmentContentTypeEnum(graphene.Enum):
LEAD = Lead._meta.model_name
ENTRY_REVIEW_COMMENT = EntryReviewComment._meta.model_name
6 changes: 6 additions & 0 deletions apps/notification/factories.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
from factory.django import DjangoModelFactory

from notification.models import (
Assignment,
Notification,
)


class NotificationFactory(DjangoModelFactory):
class Meta:
model = Notification


class AssignmentFactory(DjangoModelFactory):
class Meta:
model = Assignment
7 changes: 6 additions & 1 deletion apps/notification/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,9 @@ class Meta:

@staticmethod
def get_for(user):
return Assignment.objects.filter(created_for=user).distinct()
from entry.models import EntryComment
return Assignment.objects.filter(
created_for=user,
).exclude(
content_type__model=EntryComment._meta.model_name
) # The EntryComment assignment are excluded need to remove later
47 changes: 43 additions & 4 deletions apps/notification/mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@

import graphene

from utils.graphene.mutation import generate_input_type_for_serializer
from utils.graphene.mutation import GrapheneMutation, generate_input_type_for_serializer
from utils.graphene.error_types import mutation_is_not_valid, CustomErrorType

from .serializers import NotificationGqSerializer
from .schema import NotificationType
from .models import Notification
from .serializers import AssignmentSerializer, NotificationGqSerializer
from .schema import AssignmentType, NotificationType
from .models import Assignment, Notification

NotificationStatusInputType = generate_input_type_for_serializer(
'NotificationStatusInputType',
serializer_class=NotificationGqSerializer

)

AssignmentInputType = generate_input_type_for_serializer(
'AssignmentInputType',
serializer_class=AssignmentSerializer
)


class NotificationStatusUpdate(graphene.Mutation):
class Arguments:
Expand Down Expand Up @@ -44,5 +49,39 @@ def mutate(root, info, data):
return NotificationStatusUpdate(result=instance, ok=True, errors=None)


class AssignmentUpdate(GrapheneMutation):
class Arguments:
id = graphene.ID(required=True)
data = AssignmentInputType(required=True)
model = Assignment
result = graphene.Field(AssignmentType)
serializer_class = AssignmentSerializer

@classmethod
def check_permissions(cls, info, **kwargs):
return True # global permissions is always True

@classmethod
def filter_queryset(cls, qs, info):
return Assignment.get_for(info.context.user)


class AsssignmentBulkStatusMarkAsDone(GrapheneMutation):
model = Assignment
serializer_class = AssignmentSerializer

@classmethod
def check_permissions(cls, info, **kwargs):
return True

@classmethod
def perform_mutate(cls, root, info, **kwargs):
instance = Assignment.get_for(info.context.user).filter(is_done=False)
instance.update(is_done=True)
return cls(ok=True)


class Mutation(object):
notification_status_update = NotificationStatusUpdate.Field()
assignment_update = AssignmentUpdate.Field()
assignment_bulk_status_mark_as_done = AsssignmentBulkStatusMarkAsDone.Field()
60 changes: 58 additions & 2 deletions apps/notification/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
from utils.graphene.fields import DjangoPaginatedListObjectField
from deep.trackers import track_user

from .models import Notification
from .filter_set import NotificationGqlFilterSet
from .models import Assignment, Notification
from .filter_set import NotificationGqlFilterSet, AssignmentFilterSet
from .enums import (
NotificationTypeEnum,
NotificationStatusEnum,
AssignmentContentTypeEnum
)


Expand All @@ -24,6 +25,11 @@ def get_user_notification_qs(info):
)


def get_user_assignment_qs(info):
track_user(info.context.request.user.profile)
return Assignment.get_for(track_user)


class NotificationType(DjangoObjectType):
class Meta:
model = Notification
Expand All @@ -41,12 +47,52 @@ def get_custom_queryset(queryset, info, **kwargs):
return get_user_notification_qs(info)


class AssignmentLeadDetailType(graphene.ObjectType):
id = graphene.ID(required=True)
title = graphene.String(required=True)


class AssignmentProjectDetailType(graphene.ObjectType):
id = graphene.ID(required=True)
title = graphene.String(required=True)


class AssignmentEntryReviewCommentDetailType(graphene.ObjectType):
id = graphene.ID(required=True)
entry_id = graphene.ID(required=True)
lead_id = graphene.ID(required=True)


class AssignmentContentDataType(graphene.ObjectType):
content_type = graphene.Field(AssignmentContentTypeEnum)
lead = graphene.Field(AssignmentLeadDetailType)
entry_review_comment = graphene.Field(AssignmentEntryReviewCommentDetailType)


class AssignmentType(DjangoObjectType):
class Meta:
model = Assignment
id = graphene.ID(required=True)
project = graphene.Field(AssignmentProjectDetailType)
content_data = graphene.Field(AssignmentContentDataType)

@staticmethod
def resolve_content_data(root, info):
return info.context.dl.notification.assignment.load(root.pk)


class NotificationListType(CustomDjangoListObjectType):
class Meta:
model = Notification
filterset_class = NotificationGqlFilterSet


class AssignmentListType(CustomDjangoListObjectType):
class Meta:
model = Assignment
filterset_class = AssignmentFilterSet


class Query:
notification = DjangoObjectField(NotificationType)
notifications = DjangoPaginatedListObjectField(
Expand All @@ -55,7 +101,17 @@ class Query:
page_size_query_param='pageSize'
)
)
assignments = DjangoPaginatedListObjectField(
AssignmentListType,
pagination=PageGraphqlPagination(
page_size_query_param='pageSize'
)
)

@staticmethod
def resolve_notifications(root, info, **kwargs) -> QuerySet:
return get_user_notification_qs(info)

@staticmethod
def resolve_assignments(root, info, **kwargs) -> QuerySet:
return Assignment.get_for(info.context.user)
17 changes: 14 additions & 3 deletions apps/notification/tests/test_apis.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest
from datetime import timedelta
from django.contrib.contenttypes.models import ContentType

from deep.tests import TestCase
from django.utils import timezone
Expand Down Expand Up @@ -505,12 +506,22 @@ def test_assignment_create_on_entry_comment_assignee_change(self):
assert data['count'] == 1 # assignment for user2

def test_assignment_is_done(self):
# XXX: To avoid using content type cache from pre-tests
ContentType.objects.clear_cache()

project = self.create(Project)
user1 = self.create(User)
user2 = self.create(User)
assignment = self.create(Assignment, created_for=user1, project=project, created_by=user2)
self.create(Assignment, created_for=user1, project=project, created_by=user2)
self.create(Assignment, created_for=user1, project=project, created_by=user2)
lead = self.create(Lead, project=project)
kwargs = {
'content_object': lead,
'project': project,
'created_for': user1,
'created_by': user2,
}
assignment = self.create(Assignment, **kwargs)
self.create(Assignment, **kwargs)
self.create(Assignment, **kwargs)

url = '/api/v1/assignments/'
self.authenticate(user1)
Expand Down
Loading

0 comments on commit 84fae41

Please sign in to comment.