From 039df61588da74bd1e436d083009fb5ada1c5fbd Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 25 Feb 2025 16:52:41 +0000 Subject: [PATCH] Add new 'session expire' type to contact fires and re-add expired status to sessions --- .../0204_alter_contactfire_fire_type.py | 26 ++++++++++++++++++ temba/contacts/models.py | 12 ++++++--- temba/contacts/tests/test_contactcrudl.py | 4 +-- ...lowrun_session_alter_flowsession_status.py | 27 +++++++++++++++++++ temba/flows/models.py | 10 ++++--- temba/flows/tests/test_session.py | 2 +- temba/tests/base.py | 4 +++ 7 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 temba/contacts/migrations/0204_alter_contactfire_fire_type.py create mode 100644 temba/flows/migrations/0378_alter_flowrun_session_alter_flowsession_status.py diff --git a/temba/contacts/migrations/0204_alter_contactfire_fire_type.py b/temba/contacts/migrations/0204_alter_contactfire_fire_type.py new file mode 100644 index 00000000000..9027fc78299 --- /dev/null +++ b/temba/contacts/migrations/0204_alter_contactfire_fire_type.py @@ -0,0 +1,26 @@ +# Generated by Django 5.1.4 on 2025-02-25 16:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("contacts", "0203_remove_contactfire_extra"), + ] + + operations = [ + migrations.AlterField( + model_name="contactfire", + name="fire_type", + field=models.CharField( + choices=[ + ("E", "Wait Expiration"), + ("T", "Wait Timeout"), + ("S", "Session Expiration"), + ("C", "Campaign Event"), + ], + max_length=1, + ), + ), + ] diff --git a/temba/contacts/models.py b/temba/contacts/models.py index ebd63806c14..3211d2626cc 100644 --- a/temba/contacts/models.py +++ b/temba/contacts/models.py @@ -671,7 +671,7 @@ def get_scheduled(self) -> list: """ from temba.campaigns.models import CampaignEvent - fires = self.fires.filter(fire_type=ContactFire.TYPE_CAMPAIGN) + fires = self.fires.filter(fire_type=ContactFire.TYPE_CAMPAIGN_EVENT) event_ids = {int(f.scope) for f in fires} events = CampaignEvent.objects.filter( campaign__org=self.org, campaign__is_archived=False, id__in=event_ids, is_active=True @@ -1717,8 +1717,14 @@ class ContactFire(models.Model): TYPE_WAIT_EXPIRATION = "E" TYPE_WAIT_TIMEOUT = "T" - TYPE_CAMPAIGN = "C" - TYPE_CHOICES = ((TYPE_WAIT_EXPIRATION, "Expiration"), (TYPE_WAIT_TIMEOUT, "Timeout"), (TYPE_CAMPAIGN, "Campaign")) + TYPE_SESSION_EXPIRATION = "S" + TYPE_CAMPAIGN_EVENT = "C" + TYPE_CHOICES = ( + (TYPE_WAIT_EXPIRATION, "Wait Expiration"), + (TYPE_WAIT_TIMEOUT, "Wait Timeout"), + (TYPE_SESSION_EXPIRATION, "Session Expiration"), + (TYPE_CAMPAIGN_EVENT, "Campaign Event"), + ) id = models.BigAutoField(auto_created=True, primary_key=True) org = models.ForeignKey(Org, on_delete=models.PROTECT, db_index=False) diff --git a/temba/contacts/tests/test_contactcrudl.py b/temba/contacts/tests/test_contactcrudl.py index deb909da634..284ec145fc0 100644 --- a/temba/contacts/tests/test_contactcrudl.py +++ b/temba/contacts/tests/test_contactcrudl.py @@ -1114,14 +1114,14 @@ def test_scheduled(self): fire1 = ContactFire.objects.create( org=self.org, contact=contact1, - fire_type=ContactFire.TYPE_CAMPAIGN, + fire_type=ContactFire.TYPE_CAMPAIGN_EVENT, scope=str(event1.id), fire_on=timezone.now() + timedelta(days=2), ) fire2 = ContactFire.objects.create( org=self.org, contact=contact1, - fire_type=ContactFire.TYPE_CAMPAIGN, + fire_type=ContactFire.TYPE_CAMPAIGN_EVENT, scope=str(event2.id), fire_on=timezone.now() + timedelta(days=5), ) diff --git a/temba/flows/migrations/0378_alter_flowrun_session_alter_flowsession_status.py b/temba/flows/migrations/0378_alter_flowrun_session_alter_flowsession_status.py new file mode 100644 index 00000000000..e3780d099b3 --- /dev/null +++ b/temba/flows/migrations/0378_alter_flowrun_session_alter_flowsession_status.py @@ -0,0 +1,27 @@ +# Generated by Django 5.1.4 on 2025-02-25 16:49 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("flows", "0377_remove_flowsession_flowsessions_contact_waiting"), + ] + + operations = [ + migrations.AlterField( + model_name="flowrun", + name="session", + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to="flows.flowsession"), + ), + migrations.AlterField( + model_name="flowsession", + name="status", + field=models.CharField( + choices=[("W", "Waiting"), ("C", "Completed"), ("I", "Interrupted"), ("X", "Expired"), ("F", "Failed")], + max_length=1, + ), + ), + ] diff --git a/temba/flows/models.py b/temba/flows/models.py index ec94723a23f..497dca9c1e1 100644 --- a/temba/flows/models.py +++ b/temba/flows/models.py @@ -1004,11 +1004,13 @@ class FlowSession(models.Model): STATUS_WAITING = "W" STATUS_COMPLETED = "C" STATUS_INTERRUPTED = "I" + STATUS_EXPIRED = "X" STATUS_FAILED = "F" STATUS_CHOICES = ( (STATUS_WAITING, "Waiting"), (STATUS_COMPLETED, "Completed"), (STATUS_INTERRUPTED, "Interrupted"), + (STATUS_EXPIRED, "Expired"), (STATUS_FAILED, "Failed"), ) @@ -1108,14 +1110,11 @@ class FlowRun(models.Model): org = models.ForeignKey(Org, on_delete=models.PROTECT, related_name="runs", db_index=False) flow = models.ForeignKey(Flow, on_delete=models.PROTECT, related_name="runs") status = models.CharField(max_length=1, choices=STATUS_CHOICES) + session_uuid = models.UUIDField(null=True) # contact isn't an index because we have flows_flowrun_contact_inc_flow below contact = models.ForeignKey(Contact, on_delete=models.PROTECT, related_name="runs", db_index=False) - # session this run belongs to (can be null if session has been trimmed) - session = models.ForeignKey(FlowSession, on_delete=models.PROTECT, related_name="runs", null=True) - session_uuid = models.UUIDField(null=True) # to replace session_id above - # when this run was created, last modified and exited created_on = models.DateTimeField(default=timezone.now) modified_on = models.DateTimeField(default=timezone.now) @@ -1138,6 +1137,9 @@ class FlowRun(models.Model): # current node location of this run in the flow current_node_uuid = models.UUIDField(null=True) + # TODO drop + session = models.ForeignKey(FlowSession, on_delete=models.PROTECT, null=True) + @dataclass class Step: node: UUID diff --git a/temba/flows/tests/test_session.py b/temba/flows/tests/test_session.py index 3574ccb6bf2..628c26b54f3 100644 --- a/temba/flows/tests/test_session.py +++ b/temba/flows/tests/test_session.py @@ -83,7 +83,7 @@ def test_trim(self): # create an IVR call with session call = self.create_incoming_call(flow, contact) - run4 = call.session.runs.get() + run4 = FlowRun.objects.get(session_uuid=call.session_uuid) self.assertIsNotNone(run1.session) self.assertIsNotNone(run2.session) diff --git a/temba/tests/base.py b/temba/tests/base.py index deb7df98618..e259906c990 100644 --- a/temba/tests/base.py +++ b/temba/tests/base.py @@ -582,6 +582,7 @@ def create_incoming_call( contact=contact, status=FlowRun.STATUS_COMPLETED, session=session, + session_uuid=session.uuid, exited_on=timezone.now(), ) Msg.objects.create( @@ -599,6 +600,9 @@ def create_incoming_call( modified_on=timezone.now(), ) + call.session_uuid = session.uuid + call.save(update_fields=("session_uuid",)) + return call def create_archive(