Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opinons / Opinon Cluster Webhook #3779

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f6c2c0d
:construction: webhooks wip
jordanparker6 Feb 14, 2024
a31f05b
:construction: webhooks wip
jordanparker6 Feb 14, 2024
9f7f2ed
:construction: webhooks wip
jordanparker6 Feb 14, 2024
c59151f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 14, 2024
b70be78
Merge https://github.com/freelawproject/courtlistener into feature/op…
jordanparker6 Feb 14, 2024
33c6113
:hotfix: added migrations
jordanparker6 Feb 14, 2024
96975b1
Merge branch 'main' into feature/opinions-webhook
jordanparker6 Feb 14, 2024
64fab89
:hotfix: fixed typo
jordanparker6 Feb 14, 2024
37e61d9
Merge branch 'feature/opinions-webhook' of https://github.com/jordanp…
jordanparker6 Feb 14, 2024
9a0126f
Merge https://github.com/freelawproject/courtlistener into feature/op…
jordanparker6 Feb 27, 2024
e42f756
:sparkles: adding webhooks tasks and templates
jordanparker6 Feb 28, 2024
6e25d53
:sparkles: adding webhooks tasks and templates
jordanparker6 Feb 28, 2024
ba92762
Update cl/api/tasks.py
jordanparker6 Mar 12, 2024
1100095
Update cl/api/tasks.py
jordanparker6 Mar 12, 2024
be0e8fd
fixes
jordanparker6 Mar 12, 2024
a34249e
fixes
jordanparker6 Mar 13, 2024
1c06517
fixes
jordanparker6 Mar 13, 2024
83cb449
fixes
jordanparker6 Mar 18, 2024
160b748
:sparkles: moving launch of loops to command
dwelsh-clouru Apr 15, 2024
682cbdd
Merge branch 'main' into feature/opinions-webhook
jordanparker6 Apr 15, 2024
778953f
Merge https://github.com/freelawproject/courtlistener into feature/op…
jordanparker6 Apr 15, 2024
671e941
Merge branch 'feature/opinions-webhook' of https://github.com/jordanp…
jordanparker6 Apr 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions cl/api/migrations/0011_alter_webhook_event_type_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Generated by Django 5.0.1 on 2024-02-14 02:58

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
(
"api",
"0010_remove_webhook_update_or_delete_snapshot_update_and_more",
),
]

operations = [
migrations.AlterField(
model_name="webhook",
name="event_type",
field=models.IntegerField(
choices=[
(1, "Docket Alert"),
(2, "Search Alert"),
(3, "Recap Fetch"),
(4, "Old Docket Alerts Report"),
(5, "Opinion Create"),
(6, "Opinion Update"),
(7, "Opinion Delete"),
(8, "Opinion Cluster Create"),
(9, "Opinion Cluster Update"),
(10, "Opinion Cluster Delete"),
],
help_text="The event type that triggers the webhook.",
),
),
migrations.AlterField(
model_name="webhookhistoryevent",
name="event_type",
field=models.IntegerField(
choices=[
(1, "Docket Alert"),
(2, "Search Alert"),
(3, "Recap Fetch"),
(4, "Old Docket Alerts Report"),
(5, "Opinion Create"),
(6, "Opinion Update"),
(7, "Opinion Delete"),
(8, "Opinion Cluster Create"),
(9, "Opinion Cluster Update"),
(10, "Opinion Cluster Delete"),
],
help_text="The event type that triggers the webhook.",
),
),
]
6 changes: 6 additions & 0 deletions cl/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ class WebhookEventType(models.IntegerChoices):
SEARCH_ALERT = 2, "Search Alert"
RECAP_FETCH = 3, "Recap Fetch"
OLD_DOCKET_ALERTS_REPORT = 4, "Old Docket Alerts Report"
OPINION_CREATE = 5, "Opinion Create"
OPINION_UPDATE = 6, "Opinion Update"
OPINION_DELETE = 7, "Opinion Delete"
OPINION_CLUSTER_CREATE = 8, "Opinion Cluster Create"
OPINION_CLUSTER_UPDATE = 9, "Opinion Cluster Update"
OPINION_CLUSTER_DELETE = 10, "Opinion Cluster Delete"


HttpStatusCodes = models.IntegerChoices( # type: ignore
Expand Down
145 changes: 142 additions & 3 deletions cl/api/tasks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import json
from typing import Any
from typing import Any, Dict, List

from rest_framework.renderers import JSONRenderer

Expand All @@ -10,9 +10,13 @@
from cl.api.webhooks import send_webhook_event
from cl.celery_init import app
from cl.corpus_importer.api_serializers import DocketEntrySerializer
from cl.search.api_serializers import OAESResultSerializer
from cl.search.api_serializers import (
OAESResultSerializer,
OpinionClusterSerializerOffline,
OpinionSerializerOffline,
)
from cl.search.api_utils import ResultObject
from cl.search.models import DocketEntry
from cl.search.models import DocketEntry, Opinion, OpinionCluster


@app.task()
Expand All @@ -35,6 +39,9 @@ def send_test_webhook_event(
send_webhook_event(webhook_event, content_str.encode("utf-8"))


# -- Alert Webhook Events ----


@app.task()
def send_docket_alert_webhook_events(
des_pks: list[int],
Expand Down Expand Up @@ -116,3 +123,135 @@ def send_es_search_alert_webhook(
content=post_content,
)
send_webhook_event(webhook_event, json_bytes)


# -- CRUD Webhook Events ----


@app.task()
def send_opinion_created_webhook(opinion: Opinion) -> None:
"""Send a webhook for each new opinion created

:param opinion: The search opinion object.
"""
for webhook in Webhook.objects.filter(
event_type=WebhookEventType.OPINION_CREATE, enabled=True
):
post_content = {
"webhook": generate_webhook_key_content(webhook),
"payload": OpinionSerializerOffline(opinion).data,
}
renderer = JSONRenderer()
json_bytes = renderer.render(
post_content,
accepted_media_type="application/json;",
)
webhook_event = WebhookEvent.objects.create(
webhook=webhook,
content=post_content,
)
send_webhook_event(webhook_event, json_bytes)


@app.task()
def send_opinions_deleted_webhook(ids: List[str]) -> None:
jordanparker6 marked this conversation as resolved.
Show resolved Hide resolved
"""Send a webhook for the deleted opinion cluster

:param ids: The list of ids deleted.
"""
for webhook in Webhook.objects.filter(
event_type=WebhookEventType.OPINION_DELETE, enabled=True
):
post_content = {
"webhook": generate_webhook_key_content(webhook),
"payload": {"ids": ids},
}
renderer = JSONRenderer()
json_bytes = renderer.render(
post_content,
accepted_media_type="application/json;",
)
webhook_event = WebhookEvent.objects.create(
webhook=webhook,
content=post_content,
)
send_webhook_event(webhook_event, json_bytes)


@app.task()
def send_opinion_cluster_created_webhook(
opinion_custer: OpinionCluster,
) -> None:
"""Send a webhook for the new opinion cluster created.

:param opinion_custer: The opinion cluster object.
"""
for webhook in Webhook.objects.filter(
event_type=WebhookEventType.OPINION_CLUSTER_CREATE, enabled=True
):
post_content = {
"webhook": generate_webhook_key_content(webhook),
"payload": OpinionClusterSerializerOffline(opinion_custer).data,
}
renderer = JSONRenderer()
json_bytes = renderer.render(
post_content,
accepted_media_type="application/json;",
)
webhook_event = WebhookEvent.objects.create(
webhook=webhook,
content=post_content,
)
send_webhook_event(webhook_event, json_bytes)


@app.task()
def send_opinion_clusters_deleted_webhook(ids: List[str]) -> None:
jordanparker6 marked this conversation as resolved.
Show resolved Hide resolved
"""Send a webhook for deleted opinion cluster.

:param id: The id of the deleted opinion cluster.
"""
for webhook in Webhook.objects.filter(
event_type=WebhookEventType.OPINION_CLUSTER_DELETE, enabled=True
):
post_content = {
"webhook": generate_webhook_key_content(webhook),
"payload": {"ids": ids},
}
renderer = JSONRenderer()
json_bytes = renderer.render(
post_content,
accepted_media_type="application/json;",
)
webhook_event = WebhookEvent.objects.create(
webhook=webhook,
content=post_content,
)
send_webhook_event(webhook_event, json_bytes)


@app.task()
def send_opinion_cluster_updated_webhook(
id: str, updated_fields: Dict[str, Any]
) -> None:
"""Send a webhook for updates to an opinion cluster.

:param id: The id of the deleted opinion cluster.
"""
for webhook in Webhook.objects.filter(
event_type=WebhookEventType.OPINION_CLUSTER_UPDATE, enabled=True
):
post_content = {
"webhook": generate_webhook_key_content(webhook),
"payload": {"id": id, "updated_fields": updated_fields},
}
renderer = JSONRenderer()
json_bytes = renderer.render(
post_content,
accepted_media_type="application/json;",
)
webhook_event = WebhookEvent.objects.create(
webhook=webhook,
content=post_content,
)
send_webhook_event(webhook_event, json_bytes)
1 change: 1 addition & 0 deletions cl/api/webhooks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from typing import Any, Dict, List
jordanparker6 marked this conversation as resolved.
Show resolved Hide resolved

import requests
from django.conf import settings
Expand Down
46 changes: 46 additions & 0 deletions cl/search/api_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from cl.api.utils import HyperlinkedModelSerializerWithId
from cl.audio.models import Audio
from cl.lib.document_serializer import DocumentSerializer
from cl.people_db.api_serializers import PersonSerializer
from cl.people_db.models import PartyType, Person
from cl.recap.api_serializers import FjcIntegratedDatabaseSerializer
from cl.search.documents import (
Expand Down Expand Up @@ -172,6 +173,26 @@ class Meta:
fields = "__all__"


class OpinionSerializerOffline(
DynamicFieldsMixin, serializers.ModelSerializer
):
absolute_url = serializers.CharField(
source="get_absolute_url", read_only=True
)
cluster_id = serializers.ReadOnlyField()
# Using PrimaryKeyRelatedField for cluster
author = PersonSerializer(many=False, read_only=True)
# Using PrimaryKeyRelatedField for joined_by with many=True for representing many-to-many relationship
opinions_cited = serializers.PrimaryKeyRelatedField(
many=True, queryset=Opinion.objects.all()
)
joined_by = PersonSerializer(many=False, read_only=True)

class Meta:
model = Opinion
fields = "__all__"


class OpinionsCitedSerializer(
DynamicFieldsMixin, HyperlinkedModelSerializerWithId
):
Expand Down Expand Up @@ -243,6 +264,31 @@ class Meta:
fields = "__all__"


class OpinionClusterSerializerOffline(
DynamicFieldsMixin, serializers.ModelSerializer
):
absolute_url = serializers.CharField(
source="get_absolute_url", read_only=True
)
panel = PersonSerializer(many=True, read_only=True)
non_participating_judges = PersonSerializer(many=True, read_only=True)
judges = PersonSerializer(many=True, read_only=True)
docket_id = serializers.ReadOnlyField()
# To Delete: CONFIRM THIS
# docket = serializers.PrimaryKeyRelatedField(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, when using fields = "__all__", a docket field is generated that shows the related docket ID. Therefore, we need to decide which name makes more sense: it can either be docket_id or docket.

# queryset=Docket.objects.all()
# )
sub_opinions = serializers.PrimaryKeyRelatedField(
many=True, queryset=Opinion.objects.all()
)
# Assuming CitationSerializer is defined elsewhere and is suitable as is
citations = CitationSerializer(many=True)

class Meta:
model = OpinionCluster
fields = "__all__"


class TagSerializer(DynamicFieldsMixin, HyperlinkedModelSerializerWithId):
class Meta:
model = Tag
Expand Down
5 changes: 4 additions & 1 deletion cl/search/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2951,7 +2951,7 @@ def save(
**kwargs,
):
self.slug = slugify(trunc(best_case_name(self), 75))
if update_fields is not None:
if update_fields:
update_fields = {"slug"}.union(update_fields)
super(OpinionCluster, self).save(
update_fields=update_fields, *args, **kwargs
Expand All @@ -2968,13 +2968,15 @@ async def asave(
update_fields=None,
index=True,
force_commit=False,
send_webhook=True,
jordanparker6 marked this conversation as resolved.
Show resolved Hide resolved
*args,
**kwargs,
):
return await sync_to_async(self.save)(
update_fields=update_fields,
index=index,
force_commit=force_commit,
send_webhook=send_webhook,
jordanparker6 marked this conversation as resolved.
Show resolved Hide resolved
*args,
**kwargs,
)
Expand Down Expand Up @@ -3435,6 +3437,7 @@ def save(
**kwargs: Dict,
) -> None:
super(Opinion, self).save(*args, **kwargs)

if index:
from cl.search.tasks import add_items_to_solr

Expand Down
Loading
Loading