Skip to content

Commit

Permalink
Merge branch 'main' into DEV-add-tailwind-support-to-cl
Browse files Browse the repository at this point in the history
  • Loading branch information
ERosendo committed Jan 23, 2025
2 parents a159b34 + 0368e51 commit 88020f8
Show file tree
Hide file tree
Showing 28 changed files with 2,106 additions and 1,568 deletions.
75 changes: 22 additions & 53 deletions cl/alerts/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,8 +649,6 @@ def percolator_response_processing(response: SendAlertsResponse) -> None:
return None

scheduled_hits_to_create = []
email_alerts_to_send = []
rt_alerts_to_send = []

main_alerts_triggered = response.main_alerts_triggered
rd_alerts_triggered = response.rd_alerts_triggered
Expand All @@ -661,7 +659,6 @@ def percolator_response_processing(response: SendAlertsResponse) -> None:
instance_content_type = ContentType.objects.get(
app_label=app_label_str, model=model_str.lower()
)
schedule_alert = False
r = get_redis_interface("CACHE")
recap_document_hits = [hit.id for hit in rd_alerts_triggered]
docket_hits = [hit.id for hit in d_alerts_triggered]
Expand Down Expand Up @@ -691,7 +688,6 @@ def percolator_response_processing(response: SendAlertsResponse) -> None:
transform_percolator_child_document(
document_content_copy, hit.meta
)
schedule_alert = True
add_document_hit_to_alert_set(
r, alert_triggered.pk, "r", document_content_copy["id"]
)
Expand Down Expand Up @@ -748,64 +744,37 @@ def percolator_response_processing(response: SendAlertsResponse) -> None:
# user's donations.
send_webhook_alert_hits(alert_user, hits)

# Send RT Alerts for Audio.
if (
alert_triggered.rate == Alert.REAL_TIME
and app_label_model == "audio.Audio"
and not alert_user.profile.is_member
):
if not alert_user.profile.is_member:
continue

# Append alert RT email to be sent.
email_alerts_to_send.append((alert_user.pk, hits))
rt_alerts_to_send.append(alert_triggered.pk)

else:
if (
alert_triggered.rate == Alert.REAL_TIME
and not alert_user.profile.is_member
):
# Omit scheduling an RT alert if the user is not a member.
continue
# Schedule RT, DAILY, WEEKLY and MONTHLY Alerts
if scheduled_alert_hits_limit_reached(
alert_triggered.pk,
alert_triggered.user.pk,
instance_content_type,
object_id,
child_document,
):
# Skip storing hits for this alert-user combination because
# the SCHEDULED_ALERT_HITS_LIMIT has been reached.
continue
# Omit scheduling an RT alert if the user is not a member.
continue
# Schedule RT, DAILY, WEEKLY and MONTHLY Alerts
if scheduled_alert_hits_limit_reached(
alert_triggered.pk,
alert_triggered.user.pk,
instance_content_type,
object_id,
child_document,
):
# Skip storing hits for this alert-user combination because
# the SCHEDULED_ALERT_HITS_LIMIT has been reached.
continue

scheduled_hits_to_create.append(
ScheduledAlertHit(
user=alert_triggered.user,
alert=alert_triggered,
document_content=document_content_copy,
content_type=instance_content_type,
object_id=object_id,
)
scheduled_hits_to_create.append(
ScheduledAlertHit(
user=alert_triggered.user,
alert=alert_triggered,
document_content=document_content_copy,
content_type=instance_content_type,
object_id=object_id,
)
)

# Create scheduled RT, DAILY, WEEKLY and MONTHLY Alerts in bulk.
if scheduled_hits_to_create:
ScheduledAlertHit.objects.bulk_create(scheduled_hits_to_create)
# Sent all the related document RT emails.
if email_alerts_to_send:
send_search_alert_emails.delay(email_alerts_to_send, schedule_alert)

# Update RT Alerts date_last_hit, increase stats and log RT alerts sent.
if rt_alerts_to_send:
Alert.objects.filter(pk__in=rt_alerts_to_send).update(
date_last_hit=now()
)
alerts_sent = len(rt_alerts_to_send)
async_to_sync(tally_stat)(
f"alerts.sent.{Alert.REAL_TIME}", inc=alerts_sent
)
logger.info(f"Sent {alerts_sent} {Alert.REAL_TIME} email alerts.")


# TODO: Remove after scheduled OA alerts have been processed.
Expand Down
133 changes: 111 additions & 22 deletions cl/alerts/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2065,7 +2065,7 @@ def test_get_docket_notes_and_tags_by_user(self) -> None:
"cl.lib.es_signal_processor.allow_es_audio_indexing",
side_effect=lambda x, y: True,
)
class SearchAlertsOAESTests(ESIndexTestCase, TestCase):
class SearchAlertsOAESTests(ESIndexTestCase, TestCase, SearchAlertsAssertions):
"""Test ES Search Alerts"""

@classmethod
Expand Down Expand Up @@ -2215,11 +2215,22 @@ def test_send_oa_search_alert_webhooks(self, mock_abort_audio):
stt_source=Audio.STT_OPENAI_WHISPER,
)

# Send RT alerts
with time_machine.travel(mock_date, tick=False):
call_command("cl_send_rt_percolator_alerts", testing_mode=True)
# Confirm Alert date_last_hit is updated.
self.search_alert.refresh_from_db()
self.search_alert_2.refresh_from_db()
self.assertEqual(self.search_alert.date_last_hit, mock_date)
self.assertEqual(self.search_alert_2.date_last_hit, mock_date)
self.assertEqual(
self.search_alert.date_last_hit,
mock_date,
msg="Alert date of last hit didn't match.",
)
self.assertEqual(
self.search_alert_2.date_last_hit,
mock_date,
msg="Alert date of last hit didn't match.",
)

webhooks_enabled = Webhook.objects.filter(enabled=True)
self.assertEqual(len(webhooks_enabled), 1)
Expand Down Expand Up @@ -2331,6 +2342,8 @@ def test_send_oa_search_alert_webhooks(self, mock_abort_audio):
stt_transcript=transcript,
)

# Send RT alerts
call_command("cl_send_rt_percolator_alerts", testing_mode=True)
self.assertEqual(len(mail.outbox), 3, msg="Wrong number of emails.")
text_content = mail.outbox[2].body

Expand Down Expand Up @@ -2380,6 +2393,9 @@ def test_send_alert_on_document_creation(self, mock_abort_audio):
docket__docket_number="19-5735",
)

# Send RT alerts
call_command("cl_send_rt_percolator_alerts", testing_mode=True)

# Two OA search alert emails should be sent, one for user_profile and
# one for user_profile_2
self.assertEqual(len(mail.outbox), 2)
Expand All @@ -2404,6 +2420,8 @@ def test_send_alert_on_document_creation(self, mock_abort_audio):
rt_oral_argument.sha1 = "12345"
rt_oral_argument.save()

# Send RT alerts
call_command("cl_send_rt_percolator_alerts", testing_mode=True)
# New alerts shouldn't be sent. Since document was just updated.
self.assertEqual(len(mail.outbox), 2)
text_content = mail.outbox[0].body
Expand Down Expand Up @@ -2647,6 +2665,21 @@ def test_send_alert_multiple_alert_rates(self, mock_abort_audio):
)
def test_group_alerts_and_hits(self, mock_logger, mock_abort_audio):
""""""

rt_oa_search_alert = AlertFactory(
user=self.user_profile.user,
rate=Alert.REAL_TIME,
name="Test RT Alert OA",
query="q=docketNumber:19-5739 OR docketNumber:19-5740&type=oa",
alert_type=SEARCH_TYPES.ORAL_ARGUMENT,
)
rt_oa_search_alert_2 = AlertFactory(
user=self.user_profile.user,
rate=Alert.REAL_TIME,
name="Test RT Alert OA 2",
query="q=docketNumber:19-5741&type=oa",
alert_type=SEARCH_TYPES.ORAL_ARGUMENT,
)
with mock.patch(
"cl.api.webhooks.requests.post",
side_effect=lambda *args, **kwargs: MockResponse(
Expand Down Expand Up @@ -2675,20 +2708,58 @@ def test_group_alerts_and_hits(self, mock_logger, mock_abort_audio):
docket__docket_number="19-5741",
)

# No emails should be sent in RT, since all the alerts triggered by the
# OA documents added are not RT.
self.assertEqual(len(mail.outbox), 0)
# Send RT alerts
call_command("cl_send_rt_percolator_alerts", testing_mode=True)

# 1 email should be sent for the rt_oa_search_alert and rt_oa_search_alert_2
self.assertEqual(
len(mail.outbox), 1, msg="Wrong number of emails sent."
)

# The OA RT alert email should contain 2 alerts, one for rt_oa_search_alert
# and one for rt_oa_search_alert_2. First alert should contain 2 hits.
# Second alert should contain only 1 hit.

# Assert text version.
text_content = mail.outbox[0].body
self.assertIn(rt_oral_argument_1.case_name, text_content)
self.assertIn(rt_oral_argument_2.case_name, text_content)
self.assertIn(rt_oral_argument_3.case_name, text_content)

# Assert html version.
html_content = self.get_html_content_from_email(mail.outbox[0])
self._confirm_number_of_alerts(html_content, 2)
self._count_alert_hits_and_child_hits(
html_content,
rt_oa_search_alert.name,
2,
rt_oral_argument_1.case_name,
0,
)
self._count_alert_hits_and_child_hits(
html_content,
rt_oa_search_alert.name,
2,
rt_oral_argument_2.case_name,
0,
)
self._count_alert_hits_and_child_hits(
html_content,
rt_oa_search_alert_2.name,
1,
rt_oral_argument_3.case_name,
0,
)

# 7 webhook events should be triggered in RT:
# rt_oral_argument_1 should trigger 3: search_alert_3, search_alert_5
# and search_alert_6.
# rt_oral_argument_2 should trigger 3: search_alert_3, search_alert_5
# and search_alert_6.
# rt_oral_argument_3 should trigger 1: search_alert_4
# One webhook event should be sent to user_profile
# 10 webhook events should be triggered in RT:
# rt_oral_argument_1 should trigger 4: search_alert_3, search_alert_5,
# search_alert_6 and rt_oa_search_alert.
# rt_oral_argument_2 should trigger 4: search_alert_3, search_alert_5,
# search_alert_6 and rt_oa_search_alert.
# rt_oral_argument_3 should trigger 2: search_alert_4 and rt_oa_search_alert.
webhook_events = WebhookEvent.objects.all()
self.assertEqual(
len(webhook_events), 7, msg="Unexpected number of" "webhooks sent."
len(webhook_events), 10, msg="Unexpected number of webhooks sent."
)

# 7 webhook event should be sent to user_profile for 4 different
Expand All @@ -2699,6 +2770,8 @@ def test_group_alerts_and_hits(self, mock_logger, mock_abort_audio):
self.search_alert_4.pk,
self.search_alert_5.pk,
self.search_alert_6.pk,
rt_oa_search_alert.pk,
rt_oa_search_alert_2.pk,
]
for webhook_content in webhook_events:
content = webhook_content.content["payload"]
Expand All @@ -2718,8 +2791,10 @@ def test_group_alerts_and_hits(self, mock_logger, mock_abort_audio):

# One OA search alert email should be sent.
mock_logger.info.assert_called_with("Sent 1 dly email alerts.")
self.assertEqual(len(mail.outbox), 1)
text_content = mail.outbox[0].body
self.assertEqual(
len(mail.outbox), 2, msg="Wrong number of emails sent."
)
text_content = mail.outbox[1].body

# The right alert type template is used.
self.assertIn("oral argument", text_content)
Expand All @@ -2734,25 +2809,25 @@ def test_group_alerts_and_hits(self, mock_logger, mock_abort_audio):
self.assertIn(self.search_alert_4.name, text_content)

# Should not include the List-Unsubscribe-Post header.
self.assertIn("List-Unsubscribe", mail.outbox[0].extra_headers)
self.assertNotIn("List-Unsubscribe-Post", mail.outbox[0].extra_headers)
self.assertIn("List-Unsubscribe", mail.outbox[1].extra_headers)
self.assertNotIn("List-Unsubscribe-Post", mail.outbox[1].extra_headers)
alert_list_url = reverse("disable_alert_list")
self.assertIn(
alert_list_url,
mail.outbox[0].extra_headers["List-Unsubscribe"],
mail.outbox[1].extra_headers["List-Unsubscribe"],
)
self.assertIn(
f"keys={self.search_alert_3.secret_key}",
mail.outbox[0].extra_headers["List-Unsubscribe"],
mail.outbox[1].extra_headers["List-Unsubscribe"],
)
self.assertIn(
f"keys={self.search_alert_4.secret_key}",
mail.outbox[0].extra_headers["List-Unsubscribe"],
mail.outbox[1].extra_headers["List-Unsubscribe"],
)

# Extract HTML version.
html_content = None
for content, content_type in mail.outbox[0].alternatives:
for content, content_type in mail.outbox[1].alternatives:
if content_type == "text/html":
html_content = content
break
Expand All @@ -2764,6 +2839,17 @@ def test_group_alerts_and_hits(self, mock_logger, mock_abort_audio):
rt_oral_argument_2.delete()
rt_oral_argument_3.delete()

# Confirm Stat object is properly created and updated.
stats_objects = Stat.objects.all()
self.assertEqual(stats_objects.count(), 2)
stat_names = set([stat.name for stat in stats_objects])
self.assertEqual(stat_names, {"alerts.sent.rt", "alerts.sent.dly"})
self.assertEqual(stats_objects[0].count, 1)
self.assertEqual(stats_objects[1].count, 1)

# Remove test instances.
rt_oa_search_alert.delete()

@override_settings(ELASTICSEARCH_PAGINATION_BATCH_SIZE=5)
def test_send_multiple_rt_alerts(self, mock_abort_audio):
"""Confirm all RT alerts are properly sent if the percolator response
Expand Down Expand Up @@ -2827,6 +2913,9 @@ def test_send_multiple_rt_alerts(self, mock_abort_audio):
docket__docket_number="19-5735",
)

# Send RT alerts
call_command("cl_send_rt_percolator_alerts", testing_mode=True)

# 11 OA search alert emails should be sent, one for each user that
# had donated enough.
self.assertEqual(len(mail.outbox), 11)
Expand Down
15 changes: 12 additions & 3 deletions cl/assets/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,23 @@ <h1>You did not supply the "private" variable to your template.
<li><a href="{% url "profile_alerts" %}"
tabindex="203"><i class="fa fa-bell-o gray fa-fw"></i>&nbsp;Alerts</a></li>
<li><a href="{% url "profile_notes" %}"
tabindex="205"><i class="fa fa-bookmark-o gray fa-fw"></i>&nbsp;Notes</a></li>
tabindex="204"><i class="fa fa-bookmark-o gray fa-fw"></i>&nbsp;Notes</a></li>
<li><a href="{% url "tag_list" username=user.username %}"
tabindex="205"><i class="fa fa-tags gray fa-fw"></i>&nbsp;Tags</a></li>
{% flag "pray-and-pay" %}
<li><a href="{% url 'user_prayers' user.username %}" tabindex="206">
<div class="fa gray fa-fw">
{% include "includes/hand-holding-heart.svg" %}
</div>
&nbsp;Prayers
</a>
</li><!--need to fix class-->
{% endflag %}
<li class="divider"></li>
<li><a href="{% url "profile_your_support" %}"
tabindex="206"><i class="fa fa-money gray fa-fw"></i>&nbsp;Your Support</a></li>
tabindex="207"><i class="fa fa-money gray fa-fw"></i>&nbsp;Your Support</a></li>
<li><a href="{% url "view_settings" %}"
tabindex="207"><i class="fa fa-user gray fa-fw"></i>&nbsp;Account</a></li>
tabindex="208"><i class="fa fa-user gray fa-fw"></i>&nbsp;Account</a></li>
<li>
<form id="logout-form" method="post" action="/sign-out/">
{% csrf_token %}
Expand Down
1 change: 1 addition & 0 deletions cl/assets/templates/includes/hand-holding-heart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 7 additions & 1 deletion cl/citations/annotate_citations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from cl.citations.match_citations import NO_MATCH_RESOURCE
from cl.citations.types import MatchedResourceType, SupportedCitationType
from cl.custom_filters.templatetags.text_filters import best_case_name
from cl.lib.string_utils import trunc
from cl.search.models import Opinion, RECAPDocument


Expand Down Expand Up @@ -61,8 +63,12 @@ def generate_annotations(
"</span>",
]
else: # If successfully matched...
case_name = trunc(best_case_name(opinion.cluster), 60, "...")
annotation = [
f'<span class="citation" data-id="{opinion.pk}"><a href="{opinion.cluster.get_absolute_url()}">',
f'<span class="citation" data-id="{opinion.pk}">'
f'<a href="{opinion.cluster.get_absolute_url()}"'
f' aria-description="Citation for case: {case_name}"'
">",
"</a></span>",
]
for c in citations:
Expand Down
Loading

0 comments on commit 88020f8

Please sign in to comment.