Skip to content

Commit

Permalink
enhancing analytics (cvat-ai#8616)
Browse files Browse the repository at this point in the history
<!-- Raise an issue to propose your change
(https://github.com/cvat-ai/cvat/issues).
It helps to avoid duplication of efforts from multiple independent
contributors.
Discuss your ideas with maintainers to be sure that changes will be
approved and merged.
Read the [Contribution guide](https://docs.cvat.ai/docs/contributing/).
-->

<!-- Provide a general summary of your changes in the Title above -->

### Motivation and context
<!-- Why is this change required? What problem does it solve? If it
fixes an open
issue, please link to the issue here. Describe your changes in detail,
add
screenshots. -->
1. Minimize payload for create:tags, create:shapes, create:tracks,
update:tags, update:shapes, update:tracks, delete:tags, delete:shapes,
delete:tracks
2. sending update/create/delete for Memberships, Invitations, Webhooks

### How has this been tested?
<!-- Please describe in detail how you tested your changes.
Include details of your testing environment, and the tests you ran to
see how your change affects other areas of the code, etc. -->

### Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply.
If an item isn't applicable for some reason, then ~~explicitly
strikethrough~~ the whole
line. If you don't do that, GitHub will show incorrect progress for the
pull request.
If you're unsure about any of these, don't hesitate to ask. We're here
to help! -->
- [ ] I submit my changes into the `develop` branch
- [ ] I have created a changelog fragment <!-- see top comment in
CHANGELOG.md -->
- [ ] I have updated the documentation accordingly
- [ ] I have added tests to cover my changes
- [ ] I have linked related issues (see [GitHub docs](

https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword))
- [ ] I have increased versions of npm packages if it is necessary

([cvat-canvas](https://github.com/cvat-ai/cvat/tree/develop/cvat-canvas#versioning),

[cvat-core](https://github.com/cvat-ai/cvat/tree/develop/cvat-core#versioning),

[cvat-data](https://github.com/cvat-ai/cvat/tree/develop/cvat-data#versioning)
and

[cvat-ui](https://github.com/cvat-ai/cvat/tree/develop/cvat-ui#versioning))

### License

- [ ] I submit _my code changes_ under the same [MIT License](
https://github.com/cvat-ai/cvat/blob/develop/LICENSE) that covers the
project.
  Feel free to contact the maintainers if that's a concern.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Refined event logging by removing unnecessary fields from processed
data, enhancing clarity and efficiency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
Eldies authored Nov 26, 2024
1 parent e97ace2 commit a634f5a
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Added

- New events (create|update|delete):(membership|webhook) and (create|delete):invitation
(<https://github.com/cvat-ai/cvat/pull/8616>)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Changed

- Payload for events (create|update|delete):(shapes|tags|tracks) does not include frame and attributes anymore
(<https://github.com/cvat-ai/cvat/pull/8616>)
3 changes: 3 additions & 0 deletions cvat/apps/events/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class EventScopes:
"task": ["create", "update", "delete"],
"job": ["create", "update", "delete"],
"organization": ["create", "update", "delete"],
"membership": ["create", "update", "delete"],
"invitation": ["create", "delete"],
"user": ["create", "update", "delete"],
"cloudstorage": ["create", "update", "delete"],
"issue": ["create", "update", "delete"],
Expand All @@ -28,6 +30,7 @@ class EventScopes:
"label": ["create", "update", "delete"],
"dataset": ["export", "import"],
"function": ["call"],
"webhook": ["create", "update", "delete"],
}

@classmethod
Expand Down
76 changes: 45 additions & 31 deletions cvat/apps/events/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import datetime
import traceback
from typing import Optional, Union
from typing import Any, Optional, Union

import rq
from crum import get_current_request, get_current_user
Expand All @@ -26,6 +26,8 @@
MembershipReadSerializer,
OrganizationReadSerializer)
from cvat.apps.engine.rq_job_handler import RQJobMetaField
from cvat.apps.webhooks.models import Webhook
from cvat.apps.webhooks.serializers import WebhookReadSerializer

from .cache import get_cache
from .event import event_scope, record_server_event
Expand Down Expand Up @@ -66,6 +68,7 @@ def task_id(instance):
except Exception:
return None


def job_id(instance):
if isinstance(instance, Job):
return instance.id
Expand All @@ -78,6 +81,7 @@ def job_id(instance):
except Exception:
return None


def get_user(instance=None):
# Try to get current user from request
user = get_current_user()
Expand All @@ -97,6 +101,7 @@ def get_user(instance=None):

return None


def get_request(instance=None):
request = get_current_request()
if request is not None:
Expand All @@ -111,6 +116,7 @@ def get_request(instance=None):

return None


def _get_value(obj, key):
if obj is not None:
if isinstance(obj, dict):
Expand All @@ -119,22 +125,27 @@ def _get_value(obj, key):

return None


def request_id(instance=None):
request = get_request(instance)
return _get_value(request, "uuid")


def user_id(instance=None):
current_user = get_user(instance)
return _get_value(current_user, "id")


def user_name(instance=None):
current_user = get_user(instance)
return _get_value(current_user, "username")


def user_email(instance=None):
current_user = get_user(instance)
return _get_value(current_user, "email") or None


def organization_slug(instance):
if isinstance(instance, Organization):
return instance.slug
Expand All @@ -147,6 +158,7 @@ def organization_slug(instance):
except Exception:
return None


def get_instance_diff(old_data, data):
ignore_related_fields = (
"labels",
Expand All @@ -164,7 +176,8 @@ def get_instance_diff(old_data, data):

return diff

def _cleanup_fields(obj):

def _cleanup_fields(obj: dict[str, Any]) -> dict[str, Any]:
fields=(
"slug",
"id",
Expand All @@ -183,6 +196,7 @@ def _cleanup_fields(obj):
"url",
"issues",
"attributes",
"key",
)
subfields=(
"url",
Expand All @@ -198,6 +212,7 @@ def _cleanup_fields(obj):
data[k] = v
return data


def _get_object_name(instance):
if isinstance(instance, Organization) or \
isinstance(instance, Project) or \
Expand All @@ -217,34 +232,32 @@ def _get_object_name(instance):

return None


SERIALIZERS = [
(Organization, OrganizationReadSerializer),
(Project, ProjectReadSerializer),
(Task, TaskReadSerializer),
(Job, JobReadSerializer),
(User, BasicUserSerializer),
(CloudStorage, CloudStorageReadSerializer),
(Issue, IssueReadSerializer),
(Comment, CommentReadSerializer),
(Label, LabelSerializer),
(Membership, MembershipReadSerializer),
(Invitation, InvitationReadSerializer),
(Webhook, WebhookReadSerializer),
]


def get_serializer(instance):
context = {
"request": get_current_request()
}

serializer = None
if isinstance(instance, Organization):
serializer = OrganizationReadSerializer(instance=instance, context=context)
if isinstance(instance, Project):
serializer = ProjectReadSerializer(instance=instance, context=context)
if isinstance(instance, Task):
serializer = TaskReadSerializer(instance=instance, context=context)
if isinstance(instance, Job):
serializer = JobReadSerializer(instance=instance, context=context)
if isinstance(instance, User):
serializer = BasicUserSerializer(instance=instance, context=context)
if isinstance(instance, CloudStorage):
serializer = CloudStorageReadSerializer(instance=instance, context=context)
if isinstance(instance, Issue):
serializer = IssueReadSerializer(instance=instance, context=context)
if isinstance(instance, Comment):
serializer = CommentReadSerializer(instance=instance, context=context)
if isinstance(instance, Label):
serializer = LabelSerializer(instance=instance, context=context)
if isinstance(instance, Membership):
serializer = MembershipReadSerializer(instance=instance, context=context)
if isinstance(instance, Invitation):
serializer = InvitationReadSerializer(instance=instance, context=context)
for model, serializer_class in SERIALIZERS:
if isinstance(instance, model):
serializer = serializer_class(instance=instance, context=context)

return serializer

Expand All @@ -254,6 +267,7 @@ def get_serializer_without_url(instance):
serializer.fields.pop("url", None)
return serializer


def handle_create(scope, instance, **kwargs):
oid = organization_id(instance)
oslug = organization_slug(instance)
Expand Down Expand Up @@ -288,6 +302,7 @@ def handle_create(scope, instance, **kwargs):
payload=payload,
)


def handle_update(scope, instance, old_instance, **kwargs):
oid = organization_id(instance)
oslug = organization_slug(instance)
Expand Down Expand Up @@ -322,12 +337,14 @@ def handle_update(scope, instance, old_instance, **kwargs):
payload={"old_value": change["old_value"]},
)


def handle_delete(scope, instance, store_in_deletion_cache=False, **kwargs):
deletion_cache = get_cache()
instance_id = getattr(instance, "id", None)
if store_in_deletion_cache:
deletion_cache.set(
instance.__class__,
instance.id,
instance_id,
{
"oid": organization_id(instance),
"oslug": organization_slug(instance),
Expand All @@ -338,7 +355,7 @@ def handle_delete(scope, instance, store_in_deletion_cache=False, **kwargs):
)
return

instance_meta_info = deletion_cache.pop(instance.__class__, instance.id)
instance_meta_info = deletion_cache.pop(instance.__class__, instance_id)
if instance_meta_info:
oid = instance_meta_info["oid"]
oslug = instance_meta_info["oslug"]
Expand All @@ -360,7 +377,7 @@ def handle_delete(scope, instance, store_in_deletion_cache=False, **kwargs):
scope=scope,
request_id=request_id(),
on_commit=True,
obj_id=getattr(instance, 'id', None),
obj_id=instance_id,
obj_name=_get_object_name(instance),
org_id=oid,
org_slug=oslug,
Expand All @@ -372,15 +389,12 @@ def handle_delete(scope, instance, store_in_deletion_cache=False, **kwargs):
user_email=uemail,
)


def handle_annotations_change(instance, annotations, action, **kwargs):
def filter_data(data):
filtered_data = {
"id": data["id"],
"frame": data["frame"],
"attributes": data["attributes"],
}
if label_id := data.get("label_id"):
filtered_data["label_id"] = label_id

return filtered_data

Expand Down
35 changes: 24 additions & 11 deletions cvat/apps/events/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,30 @@
#
# SPDX-License-Identifier: MIT

from django.dispatch import receiver
from django.db.models.signals import pre_save, post_save, post_delete
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.signals import post_delete, post_save, pre_save
from django.dispatch import receiver

from cvat.apps.engine.models import (
TimestampedModel,
Project,
Task,
Job,
User,
CloudStorage,
Issue,
Comment,
Issue,
Job,
Label,
Project,
Task,
TimestampedModel,
User,
)
from cvat.apps.organizations.models import Organization
from cvat.apps.organizations.models import Invitation, Membership, Organization
from cvat.apps.webhooks.models import Webhook

from .handlers import handle_update, handle_create, handle_delete
from .event import EventScopeChoice, event_scope
from .handlers import handle_create, handle_delete, handle_update


@receiver(pre_save, sender=Webhook, dispatch_uid="webhook:update_receiver")
@receiver(pre_save, sender=Membership, dispatch_uid="membership:update_receiver")
@receiver(pre_save, sender=Organization, dispatch_uid="organization:update_receiver")
@receiver(pre_save, sender=Project, dispatch_uid="project:update_receiver")
@receiver(pre_save, sender=Task, dispatch_uid="task:update_receiver")
Expand All @@ -34,7 +38,8 @@
def resource_update(sender, *, instance, update_fields, **kwargs):
if (
isinstance(instance, TimestampedModel)
and update_fields and list(update_fields) == ["updated_date"]
and update_fields
and list(update_fields) == ["updated_date"]
):
# This is an optimization for the common case where only the date is bumped
# (see `TimestampedModel.touch`). Since the actual update of the field will
Expand All @@ -57,6 +62,10 @@ def resource_update(sender, *, instance, update_fields, **kwargs):

handle_update(scope=scope, instance=instance, old_instance=old_instance, **kwargs)


@receiver(post_save, sender=Webhook, dispatch_uid="webhook:create_receiver")
@receiver(post_save, sender=Membership, dispatch_uid="membership:create_receiver")
@receiver(post_save, sender=Invitation, dispatch_uid="invitation:create_receiver")
@receiver(post_save, sender=Organization, dispatch_uid="organization:create_receiver")
@receiver(post_save, sender=Project, dispatch_uid="project:create_receiver")
@receiver(post_save, sender=Task, dispatch_uid="task:create_receiver")
Expand All @@ -78,6 +87,10 @@ def resource_create(sender, instance, created, **kwargs):

handle_create(scope=scope, instance=instance, **kwargs)


@receiver(post_delete, sender=Webhook, dispatch_uid="webhook:delete_receiver")
@receiver(post_delete, sender=Membership, dispatch_uid="membership:delete_receiver")
@receiver(post_delete, sender=Invitation, dispatch_uid="invitation:delete_receiver")
@receiver(post_delete, sender=Organization, dispatch_uid="organization:delete_receiver")
@receiver(post_delete, sender=Project, dispatch_uid="project:delete_receiver")
@receiver(post_delete, sender=Task, dispatch_uid="task:delete_receiver")
Expand Down
1 change: 1 addition & 0 deletions dev/format_python_code.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ for paths in \
"cvat/apps/engine/model_utils.py" \
"cvat/apps/dataset_manager/tests/test_annotation.py" \
"cvat/apps/dataset_manager/tests/utils.py" \
"cvat/apps/events/signals.py" \
; do
${BLACK} -- ${paths}
${ISORT} -- ${paths}
Expand Down
6 changes: 6 additions & 0 deletions site/content/en/docs/administration/advanced/analytics.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ Server events:

- `call:function`

- `create:membership`, `update:membership`, `delete:membership`

- `create:webhook`, `update:webhook`, `delete:webhook`

- `create:invitation`, `delete:invitation`

Client events:

- `load:cvat`
Expand Down

0 comments on commit a634f5a

Please sign in to comment.