def test_group_changes(self): flow = self.create_flow("Test") nodes = flow.get_definition()["nodes"] contact1 = self.create_contact("Joe", phone="1234") contact2 = self.create_contact("Frank", phone="2345") contact3 = self.create_contact("Anne", phone="3456") group1 = self.create_group("Group 1", contacts=[contact3]) group2 = self.create_group("Group 2", contacts=[]) group3 = self.create_group("Group 3", contacts=[contact1, contact2]) # simulate a flow start which adds contacts 1 and 2 to groups 1 and 2, and removes them from group 3 start1 = FlowStart.create(flow, self.admin, contacts=[contact1, contact2]) (MockSessionWriter(contact1, flow, start=start1).visit(nodes[0]).add_contact_groups([ group1, group2 ]).remove_contact_groups([group3 ]).complete().save()) (MockSessionWriter(contact2, flow, start=start1).visit(nodes[0]).add_contact_groups([ group1, group2 ]).remove_contact_groups([group3 ]).complete().save()) # and another which adds contact 3 to group 3 start2 = FlowStart.create(flow, self.admin, contacts=[contact3]) (MockSessionWriter(contact3, flow, start=start2).visit( nodes[0]).add_contact_groups([group3]).complete().save()) t0 = timezone.now() self.assertEqual({contact1, contact2, contact3}, set(group1.contacts.all())) self.assertEqual({contact1, contact2}, set(group2.contacts.all())) self.assertEqual({contact3}, set(group3.contacts.all())) # can run with --dry-run to preview changes call_command("undo_footgun", start=start1.id, dry_run=True, quiet=True) self.assertEqual({contact1, contact2, contact3}, set(group1.contacts.all())) self.assertEqual({contact1, contact2}, set(group2.contacts.all())) self.assertEqual({contact3}, set(group3.contacts.all())) # no contacts will have had modified_on updated self.assertEqual(0, Contact.objects.filter(modified_on__gt=t0).count()) # and then actually make database changes call_command("undo_footgun", start=start1.id, quiet=True) self.assertEqual({contact3}, set(group1.contacts.all())) self.assertEqual(set(), set(group2.contacts.all())) self.assertEqual({contact1, contact2, contact3}, set(group3.contacts.all())) # contacts 1 and 2 will have had modified_on updated self.assertEqual(2, Contact.objects.filter(modified_on__gt=t0).count())
def start_flow_activity(self, org): assert not org.cache["activity"] user = org.cache["users"][0] flow = self.random_choice(org.cache["flows"]) if self.probability(0.9): # start a random group using a flow start group = self.random_choice(org.cache["groups"]) contacts_started = list(group.contacts.values_list("id", flat=True)) self._log( " > Starting flow %s for group %s (%d) in org %s\n" % (flow.name, group.name, len(contacts_started), org.name) ) start = FlowStart.create(flow, user, groups=[group], restart_participants=True) start.start() else: # start a random individual without a flow start if not org.cache["contacts"]: return contact = Contact.objects.get(id=self.random_choice(org.cache["contacts"])) contacts_started = [contact.id] self._log(" > Starting flow %s for contact #%d in org %s\n" % (flow.name, contact.id, org.name)) flow.start([], [contact], restart_participants=True) org.cache["activity"] = {"flow": flow, "unresponded": contacts_started, "started": list(contacts_started)}
def fire(self): if self.is_archived or not self.is_active: # pragma: needs cover return None channels = self.flow.org.channels.all() if not channels: # pragma: needs cover return None groups = list(self.groups.all()) contacts = list(self.contacts.all()) # nothing to do, move along if not groups and not contacts: return # for single contacts, we just start directly if not groups and contacts: self.flow.start(groups, contacts, restart_participants=True) # we have groups of contacts to start, create a flow start else: start = FlowStart.create(self.flow, self.created_by, groups=groups, contacts=contacts) start.async_start() self.last_triggered = timezone.now() self.trigger_count += 1 self.save()
def save(self): urns = self.validated_data.get("urns", []) contacts = self.validated_data.get("contacts", []) groups = self.validated_data.get("groups", []) restart_participants = self.validated_data.get("restart_participants", True) extra = self.validated_data.get("extra") params = self.validated_data.get("params") if params: extra = params # convert URNs to contacts for urn in urns: contact, urn_obj = Contact.get_or_create(self.context["org"], urn, user=self.context["user"]) contacts.append(contact) # ok, let's go create our flow start, the actual starting will happen in our view return FlowStart.create( self.validated_data["flow"], self.context["user"], restart_participants=restart_participants, contacts=contacts, groups=groups, extra=extra, )
def start_flow_activity(self, org): assert not org.cache['activity'] user = org.cache['users'][0] flow = self.random_choice(org.cache['flows']) if self.probability(0.9): # start a random group using a flow start group = self.random_choice(org.cache['groups']) contacts_started = list(group.contacts.values_list('id', flat=True)) self._log(" > Starting flow %s for group %s (%d) in org %s\n" % (flow.name, group.name, len(contacts_started), org.name)) start = FlowStart.create(flow, user, groups=[group], restart_participants=True) start.start() else: # start a random individual without a flow start if not org.cache['contacts']: return contact = Contact.objects.get(id=self.random_choice(org.cache['contacts'])) contacts_started = [contact.id] self._log(" > Starting flow %s for contact #%d in org %s\n" % (flow.name, contact.id, org.name)) flow.start([], [contact], restart_participants=True) org.cache['activity'] = {'flow': flow, 'unresponded': contacts_started, 'started': list(contacts_started)}
def batch_fire(cls, fires, flow): """ Starts a batch of event fires that are for events which use the same flow """ fired = timezone.now() contacts = [f.contact for f in fires] event = fires[0].event include_active = not (event.event_type == CampaignEvent.TYPE_MESSAGE and event.start_mode == CampaignEvent.MODE_SKIP) if event.is_active and not event.campaign.is_archived: if len(contacts) == 1: flow.start([], contacts, restart_participants=True, include_active=include_active, campaign_event=event) else: start = FlowStart.create(flow, flow.created_by, contacts=contacts, include_active=include_active, campaign_event=event) start.async_start() EventFire.objects.filter(id__in=[f.id for f in fires]).update( fired=fired) else: EventFire.objects.filter(id__in=[f.id for f in fires]).delete()
def fire(self): if self.is_archived or not self.is_active: # pragma: needs cover return None channels = self.flow.org.channels.all() if not channels: # pragma: needs cover return None groups = list(self.groups.all()) contacts = list(self.contacts.all()) # nothing to do, move along if not groups and not contacts: return # for single contacts, we just start directly if not groups and contacts: self.flow.start(groups, contacts, restart_participants=True) # we have groups of contacts to start, create a flow start else: start = FlowStart.create(self.flow, self.created_by, groups=groups, contacts=contacts) start.async_start() self.save()
def save(self): # ok, let's go create our flow start, the actual starting will happen in our view start = FlowStart.create(self.validated_data['flow'], self.context['user'], restart_participants=self.validated_data.get('restart_participants', True), contacts=self.validated_data.get('contacts', []) + self.validated_data.get('urns', []), groups=self.validated_data.get('groups', [])) return start
def save(self): # ok, let's go create our flow start, the actual starting will happen in our view start = FlowStart.create( self.validated_data['flow'], self.context['user'], restart_participants=self.validated_data.get( 'restart_participants', True), contacts=self.validated_data.get('contacts', []) + self.validated_data.get('urns', []), groups=self.validated_data.get('groups', [])) return start
def batch_fire(cls, fires, flow): """ Starts a batch of event fires that are for events which use the same flow """ fired = timezone.now() contacts = [f.contact for f in fires] if len(contacts) == 1: flow.start([], contacts, restart_participants=True) else: start = FlowStart.create(flow, flow.created_by, contacts=contacts) start.async_start() EventFire.objects.filter(id__in=[f.id for f in fires]).update(fired=fired)
def batch_fire(cls, fires, flow): """ Starts a batch of event fires that are for events which use the same flow """ fired = timezone.now() contacts = [f.contact for f in fires] event = fires[0].event if len(contacts) == 1: flow.start([], contacts, restart_participants=True, campaign_event=event) else: start = FlowStart.create(flow, flow.created_by, contacts=contacts, campaign_event=event) start.async_start() EventFire.objects.filter(id__in=[f.id for f in fires]).update(fired=fired)
def save(self): urns = self.validated_data.get('urns', []) contacts = self.validated_data.get('contacts', []) groups = self.validated_data.get('groups', []) restart_participants = self.validated_data.get('restart_participants', True) extra = self.validated_data.get('extra') # convert URNs to contacts for urn in urns: contact = Contact.get_or_create(self.context['org'], self.context['user'], urns=[urn]) contacts.append(contact) # ok, let's go create our flow start, the actual starting will happen in our view return FlowStart.create(self.validated_data['flow'], self.context['user'], restart_participants=restart_participants, contacts=contacts, groups=groups, extra=extra)
def test_queue_flow_start(self): flow = self.get_flow("favorites") jim = self.create_contact("Jim", phone="+12065551212") bobs = self.create_group( "Bobs", [self.create_contact("Bob", phone="+12065551313")]) start = FlowStart.create( flow, self.admin, groups=[bobs], contacts=[jim], urns=["tel:+1234567890", "twitter:bobby"], restart_participants=True, extra={"foo": "bar"}, include_active=True, ) start.async_start() self.assert_org_queued(self.org, "batch") self.assert_queued_batch_task( self.org, { "type": "start_flow", "org_id": self.org.id, "task": { "start_id": start.id, "start_type": "M", "org_id": self.org.id, "created_by": self.admin.username, "created_by_id": self.admin.id, "flow_id": flow.id, "flow_type": "M", "contact_ids": [jim.id], "group_ids": [bobs.id], "urns": ["tel:+1234567890", "twitter:bobby"], "query": None, "restart_participants": True, "include_active": True, "extra": { "foo": "bar" }, }, "queued_on": matchers.ISODate(), }, )
def fire(self): """ Fires this trigger in response to a schedule """ # do nothing if this trigger is no longer active if self.is_archived or not self.is_active: return groups = list(self.groups.all()) contacts = list(self.contacts.all()) # do nothing if there are no groups or contacts if not groups and not contacts: return start = FlowStart.create(self.flow, self.created_by, groups=groups, contacts=contacts) start.async_start()
def test_queue_flow_start(self): flow = self.get_flow("favorites") jim = self.create_contact("Jim", "+12065551212") bobs = self.create_group("Bobs", [self.create_contact("Bob", "+12065551313")]) start = FlowStart.create( flow, self.admin, groups=[bobs], contacts=[jim], restart_participants=True, extra={"foo": "bar"}, include_active=True, ) with override_settings(TESTING=False): start.async_start() self.assert_org_queued(self.org, "batch") self.assert_queued_batch_task( self.org, { "type": "start_flow", "org_id": self.org.id, "task": { "start_id": start.id, "org_id": self.org.id, "flow_id": flow.id, "flow_type": "M", "contact_ids": [jim.id], "group_ids": [bobs.id], "restart_participants": True, "include_active": True, "extra": { "foo": "bar" }, }, "queued_on": matchers.ISODate(), }, )
def save(self): urns = self.validated_data.get("urns", []) contacts = self.validated_data.get("contacts", []) groups = self.validated_data.get("groups", []) restart_participants = self.validated_data.get("restart_participants", True) extra = self.validated_data.get("extra") # convert URNs to contacts for urn in urns: contact, urn_obj = Contact.get_or_create(self.context["org"], urn, user=self.context["user"]) contacts.append(contact) # ok, let's go create our flow start, the actual starting will happen in our view return FlowStart.create( self.validated_data["flow"], self.context["user"], restart_participants=restart_participants, contacts=contacts, groups=groups, extra=extra, )
def test_status_changes(self): flow = self.create_flow("Test") nodes = flow.get_definition()["nodes"] contact1 = self.create_contact("Joe", phone="1234") contact2 = self.create_contact("Frank", phone="2345") contact3 = self.create_contact("Anne", phone="3456") # simulate a flow start which adds blocks contact1 and stops contact2 start1 = FlowStart.create(flow, self.admin, contacts=[contact1, contact2]) (MockSessionWriter(contact1, flow, start=start1).visit( nodes[0]).set_contact_status("blocked").complete().save()) (MockSessionWriter(contact2, flow, start=start1).visit( nodes[0]).set_contact_status("stopped").complete().save()) t0 = timezone.now() self.assertEqual({contact1}, set(Contact.objects.filter(status="B"))) self.assertEqual({contact2}, set(Contact.objects.filter(status="S"))) self.assertEqual({contact3}, set(Contact.objects.filter(status="A"))) # can run with --dry-run to preview changes call_command("undo_footgun", start=start1.id, dry_run=True, quiet=True) self.assertEqual({contact1}, set(Contact.objects.filter(status="B"))) self.assertEqual({contact2}, set(Contact.objects.filter(status="S"))) self.assertEqual({contact3}, set(Contact.objects.filter(status="A"))) # no contacts will have had modified_on updated self.assertEqual(0, Contact.objects.filter(modified_on__gt=t0).count()) # and then actually make database changes call_command("undo_footgun", start=start1.id, quiet=True) self.assertEqual({contact1, contact2, contact3}, set(Contact.objects.filter(status="A")))
def batch_fire(cls, fires, flow): """ Starts a batch of event fires that are for events which use the same flow """ fired = timezone.now() contacts = [f.contact for f in fires] event = fires[0].event include_active = not ( event.event_type == CampaignEvent.TYPE_MESSAGE and event.start_mode == CampaignEvent.MODE_SKIP ) if event.is_active and not event.campaign.is_archived: if len(contacts) == 1: flow.start( [], contacts, restart_participants=True, include_active=include_active, campaign_event=event ) else: start = FlowStart.create( flow, flow.created_by, contacts=contacts, include_active=include_active, campaign_event=event ) start.async_start() EventFire.objects.filter(id__in=[f.id for f in fires]).update(fired=fired) else: EventFire.objects.filter(id__in=[f.id for f in fires]).delete()