Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new trigger_session action to replace start_session #1288

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions flows/actions/start_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ import (
"github.com/nyaruka/goflow/flows/events"
)

// max number of times a session can trigger another session without there being input from the contact
const maxAncestorsSinceInput = 5

func init() {
registerType(TypeStartSession, func() flows.Action { return &StartSessionAction{} })
}
Expand Down Expand Up @@ -97,6 +94,6 @@ func (a *StartSessionAction) Execute(run flows.Run, step flows.Step, logModifier

history := flows.NewChildHistory(run.Session())

logEvent(events.NewSessionTriggered(flow.Reference(false), groupRefs, contactRefs, contactQuery, a.Exclusions, a.CreateContact, urnList, runSnapshot, history))
logEvent(events.NewLegacySessionTriggered(flow.Reference(false), groupRefs, contactRefs, contactQuery, a.Exclusions, a.CreateContact, urnList, runSnapshot, history))
return nil
}
3 changes: 1 addition & 2 deletions flows/actions/testdata/send_broadcast.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,7 @@
"name": "Stavros"
},
{
"uuid": "11708c34-d4ab-4b04-b82a-2578f6e0013c",
"name": ""
"uuid": "11708c34-d4ab-4b04-b82a-2578f6e0013c"
}
],
"contact_query": "name = \"Bob\"",
Expand Down
133 changes: 133 additions & 0 deletions flows/actions/testdata/trigger_session.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
[
{
"description": "Error event and NOOP if flow missing",
"action": {
"type": "trigger_session",
"uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
"flow": {
"uuid": "dede1e50-db55-4b50-8929-2116bfc56148",
"name": "Missing"
},
"contact": {
"uuid": "945493e3-933f-4668-9761-ce990fae5e5c",
"name": "Stavros"
},
"interrupt": true
},
"events": [
{
"type": "error",
"created_on": "2018-10-18T14:20:30.000123456Z",
"step_uuid": "59d74b86-3e2f-4a93-aece-b05d2fdcde0c",
"text": "missing dependency: flow[uuid=dede1e50-db55-4b50-8929-2116bfc56148,name=Missing]"
}
],
"inspection": {
"dependencies": [
{
"uuid": "dede1e50-db55-4b50-8929-2116bfc56148",
"name": "Missing",
"type": "flow",
"missing": true
},
{
"uuid": "945493e3-933f-4668-9761-ce990fae5e5c",
"name": "Stavros",
"type": "contact"
}
],
"issues": [
{
"type": "missing_dependency",
"node_uuid": "72a1f5df-49f9-45df-94c9-d86f7ea064e5",
"action_uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
"description": "missing flow dependency 'dede1e50-db55-4b50-8929-2116bfc56148'",
"dependency": {
"uuid": "dede1e50-db55-4b50-8929-2116bfc56148",
"name": "Missing",
"type": "flow"
}
}
],
"results": [],
"waiting_exits": [],
"parent_refs": []
}
},
{
"description": "Session triggered event with concrete contact reference",
"action": {
"type": "trigger_session",
"uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
"flow": {
"uuid": "b7cf0d83-f1c9-411c-96fd-c511a4cfa86d",
"name": "Collect Age"
},
"contact": {
"uuid": "945493e3-933f-4668-9761-ce990fae5e5c",
"name": "Stavros"
},
"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"
},
"contact": {
"uuid": "945493e3-933f-4668-9761-ce990fae5e5c",
"name": "Stavros"
},
"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
}
}
]
}
]
114 changes: 114 additions & 0 deletions flows/actions/trigger_session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package actions

import (
"github.com/nyaruka/gocommon/jsonx"
"github.com/nyaruka/gocommon/urns"
"github.com/nyaruka/goflow/assets"
"github.com/nyaruka/goflow/flows"
"github.com/nyaruka/goflow/flows/events"
"github.com/nyaruka/goflow/utils"
)

// max number of times a session can trigger another session without there being input from the contact
const maxAncestorsSinceInput = 5

func init() {
registerType(TypeTriggerSession, func() flows.Action { return &TriggerSessionAction{} })
}

// TypeTriggerSession is the type for the trigger session action
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.
//
// {
// "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9",
// "type": "trigger_session",
// "flow": {"uuid": "b7cf0d83-f1c9-411c-96fd-c511a4cfa86d", "name": "Registration"},
// "contact": {"uuid": "1e1ce1e1-9288-4504-869e-022d1003c72a", "name": "Bob"},
// "interrupt": true
// }
//
// @action start_session
type TriggerSessionAction struct {
baseAction
onlineAction

Flow *assets.FlowReference `json:"flow" validate:"required"`
Contact *flows.ContactReference `json:"contact" validate:"required"`
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 {
return &TriggerSessionAction{
baseAction: newBaseAction(TypeTriggerSession, uuid),
Flow: flow,
Contact: contact,
Interrupt: interrupt,

Check warning on line 51 in flows/actions/trigger_session.go

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L46-L51

Added lines #L46 - L51 were not covered by tests
}
}

// 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))
return nil

Check warning on line 60 in flows/actions/trigger_session.go

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L59-L60

Added lines #L59 - L60 were not covered by tests
}

// check that flow exists - error event if not
flow, err := run.Session().Assets().Flows().Get(a.Flow.UUID)
if err != nil {
logEvent(events.NewDependencyError(a.Flow))
return nil
}

// loop footgun prevention
ref := run.Session().History()
if ref.AncestorsSinceInput >= maxAncestorsSinceInput {
logEvent(events.NewErrorf("too many sessions have been spawned since the last time input was received"))
return nil

Check warning on line 74 in flows/actions/trigger_session.go

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L73-L74

Added lines #L73 - L74 were not covered by tests
}

runSnapshot, err := jsonx.Marshal(run.Snapshot())
if err != nil {
return err

Check warning on line 79 in flows/actions/trigger_session.go

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L79

Added line #L79 was not covered by tests
}

history := flows.NewChildHistory(run.Session())

logEvent(events.NewSessionTriggered(flow.Reference(false), contact, 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
}

// otherwise this is a variable reference so evaluate it
evaluatedURN, ok := run.EvaluateTemplate(a.Contact.URNMatch, logEvent)
if !ok {
return nil

Check warning on line 97 in flows/actions/trigger_session.go

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L95-L97

Added lines #L95 - L97 were not covered by tests
}

// if we have a valid URN now, return it
urn := urns.URN(evaluatedURN)
if urn.Validate() == nil {
return &flows.ContactReference{URNMatch: string(urn.Normalize())}

Check warning on line 103 in flows/actions/trigger_session.go

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L101-L103

Added lines #L101 - L103 were not covered by tests
}

// 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())}

Check warning on line 110 in flows/actions/trigger_session.go

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L107-L110

Added lines #L107 - L110 were not covered by tests
}

return nil

Check warning on line 113 in flows/actions/trigger_session.go

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L113

Added line #L113 was not covered by tests
}
5 changes: 3 additions & 2 deletions flows/contact.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,8 +544,9 @@ var _ contactql.Queryable = (*Contact)(nil)

// ContactReference is used to reference a contact
type ContactReference struct {
UUID ContactUUID `json:"uuid" validate:"required,uuid4"`
Name string `json:"name"`
UUID ContactUUID `json:"uuid,omitempty" validate:"omitempty,uuid4"`
Name string `json:"name,omitempty"`
URNMatch string `json:"urn_match,omitempty" engine:"evaluated"`
}

// NewContactReference creates a new contact reference with the given UUID and name
Expand Down
9 changes: 3 additions & 6 deletions flows/definition/legacy/testdata/actions.json
Original file line number Diff line number Diff line change
Expand Up @@ -474,8 +474,7 @@
"name": "Horatio"
},
{
"uuid": "cd0d8605-5abc-428c-b34b-c6f6e7a3ef42",
"name": ""
"uuid": "cd0d8605-5abc-428c-b34b-c6f6e7a3ef42"
}
],
"groups": [
Expand Down Expand Up @@ -596,12 +595,10 @@
"uuid": "5a4d00aa-807e-44af-9693-64b9fdedd352",
"contacts": [
{
"uuid": "879ace1b-740b-45f1-9198-c2f2f08a825f",
"name": ""
"uuid": "879ace1b-740b-45f1-9198-c2f2f08a825f"
},
{
"uuid": "cd0d8605-5abc-428c-b34b-c6f6e7a3ef42",
"name": ""
"uuid": "cd0d8605-5abc-428c-b34b-c6f6e7a3ef42"
}
],
"groups": [
Expand Down
7 changes: 6 additions & 1 deletion flows/definition/migrations/specdata/templates.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"send_broadcast": [
".attachments[*]",
".contact_query",
".contacts[*].urn_match",
".groups[*].name_match",
".legacy_vars[*]",
".quick_replies[*]",
Expand Down Expand Up @@ -72,10 +73,14 @@
],
"start_session": [
".contact_query",
".contacts[*].urn_match",
".groups[*].name_match",
".legacy_vars[*]"
],
"transfer_airtime": []
"transfer_airtime": [],
"trigger_session": [
".contact.urn_match"
]
},
"routers": {
"random": [
Expand Down
2 changes: 1 addition & 1 deletion flows/events/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ func TestEventMarshaling(t *testing.T) {
}`,
},
{
events.NewSessionTriggered(
events.NewLegacySessionTriggered(
assets.NewFlowReference(assets.FlowUUID("e4d441f0-24e3-4627-85fb-1e99e733baf0"), "Collect Age"),
[]*assets.GroupReference{
assets.NewGroupReference(assets.GroupUUID("5f9fd4f7-4b0f-462a-a598-18bfc7810412"), "Supervisors"),
Expand Down
24 changes: 20 additions & 4 deletions flows/events/session_triggered.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,35 @@
type SessionTriggeredEvent struct {
BaseEvent

Flow *assets.FlowReference `json:"flow" validate:"required"`
Flow *assets.FlowReference `json:"flow" validate:"required"`
Contact *flows.ContactReference `json:"contact,omitempty"`
Interrupt bool `json:"interrupt,omitempty"`
RunSummary json.RawMessage `json:"run_summary"`
History *flows.SessionHistory `json:"history"`

// deprecated (used by StartSessionAction)
Groups []*assets.GroupReference `json:"groups,omitempty" validate:"dive"`
Contacts []*flows.ContactReference `json:"contacts,omitempty" validate:"dive"`
ContactQuery string `json:"contact_query,omitempty"`
Exclusions Exclusions `json:"exclusions"`
CreateContact bool `json:"create_contact,omitempty"`
URNs []urns.URN `json:"urns,omitempty" validate:"dive,urn"`
RunSummary json.RawMessage `json:"run_summary"`
History *flows.SessionHistory `json:"history"`
}

// NewSessionTriggered returns a new session triggered event
func NewSessionTriggered(flow *assets.FlowReference, groups []*assets.GroupReference, contacts []*flows.ContactReference, contactQuery string, exclusions Exclusions, createContact bool, urns []urns.URN, runSummary json.RawMessage, history *flows.SessionHistory) *SessionTriggeredEvent {
func NewSessionTriggered(flow *assets.FlowReference, contact *flows.ContactReference, interrupt bool, runSummary json.RawMessage, history *flows.SessionHistory) *SessionTriggeredEvent {
return &SessionTriggeredEvent{
BaseEvent: NewBaseEvent(TypeSessionTriggered),
Flow: flow,
Contact: contact,
Interrupt: interrupt,
RunSummary: runSummary,
History: history,

Check warning on line 82 in flows/events/session_triggered.go

View check run for this annotation

Codecov / codecov/patch

flows/events/session_triggered.go#L75-L82

Added lines #L75 - L82 were not covered by tests
}
}

// NewLegacySessionTriggered returns a new session triggered event
func NewLegacySessionTriggered(flow *assets.FlowReference, groups []*assets.GroupReference, contacts []*flows.ContactReference, contactQuery string, exclusions Exclusions, createContact bool, urns []urns.URN, runSummary json.RawMessage, history *flows.SessionHistory) *SessionTriggeredEvent {
return &SessionTriggeredEvent{
BaseEvent: NewBaseEvent(TypeSessionTriggered),
Flow: flow,
Expand Down
Loading