diff --git a/flows/actions/base_test.go b/flows/actions/base_test.go index 92a55867a..bae19b5dc 100644 --- a/flows/actions/base_test.go +++ b/flows/actions/base_test.go @@ -697,7 +697,6 @@ func TestConstructors(t *testing.T) { actions.NewStartSession( actionUUID, assets.NewFlowReference(assets.FlowUUID("fece6eac-9127-4343-9269-56e88f391562"), "Parent"), - []*assets.GroupReference{ assets.NewGroupReference(assets.GroupUUID("b7cf0d83-f1c9-411c-96fd-c511a4cfa86d"), "Testers"), }, @@ -734,6 +733,28 @@ func TestConstructors(t *testing.T) { "create_contact": true }`, }, + { + actions.NewTriggerSession( + actionUUID, + assets.NewFlowReference(assets.FlowUUID("fece6eac-9127-4343-9269-56e88f391562"), "Parent"), + flows.NewContactReference(flows.ContactUUID("cbe87f5c-cda2-4f90-b5dd-0ac93a884950"), "Bob Smith"), + "", + true, + ), + `{ + "type": "trigger_session", + "uuid": "ad154980-7bf7-4ab8-8728-545fd6378912", + "flow": { + "uuid": "fece6eac-9127-4343-9269-56e88f391562", + "name": "Parent" + }, + "contact": { + "uuid": "cbe87f5c-cda2-4f90-b5dd-0ac93a884950", + "name": "Bob Smith" + }, + "interrupt": true + }`, + }, } for _, tc := range tests { diff --git a/flows/actions/testdata/send_broadcast.json b/flows/actions/testdata/send_broadcast.json index 8c44e01c2..68e4e3fdf 100644 --- a/flows/actions/testdata/send_broadcast.json +++ b/flows/actions/testdata/send_broadcast.json @@ -195,7 +195,8 @@ "name": "Stavros" }, { - "uuid": "11708c34-d4ab-4b04-b82a-2578f6e0013c" + "uuid": "11708c34-d4ab-4b04-b82a-2578f6e0013c", + "name": "" } ], "contact_query": "name = \"Bob\"", diff --git a/flows/actions/testdata/trigger_session.json b/flows/actions/testdata/trigger_session.json index 6ce102ffa..620e2661c 100644 --- a/flows/actions/testdata/trigger_session.json +++ b/flows/actions/testdata/trigger_session.json @@ -129,5 +129,75 @@ } } ] + }, + { + "description": "Session triggered event with URN", + "action": { + "type": "trigger_session", + "uuid": "ad154980-7bf7-4ab8-8728-545fd6378912", + "flow": { + "uuid": "b7cf0d83-f1c9-411c-96fd-c511a4cfa86d", + "name": "Collect Age" + }, + "urn": "@(\"tel:+593979123456\")", + "interrupt": true + }, + "events": [ + { + "type": "session_triggered", + "created_on": "2018-10-18T14:20:30.000123456Z", + "step_uuid": "59d74b86-3e2f-4a93-aece-b05d2fdcde0c", + "flow": { + "uuid": "b7cf0d83-f1c9-411c-96fd-c511a4cfa86d", + "name": "Collect Age" + }, + "urn": "tel:+593979123456", + "interrupt": true, + "exclusions": {}, + "run_summary": { + "uuid": "e7187099-7d38-4f60-955c-325957214c42", + "flow": { + "uuid": "bead76f5-dac4-4c9d-996c-c62b326e8c0a", + "name": "Action Tester", + "revision": 123 + }, + "contact": { + "uuid": "5d76d86b-3bb9-4d5a-b822-c9d86f5d8e4f", + "name": "Ryan Lewis", + "language": "eng", + "last_seen_on": "2018-10-18T14:20:30.000123456Z", + "status": "active", + "timezone": "America/Guayaquil", + "created_on": "2018-06-20T11:40:30.123456789Z", + "urns": [ + "tel:+12065551212?channel=57f1078f-88aa-46f4-a59a-948a5739c03d&id=123", + "twitterid:54784326227#nyaruka" + ], + "groups": [ + { + "uuid": "b7cf0d83-f1c9-411c-96fd-c511a4cfa86d", + "name": "Testers" + }, + { + "uuid": "0ec97956-c451-48a0-a180-1ce766623e31", + "name": "Males" + } + ], + "fields": { + "gender": { + "text": "Male" + } + } + }, + "status": "active", + "results": {} + }, + "history": { + "parent_uuid": "1ae96956-4b34-433e-8d1a-f05fe6923d6d", + "ancestors": 1, + "ancestors_since_input": 0 + } + } + ] } ] \ No newline at end of file diff --git a/flows/actions/trigger_session.go b/flows/actions/trigger_session.go index 8e41065f3..4f8e969bb 100644 --- a/flows/actions/trigger_session.go +++ b/flows/actions/trigger_session.go @@ -1,6 +1,8 @@ package actions import ( + "fmt" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/goflow/assets" @@ -21,8 +23,8 @@ const TypeTriggerSession string = "trigger_session" // TriggerSessionAction can be used to trigger sessions for another contact. A [event:session_triggered] event will be // created and it's the responsibility of the caller to act on that by initiating a new session with the flow engine. -// The contact can be specified via a concrete reference or as a URN via the scheme and path fields. In the latter case -// the contact will be created if they don't exist. +// The contact can be specified via a reference or as a URN. In the latter case the contact will be created if they +// don't exist. // // { // "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9", @@ -37,26 +39,36 @@ type TriggerSessionAction struct { baseAction onlineAction - Flow *assets.FlowReference `json:"flow" validate:"required"` - Contact *flows.ContactReference `json:"contact" validate:"required"` + Flow *assets.FlowReference `json:"flow" validate:"required"` + Contact *flows.ContactReference `json:"contact,omitempty"` + URN string `json:"urn,omitempty" engine:"evaluated"` Interrupt bool `json:"interrupt"` } // NewTriggerSession creates a new trigger session action -func NewTriggerSession(uuid flows.ActionUUID, flow *assets.FlowReference, contact *flows.ContactReference, interrupt bool) *TriggerSessionAction { +func NewTriggerSession(uuid flows.ActionUUID, flow *assets.FlowReference, contact *flows.ContactReference, urn string, interrupt bool) *TriggerSessionAction { return &TriggerSessionAction{ baseAction: newBaseAction(TypeTriggerSession, uuid), Flow: flow, Contact: contact, + URN: urn, Interrupt: interrupt, } } +// Validate validates our action is valid +func (a *TriggerSessionAction) Validate() error { + if (a.Contact != nil && a.URN != "") || (a.Contact == nil && a.URN == "") { + return fmt.Errorf("must specify either contact or urn") + } + return nil +} + // Execute runs our action func (a *TriggerSessionAction) Execute(run flows.Run, step flows.Step, logModifier flows.ModifierCallback, logEvent flows.EventCallback) error { - contact := a.resolveContact(run, logEvent) - if contact == nil { - logEvent(events.NewDependencyError(a.Contact)) + urn := a.resolveURN(run, logEvent) + + if urn == urns.NilURN && a.Contact == nil { return nil } @@ -81,34 +93,33 @@ func (a *TriggerSessionAction) Execute(run flows.Run, step flows.Step, logModifi history := flows.NewChildHistory(run.Session()) - logEvent(events.NewSessionTriggered(flow.Reference(false), contact, a.Interrupt, runSnapshot, history)) + logEvent(events.NewSessionTriggered(flow.Reference(false), a.Contact, urn, a.Interrupt, runSnapshot, history)) return nil } -func (a *TriggerSessionAction) resolveContact(run flows.Run, logEvent flows.EventCallback) *flows.ContactReference { - // if this is a concrete reference, return as is - if !a.Contact.Variable() { - return a.Contact +func (a *TriggerSessionAction) resolveURN(run flows.Run, logEvent flows.EventCallback) urns.URN { + if a.URN == "" { + return urns.NilURN } // otherwise this is a variable reference so evaluate it - evaluatedURN, ok := run.EvaluateTemplate(a.Contact.URNMatch, logEvent) + evaluatedURN, ok := run.EvaluateTemplate(a.URN, logEvent) if !ok { - return nil + return urns.NilURN } // if we have a valid URN now, return it urn := urns.URN(evaluatedURN) if urn.Validate() == nil { - return &flows.ContactReference{URNMatch: string(urn.Normalize())} + return urn.Normalize() } // otherwise try to parse as phone number parsedTel := utils.ParsePhoneNumber(evaluatedURN, run.Session().MergedEnvironment().DefaultCountry()) if parsedTel != "" { urn, _ := urns.New(urns.Phone, parsedTel) - return &flows.ContactReference{URNMatch: string(urn.Normalize())} + return urn.Normalize() } - return nil + return urns.NilURN } diff --git a/flows/contact.go b/flows/contact.go index 6cc688aa1..76d48bd7c 100644 --- a/flows/contact.go +++ b/flows/contact.go @@ -544,9 +544,8 @@ var _ contactql.Queryable = (*Contact)(nil) // ContactReference is used to reference a contact type ContactReference struct { - UUID ContactUUID `json:"uuid,omitempty" validate:"omitempty,uuid4"` - Name string `json:"name,omitempty"` - URNMatch string `json:"urn_match,omitempty" engine:"evaluated"` + UUID ContactUUID `json:"uuid" validate:"required,uuid4"` + Name string `json:"name"` } // NewContactReference creates a new contact reference with the given UUID and name diff --git a/flows/definition/legacy/testdata/actions.json b/flows/definition/legacy/testdata/actions.json index 33a93964e..26296fbb7 100644 --- a/flows/definition/legacy/testdata/actions.json +++ b/flows/definition/legacy/testdata/actions.json @@ -474,7 +474,8 @@ "name": "Horatio" }, { - "uuid": "cd0d8605-5abc-428c-b34b-c6f6e7a3ef42" + "uuid": "cd0d8605-5abc-428c-b34b-c6f6e7a3ef42", + "name": "" } ], "groups": [ @@ -595,10 +596,12 @@ "uuid": "5a4d00aa-807e-44af-9693-64b9fdedd352", "contacts": [ { - "uuid": "879ace1b-740b-45f1-9198-c2f2f08a825f" + "uuid": "879ace1b-740b-45f1-9198-c2f2f08a825f", + "name": "" }, { - "uuid": "cd0d8605-5abc-428c-b34b-c6f6e7a3ef42" + "uuid": "cd0d8605-5abc-428c-b34b-c6f6e7a3ef42", + "name": "" } ], "groups": [ diff --git a/flows/definition/migrations/specdata/templates.json b/flows/definition/migrations/specdata/templates.json index c57c4be23..0d2a9f7e1 100644 --- a/flows/definition/migrations/specdata/templates.json +++ b/flows/definition/migrations/specdata/templates.json @@ -37,7 +37,6 @@ "send_broadcast": [ ".attachments[*]", ".contact_query", - ".contacts[*].urn_match", ".groups[*].name_match", ".legacy_vars[*]", ".quick_replies[*]", @@ -73,13 +72,12 @@ ], "start_session": [ ".contact_query", - ".contacts[*].urn_match", ".groups[*].name_match", ".legacy_vars[*]" ], "transfer_airtime": [], "trigger_session": [ - ".contact.urn_match" + ".urn" ] }, "routers": { diff --git a/flows/events/session_triggered.go b/flows/events/session_triggered.go index 8c31a7fc7..f91104ef7 100644 --- a/flows/events/session_triggered.go +++ b/flows/events/session_triggered.go @@ -58,6 +58,7 @@ type SessionTriggeredEvent struct { Flow *assets.FlowReference `json:"flow" validate:"required"` Contact *flows.ContactReference `json:"contact,omitempty"` + URN urns.URN `json:"urn,omitempty" validate:"omitempty,urn"` Interrupt bool `json:"interrupt,omitempty"` RunSummary json.RawMessage `json:"run_summary"` History *flows.SessionHistory `json:"history"` @@ -72,11 +73,12 @@ type SessionTriggeredEvent struct { } // NewSessionTriggered returns a new session triggered event -func NewSessionTriggered(flow *assets.FlowReference, contact *flows.ContactReference, interrupt bool, runSummary json.RawMessage, history *flows.SessionHistory) *SessionTriggeredEvent { +func NewSessionTriggered(flow *assets.FlowReference, contact *flows.ContactReference, urn urns.URN, interrupt bool, runSummary json.RawMessage, history *flows.SessionHistory) *SessionTriggeredEvent { return &SessionTriggeredEvent{ BaseEvent: NewBaseEvent(TypeSessionTriggered), Flow: flow, Contact: contact, + URN: urn, Interrupt: interrupt, RunSummary: runSummary, History: history,