Skip to content

Commit

Permalink
Create optin flow triggers for optin channel events
Browse files Browse the repository at this point in the history
  • Loading branch information
rowanseymour committed Sep 18, 2023
1 parent 972d93b commit 0ed91f1
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 34 deletions.
25 changes: 15 additions & 10 deletions core/tasks/handler/contact_tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/gomodule/redigo/redis"
"github.com/jmoiron/sqlx"
"github.com/nyaruka/gocommon/urns"
"github.com/nyaruka/goflow/assets"
"github.com/nyaruka/goflow/excellent/types"
"github.com/nyaruka/goflow/flows"
"github.com/nyaruka/goflow/flows/engine"
Expand Down Expand Up @@ -242,20 +243,24 @@ func HandleChannelEvent(ctx context.Context, rt *runtime.Runtime, eventType mode
}
}

var optIn *flows.OptIn
if eventType == models.EventTypeOptIn || eventType == models.EventTypeOptOut {
optIn = oa.SessionAssets().OptIns().Get(assets.OptInUUID(event.ExtraString("optin_uuid")))
}

// build our flow trigger
var flowTrigger flows.Trigger
tb := triggers.NewBuilder(oa.Env(), flow.Reference(), contact)
var trig flows.Trigger

if eventType == models.EventTypeIncomingCall {
urn := contacts[0].URNForID(event.URNID())
flowTrigger = triggers.NewBuilder(oa.Env(), flow.Reference(), contact).
Channel(channel.ChannelReference(), triggers.ChannelEventTypeIncomingCall).
WithCall(urn).
Build()
trig = tb.Channel(channel.ChannelReference(), triggers.ChannelEventTypeIncomingCall).WithCall(urn).Build()
} else if eventType == models.EventTypeOptIn && optIn != nil {
trig = tb.OptIn(optIn, triggers.OptInEventTypeStarted).Build()
} else if eventType == models.EventTypeOptOut && optIn != nil {
trig = tb.OptIn(optIn, triggers.OptInEventTypeStopped).Build()
} else {
flowTrigger = triggers.NewBuilder(oa.Env(), flow.Reference(), contact).
Channel(channel.ChannelReference(), triggers.ChannelEventType(eventType)).
WithParams(params).
Build()
trig = tb.Channel(channel.ChannelReference(), triggers.ChannelEventType(eventType)).WithParams(params).Build()
}

// if we have a channel connection we set the connection on the session before our event hooks fire
Expand All @@ -270,7 +275,7 @@ func HandleChannelEvent(ctx context.Context, rt *runtime.Runtime, eventType mode
}
}

sessions, err := runner.StartFlowForContacts(ctx, rt, oa, flow, []*models.Contact{modelContact}, []flows.Trigger{flowTrigger}, hook, true)
sessions, err := runner.StartFlowForContacts(ctx, rt, oa, flow, []*models.Contact{modelContact}, []flows.Trigger{trig}, hook, true)
if err != nil {
return nil, errors.Wrapf(err, "error starting flow for contact")
}
Expand Down
117 changes: 96 additions & 21 deletions core/tasks/handler/handle_contact_event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"
"time"

"github.com/buger/jsonparser"
"github.com/nyaruka/gocommon/dbutil/assertdb"
"github.com/nyaruka/gocommon/jsonx"
"github.com/nyaruka/gocommon/urns"
Expand Down Expand Up @@ -420,26 +421,90 @@ func TestChannelEvents(t *testing.T) {
testdata.InsertOptInTrigger(rt, testdata.Org1, testdata.Favorites, testdata.VonageChannel)
testdata.InsertOptOutTrigger(rt, testdata.Org1, testdata.PickANumber, testdata.VonageChannel)

polls := testdata.InsertOptIn(rt, testdata.Org1, "Polls")

// add a URN for cathy so we can test twitter URNs
testdata.InsertContactURN(rt, testdata.Org1, testdata.Bob, urns.URN("twitterid:123456"), 10)

tcs := []struct {
EventType models.ChannelEventType
ContactID models.ContactID
URNID models.URNID
OrgID models.OrgID
ChannelID models.ChannelID
Extra map[string]any
Response string
UpdateLastSeen bool
EventType models.ChannelEventType
ContactID models.ContactID
URNID models.URNID
ChannelID models.ChannelID
Extra map[string]any
expectedTriggerType string
expectedResponse string
updatesLastSeen bool
}{
{models.EventTypeNewConversation, testdata.Cathy.ID, testdata.Cathy.URNID, testdata.Org1.ID, testdata.TwitterChannel.ID, nil, "What is your favorite color?", true},
{models.EventTypeNewConversation, testdata.Cathy.ID, testdata.Cathy.URNID, testdata.Org1.ID, testdata.VonageChannel.ID, nil, "", true},
{models.EventTypeWelcomeMessage, testdata.Cathy.ID, testdata.Cathy.URNID, testdata.Org1.ID, testdata.VonageChannel.ID, nil, "", false},
{models.EventTypeReferral, testdata.Cathy.ID, testdata.Cathy.URNID, testdata.Org1.ID, testdata.TwitterChannel.ID, nil, "", true},
{models.EventTypeReferral, testdata.Cathy.ID, testdata.Cathy.URNID, testdata.Org1.ID, testdata.VonageChannel.ID, nil, "Pick a number between 1-10.", true},
{models.EventTypeOptIn, testdata.Cathy.ID, testdata.Cathy.URNID, testdata.Org1.ID, testdata.VonageChannel.ID, map[string]any{"optin_name": "Updates"}, "What is your favorite color?", true},
{models.EventTypeOptOut, testdata.Cathy.ID, testdata.Cathy.URNID, testdata.Org1.ID, testdata.VonageChannel.ID, map[string]any{"optin_name": "Updates"}, "Pick a number between 1-10.", true},
{
models.EventTypeNewConversation,
testdata.Cathy.ID,
testdata.Cathy.URNID,
testdata.TwitterChannel.ID,
nil,
"channel",
"What is your favorite color?",
true,
},
{
models.EventTypeNewConversation,
testdata.Cathy.ID,
testdata.Cathy.URNID,
testdata.VonageChannel.ID,
nil,
"",
"",
true,
},
{
models.EventTypeWelcomeMessage,
testdata.Cathy.ID,
testdata.Cathy.URNID,
testdata.VonageChannel.ID,
nil,
"",
"",
false,
},
{
models.EventTypeReferral,
testdata.Cathy.ID,
testdata.Cathy.URNID,
testdata.TwitterChannel.ID,
nil,
"",
"",
true,
},
{
models.EventTypeReferral,
testdata.Cathy.ID, testdata.Cathy.URNID,
testdata.VonageChannel.ID,
nil,
"channel",
"Pick a number between 1-10.",
true,
},
{
models.EventTypeOptIn,
testdata.Cathy.ID,
testdata.Cathy.URNID,
testdata.VonageChannel.ID,
map[string]any{"optin_uuid": polls.UUID},
"optin",
"What is your favorite color?",
true,
},
{
models.EventTypeOptOut,
testdata.Cathy.ID,
testdata.Cathy.URNID,
testdata.VonageChannel.ID,
map[string]any{"optin_uuid": polls.UUID},
"optin",
"Pick a number between 1-10.",
true,
},
}

models.FlushCache()
Expand All @@ -448,13 +513,13 @@ func TestChannelEvents(t *testing.T) {
start := time.Now()
time.Sleep(time.Millisecond * 5)

event := models.NewChannelEvent(tc.EventType, tc.OrgID, tc.ChannelID, tc.ContactID, tc.URNID, tc.Extra, false)
event := models.NewChannelEvent(tc.EventType, testdata.Org1.ID, tc.ChannelID, tc.ContactID, tc.URNID, tc.Extra, false)
eventJSON, err := json.Marshal(event)
assert.NoError(t, err)

task := &queue.Task{
Type: string(tc.EventType),
OrgID: int(tc.OrgID),
OrgID: int(testdata.Org1.ID),
Task: eventJSON,
}

Expand All @@ -467,13 +532,23 @@ func TestChannelEvents(t *testing.T) {
err = tasks.Perform(ctx, rt, task)
assert.NoError(t, err, "%d: error when handling event", i)

// if we are meant to have a response
if tc.Response != "" {
// if we are meant to trigger a new session...
if tc.expectedTriggerType != "" {
assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowsession WHERE contact_id = $1 AND created_on > $2`, tc.ContactID, start).Returns(1)

var output []byte
err = rt.DB.Get(&output, `SELECT output FROM flows_flowsession WHERE contact_id = $1 AND created_on > $2`, tc.ContactID, start)
require.NoError(t, err)

trigType, err := jsonparser.GetString(output, "trigger", "type")
require.NoError(t, err)
assert.Equal(t, tc.expectedTriggerType, trigType)

assertdb.Query(t, rt.DB, `SELECT text FROM msgs_msg WHERE contact_id = $1 AND contact_urn_id = $2 AND created_on > $3 ORDER BY id DESC LIMIT 1`, tc.ContactID, tc.URNID, start).
Returns(tc.Response, "%d: response mismatch", i)
Returns(tc.expectedResponse, "%d: response mismatch", i)
}

if tc.UpdateLastSeen {
if tc.updatesLastSeen {
var lastSeen time.Time
err = rt.DB.Get(&lastSeen, `SELECT last_seen_on FROM contacts_contact WHERE id = $1`, tc.ContactID)
assert.NoError(t, err)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ require (
github.com/lib/pq v1.10.9
github.com/nyaruka/ezconf v0.2.1
github.com/nyaruka/gocommon v1.41.2
github.com/nyaruka/goflow v0.195.0
github.com/nyaruka/goflow v0.195.1
github.com/nyaruka/logrus_sentry v0.8.2-0.20190129182604-c2962b80ba7d
github.com/nyaruka/null/v3 v3.0.0
github.com/nyaruka/redisx v0.5.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0=
github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw=
github.com/nyaruka/gocommon v1.41.2 h1:eGHOJMu9VZhnHri7XzQQax/sW5SSGS2qwS1ORt6idEo=
github.com/nyaruka/gocommon v1.41.2/go.mod h1:cJ2XmEX+FDOzBvE19IW+hG8EFVsSrNgCp7NrxAlP4Xg=
github.com/nyaruka/goflow v0.195.0 h1:AuoNa4w5qtRyrLc7dxvnMr1TW9DGkxpFiVy5vq3gSIw=
github.com/nyaruka/goflow v0.195.0/go.mod h1:ZC+XSZMA+R1HV3C7mfcrEYUb/SyF5BnjnonRBEPA8gM=
github.com/nyaruka/goflow v0.195.1 h1:EV+qbODCq/3DoEkcnS50PamJ8FkzTjwolG5UcIrKBB8=
github.com/nyaruka/goflow v0.195.1/go.mod h1:ZC+XSZMA+R1HV3C7mfcrEYUb/SyF5BnjnonRBEPA8gM=
github.com/nyaruka/librato v1.0.0 h1:Vznj9WCeC1yZXbBYyYp40KnbmXLbEkjKmHesV/v2SR0=
github.com/nyaruka/librato v1.0.0/go.mod h1:pkRNLFhFurOz0QqBz6/DuTFhHHxAubWxs4Jx+J7yUgg=
github.com/nyaruka/logrus_sentry v0.8.2-0.20190129182604-c2962b80ba7d h1:hyp9u36KIwbTCo2JAJ+TuJcJBc+UZzEig7RI/S5Dvkc=
Expand Down
16 changes: 16 additions & 0 deletions testsuite/testdata/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ type Label struct {
UUID assets.LabelUUID
}

type OptIn struct {
ID models.OptInID
UUID assets.OptInUUID
}

// InsertIncomingMsg inserts an incoming text message
func InsertIncomingMsg(rt *runtime.Runtime, org *Org, channel *Channel, contact *Contact, text string, status models.MsgStatus) *MsgIn {
msgUUID := flows.MsgUUID(uuids.New())
Expand Down Expand Up @@ -111,3 +116,14 @@ func InsertBroadcast(rt *runtime.Runtime, org *Org, baseLanguage i18n.Language,

return id
}

// InsertOptIn inserts an opt in
func InsertOptIn(rt *runtime.Runtime, org *Org, name string) *OptIn {
uuid := assets.OptInUUID(uuids.New())
var id models.OptInID
must(rt.DB.Get(&id,
`INSERT INTO msgs_optin(uuid, org_id, name, created_on, modified_on, created_by_id, modified_by_id, is_active, is_system)
VALUES($1, $2, $3, NOW(), NOW(), 1, 1, TRUE, FALSE) RETURNING id`, uuid, org.ID, name,
))
return &OptIn{ID: id, UUID: uuid}
}

0 comments on commit 0ed91f1

Please sign in to comment.