Skip to content

Commit

Permalink
Merge pull request #169 from praekeltfoundation/add-rapidpro-group-co…
Browse files Browse the repository at this point in the history
…unt-monitors

Add model to save group monitors
  • Loading branch information
erikh360 authored Sep 27, 2023
2 parents 140e892 + 164007f commit cb7102d
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 13 deletions.
2 changes: 0 additions & 2 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,5 +217,3 @@
# something more human-readable.
# release="myapp@1.0.0",
)

RAPIDPRO_MONITOR_GROUP = env.str("RAPIDPRO_MONITOR_GROUP", "")
3 changes: 2 additions & 1 deletion sidekick/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.contrib import admin

from .models import Consent, Organization
from .models import Consent, GroupMonitor, Organization

admin.site.register(Organization)
admin.site.register(GroupMonitor)
admin.site.register(Consent)
24 changes: 24 additions & 0 deletions sidekick/migrations/0013_groupmonitor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 3.2.21 on 2023-09-27 10:33

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('sidekick', '0012_alter_organization_filter_rapidpro_fields'),
]

operations = [
migrations.CreateModel(
name='GroupMonitor',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('group_name', models.CharField(max_length=200)),
('minimum_count', models.IntegerField(default=0)),
('triggered', models.BooleanField(default=False)),
('org', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='group_monitors', to='sidekick.organization')),
],
),
]
25 changes: 25 additions & 0 deletions sidekick/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,31 @@ def user_token_creation(sender, instance, created, **kwargs):
Token.objects.create(user=instance)


class GroupMonitor(models.Model):
org = models.ForeignKey(
Organization,
related_name="group_monitors",
null=False,
on_delete=models.CASCADE,
)
group_name = models.CharField(max_length=200, null=False, blank=False)
minimum_count = models.IntegerField(default=0)
triggered = models.BooleanField(default=False)

def __str__(self):
return f"{self.name} - {self.minimum_count}"

def check_group_count(self, group_count):
if self.triggered and group_count > self.minimum_count:
self.triggered = False
self.save()
elif not self.triggered and group_count <= self.minimum_count:
self.triggered = True
self.save()
return True
return False


class Consent(models.Model):
org = models.ForeignKey(Organization, on_delete=models.CASCADE)
label = models.CharField(
Expand Down
15 changes: 7 additions & 8 deletions sidekick/tasks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from celery.exceptions import SoftTimeLimitExceeded
from django.conf import settings
from requests import RequestException
from temba_client.exceptions import TembaHttpError

Expand Down Expand Up @@ -80,14 +79,14 @@ def archive_turn_conversation(org_id, wa_id, reason):
time_limit=15,
)
def check_rapidpro_group_membership_count():
if settings.RAPIDPRO_MONITOR_GROUP:
group_name = settings.RAPIDPRO_MONITOR_GROUP
for org in Organization.objects.all():
client = org.get_rapidpro_client()
group = client.get_groups(name=group_name).first()
if group and group.count <= 0:
for org in Organization.objects.all():
client = org.get_rapidpro_client()

for group_monitor in org.group_monitors.all():
group = client.get_groups(name=group_monitor.group_name).first()
if group and group_monitor.check_group_count(group.count):
raise_group_membership_error.delay(
f"Org: {org.name} - {group_name} group is empty"
f"Org: {org.name} - {group_monitor.group_name} group is empty"
)


Expand Down
103 changes: 101 additions & 2 deletions sidekick/tests/test_tasks.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from unittest.mock import patch
from unittest.mock import MagicMock, patch

from django.test import TestCase

from sidekick.tasks import add_label_to_turn_conversation, archive_turn_conversation
from sidekick.models import GroupMonitor
from sidekick.tasks import (
add_label_to_turn_conversation,
archive_turn_conversation,
check_rapidpro_group_membership_count,
)
from sidekick.tests.utils import create_org


Expand Down Expand Up @@ -87,3 +92,97 @@ def test_get_last_message(self, get_messages, archive):
archive.assert_called_once_with(
self.org, "27820001001", "second-inbound", "Test reason"
)


class CheckRapidproGroupMembershipCountTests(TestCase):
def setUp(self):
self.org = create_org()

def create_group_monitor(self, minimum=0, triggered=False):
return GroupMonitor.objects.create(
org_id=self.org.id,
group_name="Test participants",
minimum_count=minimum,
triggered=triggered,
)

def create_rapidpro_group_mock(self, group_membership_count):
fake_group_object = MagicMock()
fake_group_object.count = group_membership_count

fake_query_obj = MagicMock()
fake_query_obj.first.return_value = fake_group_object
return fake_query_obj

@patch("sidekick.tasks.raise_group_membership_error")
@patch("temba_client.v2.TembaClient.get_groups", autospec=True)
def test_group_monitor_dont_trigger(self, mock_get_groups, mock_raise_group_error):
"""
If the group has more members than the minimum count don't trigger
"""
mock_get_groups.return_value = self.create_rapidpro_group_mock(10)

monitor = self.create_group_monitor()

check_rapidpro_group_membership_count()

self.assertFalse(monitor.triggered)
mock_raise_group_error.delay.assert_not_called()

@patch("sidekick.tasks.raise_group_membership_error")
@patch("temba_client.v2.TembaClient.get_groups", autospec=True)
def test_group_monitor_trigger(self, mock_get_groups, mock_raise_group_error):
"""
If the group has less members than the minimum count then trigger
"""
mock_get_groups.return_value = self.create_rapidpro_group_mock(0)

monitor = self.create_group_monitor()

check_rapidpro_group_membership_count()

monitor.refresh_from_db()
self.assertTrue(monitor.triggered)

mock_raise_group_error.delay.assert_called_with(
"Org: Test Organization - Test participants group is empty"
)

@patch("sidekick.tasks.raise_group_membership_error")
@patch("temba_client.v2.TembaClient.get_groups", autospec=True)
def test_group_monitor_already_triggered(
self, mock_get_groups, mock_raise_group_error
):
"""
If the monitor is already triggered on't triggered again
"""
mock_get_groups.return_value = self.create_rapidpro_group_mock(0)

monitor = self.create_group_monitor(triggered=True)

check_rapidpro_group_membership_count()

monitor.refresh_from_db()
self.assertTrue(monitor.triggered)

mock_raise_group_error.delay.assert_not_called()

@patch("sidekick.tasks.raise_group_membership_error")
@patch("temba_client.v2.TembaClient.get_groups", autospec=True)
def test_group_monitor_reset_triggered(
self, mock_get_groups, mock_raise_group_error
):
"""
If the monitor is triggered and the membership count is more than the minimum,
reset the triggered flag
"""
mock_get_groups.return_value = self.create_rapidpro_group_mock(10)

monitor = self.create_group_monitor(triggered=True)

check_rapidpro_group_membership_count()

monitor.refresh_from_db()
self.assertFalse(monitor.triggered)

mock_raise_group_error.delay.assert_not_called()

0 comments on commit cb7102d

Please sign in to comment.