Skip to content

Commit 5d4c8dc

Browse files
raymond1242Raymond Negronmgonnav
authored
BITMAKER-2625 estela: Propose Model and implementation for Activity Menu (#160)
* Call save_action method in views and serializers * Refactor Activity and Notification code * Changed Project ordering to use the name field * Fixed serializers and migrations. * Update serializers and views to correctly use activity and notification * Fix cronjob "run once" method --------- Co-authored-by: Raymond Negron <raymond1242@Raymonds-MacBook-Air.local> Co-authored-by: mgonnav <mateo@emegona.com>
1 parent 04bf729 commit 5d4c8dc

File tree

36 files changed

+2037
-546
lines changed

36 files changed

+2037
-546
lines changed

estela-api/api/mixins.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from rest_framework.permissions import IsAuthenticated
66

77
from api.permissions import IsProjectUser, IsAdminOrReadOnly
8-
from core.models import Notification
8+
from core.models import Notification, Activity
99

1010

1111
class APIPageNumberPagination(PageNumberPagination):
@@ -22,14 +22,13 @@ class BaseViewSet(viewsets.GenericViewSet):
2222
pagination_class = APIPageNumberPagination
2323

2424

25-
class NotificationsHandlerMixin:
26-
def save_notification(self, user, message, project):
27-
notification = Notification(
28-
message=message,
29-
user=user,
30-
project=project,
25+
class ActionHandlerMixin:
26+
def save_action(self, user, description, project):
27+
activity = Activity.objects.create(
28+
user=user, project=project, description=description
3129
)
32-
notification.save()
30+
31+
notifications = []
3332
for _user in project.users.all():
34-
notification.users.add(_user, through_defaults={"seen": False})
35-
notification.save()
33+
notifications.append(Notification(user=_user, activity=activity))
34+
Notification.objects.bulk_create(notifications)

estela-api/api/serializers/cronjob.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
from croniter import croniter
22
from rest_framework import serializers
33

4-
from api import errors
5-
6-
from api.mixins import NotificationsHandlerMixin
4+
from api.mixins import ActionHandlerMixin
75
from api.serializers.job_specific import (
86
SpiderJobArgSerializer,
97
SpiderJobEnvVarSerializer,
@@ -119,7 +117,8 @@ def create(self, validated_data):
119117

120118

121119
class SpiderCronJobUpdateSerializer(
122-
serializers.ModelSerializer, NotificationsHandlerMixin
120+
serializers.ModelSerializer,
121+
ActionHandlerMixin,
123122
):
124123
def validate(self, attrs):
125124
attrs = super(SpiderCronJobUpdateSerializer, self).validate(attrs)
@@ -149,35 +148,38 @@ def update(self, instance, validated_data):
149148
data_status = validated_data.get("data_status", "")
150149
data_expiry_days = int(validated_data.get("data_expiry_days", 1))
151150
name = instance.name
152-
message = ""
151+
description = ""
152+
153153
if "schedule" in validated_data:
154154
instance.schedule = schedule
155155
update_schedule(name, schedule)
156-
message = f"changed the schedule of Scheduled-job-{instance.cjid}."
156+
description = f"changed the schedule of Sche-Job-{instance.cjid}."
157+
157158
if "status" in validated_data:
158159
instance.status = status
159160
if status == SpiderCronJob.ACTIVE_STATUS:
160161
enable_cronjob(name)
161-
message = f"enabled Scheduled-job-{instance.cjid}."
162+
description = f"enabled Sche-Job-{instance.cjid}."
162163
elif status == SpiderCronJob.DISABLED_STATUS:
163164
disable_cronjob(name)
164-
message = f"disabled Scheduled-job-{instance.cjid}."
165+
description = f"disabled Sche-Job-{instance.cjid}."
166+
165167
if "unique_collection" in validated_data:
166168
instance.unique_collection = unique_collection
169+
167170
if "data_status" in validated_data:
168171
if data_status == DataStatus.PERSISTENT_STATUS:
169172
instance.data_status = DataStatus.PERSISTENT_STATUS
170-
message = f"changed data persistence of Scheduled-job-{instance.cjid} to persistent."
173+
description = f"changed data persistence of Sche-Job-{instance.cjid} to persistent."
171174
elif data_status == DataStatus.PENDING_STATUS and data_expiry_days > 0:
172175
instance.data_status = DataStatus.PENDING_STATUS
173176
instance.data_expiry_days = data_expiry_days
174-
message = f"changed data persistence of Scheduled-job-{instance.cjid} to {data_expiry_days} days."
175-
else:
176-
raise serializers.ValidationError({"error": errors.INVALID_DATA_STATUS})
177+
days_ = data_expiry_days
178+
description = f"changed data persistence of Sche-Job-{instance.cjid} to {days_} days."
177179

178-
self.save_notification(
180+
self.save_action(
179181
user=user,
180-
message=message,
182+
description=description,
181183
project=instance.spider.project,
182184
)
183185
instance.save()

estela-api/api/serializers/notification.py

Lines changed: 7 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,21 @@
11
from rest_framework import serializers
22

3-
from api.serializers.project import ProjectDetailSerializer, UserDetailSerializer
4-
from core.models import Notification, UserNotification
3+
from api.serializers.project import ActivitySerializer
4+
from core.models import Notification
55

66

77
class NotificationSerializer(serializers.ModelSerializer):
8-
user = UserDetailSerializer(
9-
required=True, help_text="User who performed the action."
10-
)
11-
project = ProjectDetailSerializer(
12-
required=True, help_text="Project where the action was performed."
13-
)
8+
activity = ActivitySerializer(required=True, help_text="Activity being notified.")
149

1510
class Meta:
1611
model = Notification
17-
fields = ("nid", "message", "user", "project")
12+
fields = ("nid", "activity", "seen")
1813

1914

20-
class UserNotificationSerializer(serializers.ModelSerializer):
21-
id = serializers.IntegerField(
22-
required=True, help_text="Unique user notification ID."
23-
)
24-
notification = NotificationSerializer(
25-
required=True, help_text="Notification to which the user is subscribed."
26-
)
27-
28-
class Meta:
29-
model = UserNotification
30-
fields = ("id", "notification", "seen", "created")
31-
32-
def to_representation(self, instance):
33-
ret = super().to_representation(instance)
34-
user = instance.notification.user
35-
36-
is_superuser = user.is_superuser or user.is_staff
37-
user_in_project = instance.notification.project.users.filter(id=user.id)
38-
if is_superuser and not user_in_project:
39-
ret["notification"]["user"]["username"] = "Bitmaker Cloud Admin"
40-
else:
41-
ret["notification"]["user"][
42-
"username"
43-
] = user.username
44-
return ret
45-
46-
47-
class UserNotificationUpdateSerializer(serializers.ModelSerializer):
48-
seen = serializers.BooleanField(
49-
required=False,
50-
help_text="Whether the user has seen the notification.",
51-
)
52-
15+
class NotificationUpdateSerializer(serializers.ModelSerializer):
5316
class Meta:
54-
model = UserNotification
55-
fields = ("id", "seen")
17+
model = Notification
18+
fields = ("nid", "seen")
5619

5720
def update(self, instance, validated_data):
5821
instance.seen = validated_data.get("seen", instance.seen)

estela-api/api/serializers/project.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.contrib.auth.models import User
22
from rest_framework import serializers
33

4-
from core.models import DataStatus, Permission, Project, UsageRecord
4+
from core.models import DataStatus, Permission, Project, UsageRecord, Activity
55

66

77
class UserDetailSerializer(serializers.ModelSerializer):
@@ -162,3 +162,41 @@ class Meta:
162162
"data_status",
163163
"data_expiry_days",
164164
)
165+
166+
167+
class ActivitySerializer(serializers.ModelSerializer):
168+
user = UserDetailSerializer(
169+
required=True, help_text="User who performed the deploy."
170+
)
171+
project = ProjectDetailSerializer(
172+
required=True, help_text="Project where the activity was performed."
173+
)
174+
175+
class Meta:
176+
model = Activity
177+
fields = (
178+
"user",
179+
"project",
180+
"description",
181+
"created",
182+
)
183+
184+
def to_representation(self, instance):
185+
ret = super().to_representation(instance)
186+
user = instance.user
187+
188+
is_superuser = user.is_superuser or user.is_staff
189+
user_in_project = instance.project.users.filter(id=user.id)
190+
if is_superuser and not user_in_project:
191+
ret["user"]["username"] = user.username + " (admin)"
192+
193+
return ret
194+
195+
196+
class ProjectActivitySerializer(serializers.Serializer):
197+
results = ActivitySerializer(
198+
many=True, required=True, help_text="Project activities."
199+
)
200+
count = serializers.IntegerField(
201+
required=True, help_text="Project activities count."
202+
)

estela-api/api/urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
)
2121
router.register(
2222
prefix=r"notifications",
23-
viewset=notification_views.UserNotificationViewSet,
23+
viewset=notification_views.NotificationViewSet,
2424
basename="notification",
2525
)
2626
router.register(

estela-api/api/views/cronjob.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from rest_framework.response import Response
99

1010
from api.filters import SpiderCronJobFilter
11-
from api.mixins import BaseViewSet, NotificationsHandlerMixin
11+
from api.mixins import BaseViewSet, ActionHandlerMixin
1212
from api.serializers.cronjob import (
1313
SpiderCronJobCreateSerializer,
1414
SpiderCronJobSerializer,
@@ -20,7 +20,7 @@
2020

2121
class SpiderCronJobViewSet(
2222
BaseViewSet,
23-
NotificationsHandlerMixin,
23+
ActionHandlerMixin,
2424
mixins.CreateModelMixin,
2525
mixins.RetrieveModelMixin,
2626
mixins.UpdateModelMixin,
@@ -90,11 +90,10 @@ def create(self, request, *args, **kwargs):
9090
data_expiry_days=data_expiry_days,
9191
)
9292

93-
# Send notification action
9493
project = get_object_or_404(Project, pid=self.kwargs["pid"])
95-
self.save_notification(
94+
self.save_action(
9695
user=request.user,
97-
message=f"scheduled a new Scheduled-job-{cronjob.cjid} for spider {spider.name}.",
96+
description=f"scheduled a new Sche-Job-{cronjob.cjid} for spider {spider.name}.",
9897
project=project,
9998
)
10099

@@ -128,6 +127,11 @@ def update(self, request, *args, **kwargs):
128127
def destroy(self, request, *args, **kwargs):
129128
instance = self.get_object()
130129
self.perform_destroy(instance)
130+
self.save_action(
131+
user=request.user,
132+
description=f"deleted Sche-Job-{instance.cjid} for spider {instance.spider.name}.",
133+
project=instance.spider.project,
134+
)
131135
return Response(status=status.HTTP_204_NO_CONTENT)
132136

133137
def perform_destroy(self, instance):

estela-api/api/views/deploy.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from rest_framework.response import Response
55
from rest_framework.exceptions import ParseError, APIException, PermissionDenied
66

7-
from api.mixins import BaseViewSet, NotificationsHandlerMixin
7+
from api.mixins import BaseViewSet, ActionHandlerMixin
88
from api.serializers.deploy import (
99
DeploySerializer,
1010
DeployCreateSerializer,
@@ -19,8 +19,8 @@
1919

2020
class DeployViewSet(
2121
BaseViewSet,
22-
NotificationsHandlerMixin,
2322
viewsets.ModelViewSet,
23+
ActionHandlerMixin,
2424
):
2525
model_class = Deploy
2626
serializer_class = DeploySerializer
@@ -94,9 +94,9 @@ def update(self, request, *args, **kwargs):
9494

9595
# Send action notification
9696
project = get_object_or_404(Project, pid=self.kwargs["pid"])
97-
self.save_notification(
97+
self.save_action(
9898
user=instance.user,
99-
message=f"made a new Deploy #{serializer.data['did']}.",
99+
description=f"made a new Deploy #{serializer.data['did']}.",
100100
project=project,
101101
)
102102

estela-api/api/views/job.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from rest_framework.exceptions import ParseError
88

99
from api.filters import SpiderJobFilter
10-
from api.mixins import BaseViewSet, NotificationsHandlerMixin
10+
from api.mixins import BaseViewSet, ActionHandlerMixin
1111
from api.serializers.job import (
1212
SpiderJobSerializer,
1313
SpiderJobCreateSerializer,
@@ -19,7 +19,7 @@
1919

2020
class SpiderJobViewSet(
2121
BaseViewSet,
22-
NotificationsHandlerMixin,
22+
ActionHandlerMixin,
2323
mixins.CreateModelMixin,
2424
mixins.RetrieveModelMixin,
2525
mixins.UpdateModelMixin,
@@ -140,9 +140,9 @@ def create(self, request, *args, **kwargs):
140140

141141
# Send action notification
142142
project = get_object_or_404(Project, pid=self.kwargs["pid"])
143-
self.save_notification(
143+
self.save_action(
144144
user=request.user,
145-
message=f"run a new job for spider {spider.name}.",
145+
description=f"run Job-{serializer.data['jid']} for spider {spider.name}.",
146146
project=project,
147147
)
148148

estela-api/api/views/notification.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,30 @@
44

55
from api.mixins import BaseViewSet
66
from api.serializers.notification import (
7-
UserNotificationSerializer,
8-
UserNotificationUpdateSerializer,
7+
NotificationSerializer,
8+
NotificationUpdateSerializer,
99
)
10-
from core.models import UserNotification
10+
from core.models import Notification
1111

1212

13-
class UserNotificationViewSet(BaseViewSet, viewsets.ModelViewSet):
14-
model_class = UserNotification
15-
serializer_class = UserNotificationSerializer
16-
queryset = UserNotification.objects.all()
13+
class NotificationViewSet(BaseViewSet, viewsets.ModelViewSet):
14+
model_class = Notification
15+
serializer_class = NotificationSerializer
16+
queryset = Notification.objects.all()
1717

1818
def get_queryset(self):
1919
if self.request is None:
20-
return UserNotification.objects.none()
21-
return UserNotification.objects.filter(user=self.request.user)
20+
return Notification.objects.none()
21+
return Notification.objects.filter(user=self.request.user).order_by("-activity__created")
2222

2323
@swagger_auto_schema(
24-
request_body=UserNotificationUpdateSerializer,
25-
responses={status.HTTP_200_OK: UserNotificationUpdateSerializer()},
24+
request_body=NotificationUpdateSerializer,
25+
responses={status.HTTP_200_OK: NotificationUpdateSerializer()},
2626
)
2727
def update(self, request, *args, **kwargs):
2828
partial = kwargs.pop("partial", False)
2929
instance = self.get_object()
30-
serializer = UserNotificationUpdateSerializer(
30+
serializer = NotificationUpdateSerializer(
3131
instance, data=request.data, partial=partial
3232
)
3333
serializer.is_valid(raise_exception=True)

0 commit comments

Comments
 (0)