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 all commits
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
23 changes: 22 additions & 1 deletion flows/actions/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
},
Expand Down Expand Up @@ -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 {
Expand Down
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
}
203 changes: 203 additions & 0 deletions flows/actions/testdata/trigger_session.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
[
{
"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
}
}
]
},
{
"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
}
}
]
}
]
125 changes: 125 additions & 0 deletions flows/actions/trigger_session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package actions

import (
"fmt"

"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 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",
// "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,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, 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")

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

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L62

Added line #L62 was not covered by tests
}
return nil
}

// Execute runs our action
func (a *TriggerSessionAction) Execute(run flows.Run, step flows.Step, logModifier flows.ModifierCallback, logEvent flows.EventCallback) error {
urn := a.resolveURN(run, logEvent)

if urn == urns.NilURN && a.Contact == nil {
return nil

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

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L72

Added line #L72 was 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 86 in flows/actions/trigger_session.go

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L85-L86

Added lines #L85 - L86 were not covered by tests
}

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

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

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L91

Added line #L91 was not covered by tests
}

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

logEvent(events.NewSessionTriggered(flow.Reference(false), a.Contact, urn, a.Interrupt, runSnapshot, history))
return nil
}

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.URN, logEvent)
if !ok {
return urns.NilURN

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

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L108

Added line #L108 was not covered by tests
}

// if we have a valid URN now, return it
urn := urns.URN(evaluatedURN)
if urn.Validate() == nil {
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 urn.Normalize()

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

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L118-L121

Added lines #L118 - L121 were not covered by tests
}

return urns.NilURN

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

View check run for this annotation

Codecov / codecov/patch

flows/actions/trigger_session.go#L124

Added line #L124 was not covered by tests
}
5 changes: 4 additions & 1 deletion flows/definition/migrations/specdata/templates.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@
".groups[*].name_match",
".legacy_vars[*]"
],
"transfer_airtime": []
"transfer_airtime": [],
"trigger_session": [
".urn"
]
},
"routers": {
"random": [
Expand Down
Loading
Loading