Example #1
0
    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())
Example #2
0
    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)}
Example #3
0
    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()
Example #4
0
    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,
        )
Example #5
0
    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)}
Example #6
0
    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()
Example #7
0
    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()
Example #8
0
    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
Example #9
0
    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
Example #10
0
    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)
Example #11
0
    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)
Example #12
0
    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)
Example #13
0
    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)
Example #14
0
    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(),
            },
        )
Example #15
0
    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()
Example #16
0
    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(),
            },
        )
Example #17
0
    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,
        )
Example #18
0
    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")))
Example #19
0
    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()