Skip to content

Commit

Permalink
Stop reading node and status counts from old models
Browse files Browse the repository at this point in the history
  • Loading branch information
rowanseymour committed Nov 27, 2024
1 parent 470f4ab commit e17993b
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 242 deletions.
49 changes: 0 additions & 49 deletions temba/flows/management/commands/recalc_node_counts.py

This file was deleted.

84 changes: 1 addition & 83 deletions temba/flows/management/commands/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.utils import timezone

from temba.contacts.models import Contact
from temba.flows.models import FlowNodeCount, FlowStart
from temba.flows.models import FlowStart
from temba.tests import TembaTest
from temba.tests.engine import MockSessionWriter

Expand Down Expand Up @@ -73,88 +73,6 @@ def test_command(self):
self.assertFalse(flow3.has_issues)


class RecalcNodeCountsTest(TembaTest):
def test_recalc_node_counts(self):
contact1 = self.create_contact("Ben Haggerty", phone="+12065552020")
contact2 = self.create_contact("Joe", phone="+12065550002")
contact3 = self.create_contact("Frank", phone="+12065550003")

def check_node_count_rebuild(flow, assert_count):
node_counts = FlowNodeCount.get_totals(flow)

call_command("recalc_node_counts", flow_id=flow.id)

new_counts = FlowNodeCount.get_totals(flow)
self.assertEqual(new_counts, node_counts)
self.assertEqual(assert_count, sum(new_counts.values()))

flow = self.get_flow("favorites_v13")
nodes = flow.get_definition()["nodes"]

color_prompt = nodes[0]
color_other = nodes[1]
color_split = nodes[2]
beer_prompt = nodes[3]
beer_split = nodes[5]
name_prompt = nodes[6]
name_split = nodes[7]
name_reply = nodes[8]

session1 = MockSessionWriter(contact1, flow).visit(color_prompt).visit(color_split).wait().save()
session2 = MockSessionWriter(contact2, flow).visit(color_prompt).visit(color_split).wait().save()
session3 = MockSessionWriter(contact3, flow).visit(color_prompt).visit(color_split).wait().save()

# recalculate node counts and check they are the same
check_node_count_rebuild(flow, 3)

(session1.resume(self.create_incoming_msg(contact1, "Blue")).visit(beer_prompt).visit(beer_split).wait().save())
(
session2.resume(self.create_incoming_msg(contact2, "Beige"))
.visit(color_other)
.visit(color_split)
.wait()
.save()
)
(
session3.resume(self.create_incoming_msg(contact3, "Amber"))
.visit(color_other)
.visit(color_split)
.wait()
.save()
)

check_node_count_rebuild(flow, 3)

(
session1.resume(self.create_incoming_msg(contact1, "Primus"))
.visit(name_prompt)
.visit(name_split)
.wait()
.save()
)
(
session2.resume(self.create_incoming_msg(contact2, "Orange"))
.visit(color_other)
.visit(color_split)
.wait()
.save()
)
(
session3.resume(self.create_incoming_msg(contact3, "Amber"))
.visit(color_other)
.visit(color_split)
.wait()
.save()
)

check_node_count_rebuild(flow, 3)

# contact1 replies with name to complete the flow
(session1.resume(self.create_incoming_msg(contact1, "Bob")).visit(name_reply).complete().save())

check_node_count_rebuild(flow, 2)


class UndoFootgunTest(TembaTest):
def test_group_changes(self):
flow = self.create_flow("Test")
Expand Down
10 changes: 0 additions & 10 deletions temba/flows/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1596,11 +1596,6 @@ def get_squash_query(cls, distinct_set):

return sql, (distinct_set.node_uuid, distinct_set.flow_id, distinct_set.node_uuid)

@classmethod
def get_totals(cls, flow):
totals = list(cls.objects.filter(flow=flow).values_list("node_uuid").annotate(replies=Sum("count")))
return {str(t[0]): t[1] for t in totals if t[1]}

class Meta:
indexes = [
models.Index(
Expand Down Expand Up @@ -1632,11 +1627,6 @@ def get_squash_query(cls, distinct_set):

return sql, (distinct_set.flow_id, distinct_set.status) * 2

@classmethod
def get_totals(cls, flow):
totals = list(cls.objects.filter(flow=flow).values_list("status").annotate(total=Sum("count")))
return {t[0]: t[1] for t in totals}

class Meta:
indexes = [
models.Index(fields=("flow", "status")),
Expand Down
198 changes: 98 additions & 100 deletions temba/flows/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,8 +385,6 @@ def test_activity(self, mr_mocks):
.save()
)

self.assertEqual({color_split["uuid"]: 1}, FlowNodeCount.get_totals(flow))

(active, visited) = flow.get_activity()

self.assertEqual({color_split["uuid"]: 1}, active)
Expand Down Expand Up @@ -3455,104 +3453,6 @@ def setUp(self):

self.contact = self.create_contact("Ben Haggerty", phone="+250788123123")

def test_status_counts(self):
contact = self.create_contact("Bob", phone="+1234567890")
session = FlowSession.objects.create(
uuid=uuid4(),
org=self.org,
contact=self.contact,
status=FlowSession.STATUS_WAITING,
output_url="http://sessions.com/123.json",
created_on=timezone.now(),
wait_started_on=timezone.now(),
wait_expires_on=timezone.now() + timedelta(days=7),
wait_resume_on_expire=False,
)

def create_runs(flow_status_pairs: tuple) -> list:
runs = []
for flow, status in flow_status_pairs:
runs.append(
FlowRun(
uuid=uuid4(),
org=self.org,
session=session,
flow=flow,
contact=contact,
status=status,
created_on=timezone.now(),
modified_on=timezone.now(),
exited_on=timezone.now() if status not in ("A", "W") else None,
)
)
return FlowRun.objects.bulk_create(runs)

flow1 = self.create_flow("Test 1")
flow2 = self.create_flow("Test 2")

runs1 = create_runs(
(
(flow1, FlowRun.STATUS_ACTIVE),
(flow2, FlowRun.STATUS_WAITING),
(flow1, FlowRun.STATUS_ACTIVE),
(flow2, FlowRun.STATUS_WAITING),
(flow1, FlowRun.STATUS_WAITING),
(flow1, FlowRun.STATUS_COMPLETED),
)
)

self.assertEqual(
{(flow1, "A"): 2, (flow2, "W"): 2, (flow1, "W"): 1, (flow1, "C"): 1},
{(c.flow, c.status): c.count for c in FlowRunStatusCount.objects.all()},
)
self.assertEqual({"A": 2, "W": 1, "C": 1}, FlowRunStatusCount.get_totals(flow1))
self.assertEqual({"W": 2}, FlowRunStatusCount.get_totals(flow2))

# no difference after squashing
squash_flow_counts()

self.assertEqual({"A": 2, "W": 1, "C": 1}, FlowRunStatusCount.get_totals(flow1))
self.assertEqual({"W": 2}, FlowRunStatusCount.get_totals(flow2))

runs2 = create_runs(
(
(flow1, FlowRun.STATUS_ACTIVE),
(flow1, FlowRun.STATUS_ACTIVE),
(flow2, FlowRun.STATUS_EXPIRED),
)
)

self.assertEqual({"A": 4, "W": 1, "C": 1}, FlowRunStatusCount.get_totals(flow1))
self.assertEqual({"W": 2, "X": 1}, FlowRunStatusCount.get_totals(flow2))

# bulk update runs like they're being interrupted
FlowRun.objects.filter(id__in=[r.id for r in runs1]).update(
status=FlowRun.STATUS_INTERRUPTED, exited_on=timezone.now()
)

self.assertEqual({"A": 2, "W": 0, "C": 0, "I": 4}, FlowRunStatusCount.get_totals(flow1))
self.assertEqual({"W": 0, "X": 1, "I": 2}, FlowRunStatusCount.get_totals(flow2))

# no difference after squashing
squash_flow_counts()

self.assertEqual({"A": 2, "W": 0, "C": 0, "I": 4}, FlowRunStatusCount.get_totals(flow1))
self.assertEqual({"W": 0, "X": 1, "I": 2}, FlowRunStatusCount.get_totals(flow2))

# do manual deletion of some runs
FlowRun.objects.filter(id__in=[r.id for r in runs2]).update(delete_from_results=True)
FlowRun.objects.filter(id__in=[r.id for r in runs2]).delete()

self.assertEqual({"A": 0, "W": 0, "C": 0, "I": 4}, FlowRunStatusCount.get_totals(flow1))
self.assertEqual({"W": 0, "X": 0, "I": 2}, FlowRunStatusCount.get_totals(flow2))

# do archival deletion of the rest
FlowRun.objects.filter(id__in=[r.id for r in runs1]).delete()

# status counts are unchanged
self.assertEqual({"A": 0, "W": 0, "C": 0, "I": 4}, FlowRunStatusCount.get_totals(flow1))
self.assertEqual({"W": 0, "X": 0, "I": 2}, FlowRunStatusCount.get_totals(flow2))

def test_as_archive_json(self):
flow = self.get_flow("color_v13")
flow_nodes = flow.get_definition()["nodes"]
Expand Down Expand Up @@ -5607,6 +5507,104 @@ def test_trim_revisions(self):


class FlowActivityCountTest(TembaTest):
def test_status_counts(self):
contact = self.create_contact("Bob", phone="+1234567890")
session = FlowSession.objects.create(
uuid=uuid4(),
org=self.org,
contact=contact,
status=FlowSession.STATUS_WAITING,
output_url="http://sessions.com/123.json",
created_on=timezone.now(),
wait_started_on=timezone.now(),
wait_expires_on=timezone.now() + timedelta(days=7),
wait_resume_on_expire=False,
)

def create_runs(flow_status_pairs: tuple) -> list:
runs = []
for flow, status in flow_status_pairs:
runs.append(
FlowRun(
uuid=uuid4(),
org=self.org,
session=session,
flow=flow,
contact=contact,
status=status,
created_on=timezone.now(),
modified_on=timezone.now(),
exited_on=timezone.now() if status not in ("A", "W") else None,
)
)
return FlowRun.objects.bulk_create(runs)

flow1 = self.create_flow("Test 1")
flow2 = self.create_flow("Test 2")

runs1 = create_runs(
(
(flow1, FlowRun.STATUS_ACTIVE),
(flow2, FlowRun.STATUS_WAITING),
(flow1, FlowRun.STATUS_ACTIVE),
(flow2, FlowRun.STATUS_WAITING),
(flow1, FlowRun.STATUS_WAITING),
(flow1, FlowRun.STATUS_COMPLETED),
)
)

self.assertEqual(
{(flow1, "status:A"): 2, (flow2, "status:W"): 2, (flow1, "status:W"): 1, (flow1, "status:C"): 1},
{(c.flow, c.scope): c.count for c in FlowActivityCount.objects.all()},
)
self.assertEqual({"status:A": 2, "status:W": 1, "status:C": 1}, flow1.counts.scope_totals())
self.assertEqual({"status:W": 2}, flow2.counts.scope_totals())

# no difference after squashing
squash_activity_counts()

self.assertEqual({"status:A": 2, "status:W": 1, "status:C": 1}, flow1.counts.scope_totals())
self.assertEqual({"status:W": 2}, flow2.counts.scope_totals())

runs2 = create_runs(
(
(flow1, FlowRun.STATUS_ACTIVE),
(flow1, FlowRun.STATUS_ACTIVE),
(flow2, FlowRun.STATUS_EXPIRED),
)
)

self.assertEqual({"status:A": 4, "status:W": 1, "status:C": 1}, flow1.counts.scope_totals())
self.assertEqual({"status:W": 2, "status:X": 1}, flow2.counts.scope_totals())

# bulk update runs like they're being interrupted
FlowRun.objects.filter(id__in=[r.id for r in runs1]).update(
status=FlowRun.STATUS_INTERRUPTED, exited_on=timezone.now()
)

self.assertEqual({"status:A": 2, "status:W": 0, "status:C": 0, "status:I": 4}, flow1.counts.scope_totals())
self.assertEqual({"status:W": 0, "status:X": 1, "status:I": 2}, flow2.counts.scope_totals())

# no difference after squashing except zeros gone
squash_activity_counts()

self.assertEqual({"status:A": 2, "status:I": 4}, flow1.counts.scope_totals())
self.assertEqual({"status:X": 1, "status:I": 2}, flow2.counts.scope_totals())

# do manual deletion of some runs
FlowRun.objects.filter(id__in=[r.id for r in runs2]).update(delete_from_results=True)
FlowRun.objects.filter(id__in=[r.id for r in runs2]).delete()

self.assertEqual({"status:A": 0, "status:I": 4}, flow1.counts.scope_totals())
self.assertEqual({"status:X": 0, "status:I": 2}, flow2.counts.scope_totals())

# do archival deletion of the rest
FlowRun.objects.filter(id__in=[r.id for r in runs1]).delete()

# status counts are unchanged
self.assertEqual({"status:A": 0, "status:I": 4}, flow1.counts.scope_totals())
self.assertEqual({"status:X": 0, "status:I": 2}, flow2.counts.scope_totals())

def test_msgsin_counts(self):
flow1 = self.create_flow("Test 1")
flow2 = self.create_flow("Test 2")
Expand Down

0 comments on commit e17993b

Please sign in to comment.