Skip to content

Commit

Permalink
Merge pull request #96 from rapidpro/master
Browse files Browse the repository at this point in the history
Update master
  • Loading branch information
edudouglas committed Nov 5, 2018
2 parents 6aae5df + 79b0721 commit 0fca799
Show file tree
Hide file tree
Showing 41 changed files with 987 additions and 325 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ before_script:
- ln -s $TRAVIS_BUILD_DIR/temba/settings.py.dev $TRAVIS_BUILD_DIR/temba/settings.py

# setup a goflow server instance
- GOFLOW_VERSION=0.19.2
- GOFLOW_VERSION=0.22.0
- wget https://github.com/nyaruka/goflow/releases/download/v${GOFLOW_VERSION}/goflow_${GOFLOW_VERSION}_linux_amd64.tar.gz
- tar -xvf goflow_${GOFLOW_VERSION}_linux_amd64.tar.gz

Expand Down
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
v4.10.1
----------
* Add config to specify content that should be present in the response of the request, if not mark that as msg failed
* Allow campaign events to be skipped if contacts already active in flows

v4.10.0
----------
* Add FlowRun.parent_uuid
* Add FlowSession.timeout_on
* Create new flows with flow_server_enabled when org is enabled
* Add flow-server-enabled to org, dont deal with flow server enabled timeouts or expirations on rapidpro

v4.9.2
----------
* Fix flowserver resume tests by including modified_on on runs sent to goflow

v4.9.1
----------
* Dont set preferred channels if they can't send or call
* Don't assume events from goflow have step_uuid
* Add indexes for flow node and category count squashing

v4.9.0
----------
* Delete event fires in bulk for inactive events
* Fix using contact language for categories when it's not a valid org language
* Fix translation of quick replies
* Add FlowSession.current_flow and start populating
* Refresh contacts list page after managing fields
* Update to latest goflow (no more caller events, resumes, etc)
* Fix flow results export to read old archive format
* Batch event fires by event ID and not by flow ID
* Make campaign events immutable

v4.8.1
----------
* Add novo channel
Expand Down
6 changes: 5 additions & 1 deletion temba/api/v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ def save(self):
flow = self.validated_data.get("flow")

if self.instance:

# we dont update, we only create
self.instance = self.instance.deactivate_and_copy()

# we are being set to a flow
if flow:
self.instance.flow = flow
Expand Down Expand Up @@ -347,7 +351,7 @@ def save(self):
self.instance.update_flow_name()

# create our event fires for this event in the background
EventFire.update_eventfires_for_event(self.instance)
EventFire.create_eventfires_for_event(self.instance)

return self.instance

Expand Down
7 changes: 4 additions & 3 deletions temba/api/v2/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1129,7 +1129,8 @@ def test_campaign_events(self):
)
self.assertEqual(response.status_code, 200)

event1.refresh_from_db()
event1 = CampaignEvent.objects.filter(campaign=campaign1).order_by("-id").first()

self.assertEqual(event1.event_type, CampaignEvent.TYPE_FLOW)
self.assertIsNone(event1.message)
self.assertEqual(event1.flow, flow)
Expand All @@ -1149,7 +1150,7 @@ def test_campaign_events(self):
)
self.assertEqual(response.status_code, 200)

event2.refresh_from_db()
event2 = CampaignEvent.objects.filter(campaign=campaign1).order_by("-id").first()
self.assertEqual(event2.event_type, CampaignEvent.TYPE_MESSAGE)
self.assertEqual(event2.message, {"base": "OK", "fra": "D'accord"})

Expand All @@ -1168,7 +1169,7 @@ def test_campaign_events(self):
)
self.assertEqual(response.status_code, 200)

event2.refresh_from_db()
event2 = CampaignEvent.objects.filter(campaign=campaign1).order_by("-id").first()
self.assertEqual(event2.event_type, CampaignEvent.TYPE_MESSAGE)
self.assertEqual(event2.message, {"base": "OK", "fra": "D'accord", "kin": "Sawa"})

Expand Down
21 changes: 21 additions & 0 deletions temba/campaigns/migrations/0028_campaignevent_start_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 2.0.8 on 2018-10-04 20:30

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [("campaigns", "0027_indexes")]

operations = [
migrations.AddField(
model_name="campaignevent",
name="start_mode",
field=models.CharField(
choices=[("I", "Interrupt"), ("S", "Skip"), ("P", "Passive")],
default="I",
help_text="The start mode of this event",
max_length=1,
),
)
]
130 changes: 94 additions & 36 deletions temba/campaigns/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def import_campaigns(cls, exported_json, org, user, same_site=False):
label=event_spec["relative_to"]["label"],
value_type="D",
)
start_mode = event_spec.get("start_mode", CampaignEvent.MODE_INTERRUPT)

# create our message flow for message events
if event_spec["event_type"] == CampaignEvent.TYPE_MESSAGE:
Expand All @@ -131,6 +132,7 @@ def import_campaigns(cls, exported_json, org, user, same_site=False):
message,
event_spec["delivery_hour"],
base_language=base_language,
start_mode=start_mode,
)
event.update_flow_name()
else:
Expand All @@ -147,6 +149,7 @@ def import_campaigns(cls, exported_json, org, user, same_site=False):
event_spec["unit"],
flow,
event_spec["delivery_hour"],
start_mode=start_mode,
)

# update our scheduled events for this campaign
Expand Down Expand Up @@ -203,6 +206,7 @@ def as_json(self):
delivery_hour=event.delivery_hour,
message=event.message,
relative_to=dict(label=event.relative_to.label, key=event.relative_to.key),
start_mode=event.start_mode,
)

# only include the flow definition for standalone flows
Expand Down Expand Up @@ -261,6 +265,12 @@ class CampaignEvent(TembaModel):

UNIT_CHOICES = [(u[0], u[1]) for u in UNIT_CONFIG]

MODE_INTERRUPT = "I"
MODE_SKIP = "S"
MODE_PASSIVE = "P"

START_MODES_CHOICES = ((MODE_INTERRUPT, _("Interrupt")), (MODE_SKIP, _("Skip")), (MODE_PASSIVE, _("Passive")))

campaign = models.ForeignKey(
Campaign, on_delete=models.PROTECT, related_name="events", help_text="The campaign this event is part of"
)
Expand All @@ -281,6 +291,10 @@ class CampaignEvent(TembaModel):
Flow, on_delete=models.PROTECT, related_name="events", help_text="The flow that will be triggered"
)

start_mode = models.CharField(
max_length=1, choices=START_MODES_CHOICES, default=MODE_INTERRUPT, help_text="The start mode of this event"
)

event_type = models.CharField(
max_length=1, choices=TYPE_CHOICES, default=TYPE_FLOW, help_text="The type of this event"
)
Expand All @@ -292,7 +306,17 @@ class CampaignEvent(TembaModel):

@classmethod
def create_message_event(
cls, org, user, campaign, relative_to, offset, unit, message, delivery_hour=-1, base_language=None
cls,
org,
user,
campaign,
relative_to,
offset,
unit,
message,
delivery_hour=-1,
base_language=None,
start_mode=MODE_INTERRUPT,
):
if campaign.org != org:
raise ValueError("Org mismatch")
Expand All @@ -317,12 +341,15 @@ def create_message_event(
message=message,
flow=flow,
delivery_hour=delivery_hour,
start_mode=start_mode,
created_by=user,
modified_by=user,
)

@classmethod
def create_flow_event(cls, org, user, campaign, relative_to, offset, unit, flow, delivery_hour=-1):
def create_flow_event(
cls, org, user, campaign, relative_to, offset, unit, flow, delivery_hour=-1, start_mode=MODE_INTERRUPT
):
if campaign.org != org:
raise ValueError("Org mismatch")

Expand All @@ -338,6 +365,7 @@ def create_flow_event(cls, org, user, campaign, relative_to, offset, unit, flow,
unit=unit,
event_type=cls.TYPE_FLOW,
flow=flow,
start_mode=start_mode,
delivery_hour=delivery_hour,
created_by=user,
modified_by=user,
Expand All @@ -360,10 +388,14 @@ def get_message(self, contact=None):
if not self.message:
return None

message = None
if contact and contact.language and contact.language in self.message:
return self.message[contact.language]
message = self.message[contact.language]

if not message:
message = self.message[self.flow.base_language]

return self.message[self.flow.base_language]
return message

def update_flow_name(self):
"""
Expand Down Expand Up @@ -451,18 +483,47 @@ def calculate_scheduled_fire_for_value(self, date_value, now):

return None # pragma: no cover

def deactivate_and_copy(self):

self.release()

# clone our event into a new event
if self.event_type == CampaignEvent.TYPE_FLOW:
return CampaignEvent.create_flow_event(
self.campaign.org,
self.created_by,
self.campaign,
self.relative_to,
self.offset,
self.unit,
self.flow,
self.delivery_hour,
self.start_mode,
)

elif self.event_type == CampaignEvent.TYPE_MESSAGE:
return CampaignEvent.create_message_event(
self.campaign.org,
self.created_by,
self.campaign,
self.relative_to,
self.offset,
self.unit,
self.message,
self.delivery_hour,
self.flow.base_language,
self.start_mode,
)

def release(self):
"""
Removes this event.. also takes care of removing any event fires that were scheduled and unfired
Marks the event inactive and releases flows for single message flows
"""

# we need to be inactive so our message flows don't try to circle back and release us
# we need to be inactive so our fires are noops
self.is_active = False
self.save(update_fields=("is_active",))

# delete any event fires
self.event_fires.all().delete()

# detach any associated flow starts
self.flow_starts.all().update(campaign_event=None)

Expand Down Expand Up @@ -503,20 +564,6 @@ def get_relative_to_value(self):
value = self.contact.get_field_value(self.event.relative_to)
return value.replace(second=0, microsecond=0) if value else None

def fire(self):
"""
Actually fires this event for the passed in contact and flow
"""
self.fired = timezone.now()
self.event.flow.start([], [self.contact], restart_participants=True)
self.save(update_fields=("fired",))

def release(self):
"""
Deletes this fire
"""
self.delete()

@classmethod
def batch_fire(cls, fires, flow):
"""
Expand All @@ -526,12 +573,22 @@ def batch_fire(cls, fires, flow):
contacts = [f.contact for f in fires]
event = fires[0].event

if len(contacts) == 1:
flow.start([], contacts, restart_participants=True, campaign_event=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:
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)
EventFire.objects.filter(id__in=[f.id for f in fires]).delete()

@classmethod
def update_campaign_events(cls, campaign):
Expand All @@ -549,20 +606,21 @@ def do_update_campaign_events(cls, campaign):
cls.update_campaign_events_for_contact(campaign, contact)

@classmethod
def update_eventfires_for_event(cls, event):
from temba.campaigns.tasks import update_event_fires
def create_eventfires_for_event(cls, event):
from temba.campaigns.tasks import create_event_fires

on_transaction_commit(lambda: update_event_fires.delay(event.pk))
on_transaction_commit(lambda: create_event_fires.delay(event.pk))

@classmethod
def do_update_eventfires_for_event(cls, event):
# unschedule any fires
EventFire.objects.filter(event=event, fired=None).delete()
def do_create_eventfires_for_event(cls, event):

if EventFire.objects.filter(event=event).exists():
return

# add new ones if this event exists and the campaign is active
if event.is_active and not event.campaign.is_archived:
field = event.relative_to

# create fires for our event
field = event.relative_to
if field.field_type == ContactField.FIELD_TYPE_USER:
field_uuid = str(field.uuid)

Expand Down
Loading

0 comments on commit 0fca799

Please sign in to comment.