From 0b5ff74635e162f65d9004f290b0905036f1f250 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 12 Mar 2018 16:03:22 -0500 Subject: [PATCH 1/3] Rename label and group actions --- cmd/flowrunner/testdata/flows/all_actions.json | 6 +++--- cmd/flowrunner/testdata/flows/brochure.json | 2 +- cmd/flowrunner/testdata/flows/no_contact.json | 4 ++-- cmd/flowserver/server_test.go | 6 +++--- .../{add_to_group.go => add_contact_groups.go} | 18 +++++++++--------- .../{add_label.go => add_input_labels.go} | 18 +++++++++--------- flows/actions/envelope.go | 8 ++++---- ..._from_group.go => remove_contact_groups.go} | 6 +++--- flows/definition/legacy.go | 4 ++-- .../testdata/migrations/actions.json | 6 +++--- flows/engine/assets_test.go | 12 ++++++------ flows/engine/testdata/assets.json | 4 ++-- 12 files changed, 47 insertions(+), 47 deletions(-) rename flows/actions/{add_to_group.go => add_contact_groups.go} (72%) rename flows/actions/{add_label.go => add_input_labels.go} (68%) rename flows/actions/{remove_from_group.go => remove_contact_groups.go} (94%) diff --git a/cmd/flowrunner/testdata/flows/all_actions.json b/cmd/flowrunner/testdata/flows/all_actions.json index e1114a2a5..76323cfb2 100644 --- a/cmd/flowrunner/testdata/flows/all_actions.json +++ b/cmd/flowrunner/testdata/flows/all_actions.json @@ -12,7 +12,7 @@ "actions": [ { "uuid": "ad154980-7bf7-4ab8-8728-545fd6378912", - "type": "add_label", + "type": "add_input_labels", "labels": [ { "uuid": "3f65d88a-95dc-4140-9451-943e94e06fea", @@ -25,7 +25,7 @@ }, { "uuid": "ad154980-7bf7-4ab8-8728-545fd6378912", - "type": "add_to_group", + "type": "add_contact_groups", "groups": [ { "uuid": "2aad21f6-30b7-42c5-bd7f-1b720c154817", @@ -107,7 +107,7 @@ }, { "uuid": "6d1346c0-48d8-4108-9c58-e45a1eb0ff7a", - "type": "remove_from_group", + "type": "remove_contact_groups", "groups": [ { "uuid": "2aad21f6-30b7-42c5-bd7f-1b720c154817", diff --git a/cmd/flowrunner/testdata/flows/brochure.json b/cmd/flowrunner/testdata/flows/brochure.json index 0f710cb8c..1751340e4 100644 --- a/cmd/flowrunner/testdata/flows/brochure.json +++ b/cmd/flowrunner/testdata/flows/brochure.json @@ -72,7 +72,7 @@ }, { "uuid": "b3fa763e-474b-49df-b4d6-15e86507668f", - "type": "add_to_group", + "type": "add_contact_groups", "groups": [ { "uuid": "7be2f40b-38a0-4b06-9e6d-522dca592cc8", diff --git a/cmd/flowrunner/testdata/flows/no_contact.json b/cmd/flowrunner/testdata/flows/no_contact.json index 2b98f2b4d..d5d78a34b 100644 --- a/cmd/flowrunner/testdata/flows/no_contact.json +++ b/cmd/flowrunner/testdata/flows/no_contact.json @@ -12,7 +12,7 @@ "actions": [ { "uuid": "ad154980-7bf7-4ab8-8728-545fd6378912", - "type": "add_label", + "type": "add_input_labels", "labels": [ { "uuid": "3f65d88a-95dc-4140-9451-943e94e06fea", @@ -22,7 +22,7 @@ }, { "uuid": "ad154980-7bf7-4ab8-8728-545fd6378912", - "type": "add_to_group", + "type": "add_contact_groups", "groups": [ { "uuid": "2aad21f6-30b7-42c5-bd7f-1b720c154817", diff --git a/cmd/flowserver/server_test.go b/cmd/flowserver/server_test.go index 40528a2b9..39735f88d 100644 --- a/cmd/flowserver/server_test.go +++ b/cmd/flowserver/server_test.go @@ -57,7 +57,7 @@ var testFlowMissingGroupAssets = `[ "actions": [ { "uuid": "ad154980-7bf7-4ab8-8728-545fd6378912", - "type": "add_to_group", + "type": "add_contact_groups", "groups": [ { "uuid": "77a1bb5c-92f7-42bc-8a54-d21c1536ebc0", @@ -335,7 +335,7 @@ func (ts *ServerTestSuite) TestFlowStartAndResume() { requestBody = fmt.Sprintf(startRequestTemplate, testFlowMissingGroupAssets) status, body = ts.testHTTPRequest("POST", "http://localhost:8800/flow/start", requestBody) ts.Equal(400, status) - ts.assertErrorResponse(body, []string{"validation failed for flow[uuid=76f0a02f-3b75-4b86-9064-e9195e1b3a02]: validation failed for action[uuid=ad154980-7bf7-4ab8-8728-545fd6378912, type=add_to_group]: no such group with uuid '77a1bb5c-92f7-42bc-8a54-d21c1536ebc0'"}) + ts.assertErrorResponse(body, []string{"validation failed for flow[uuid=76f0a02f-3b75-4b86-9064-e9195e1b3a02]: validation failed for action[uuid=ad154980-7bf7-4ab8-8728-545fd6378912, type=add_contact_groups]: no such group with uuid '77a1bb5c-92f7-42bc-8a54-d21c1536ebc0'"}) // POST to the start endpoint with a valid flow with no wait (it should complete) requestBody = fmt.Sprintf(startRequestTemplate, testValidFlowWithNoWaitAssets) @@ -379,7 +379,7 @@ func (ts *ServerTestSuite) TestFlowStartAndResume() { events.NewMsgReceivedEvent(msg), })) ts.Equal(400, status) - ts.assertErrorResponse(body, []string{"validation failed for flow[uuid=76f0a02f-3b75-4b86-9064-e9195e1b3a02]: validation failed for action[uuid=ad154980-7bf7-4ab8-8728-545fd6378912, type=add_to_group]: no such group with uuid '77a1bb5c-92f7-42bc-8a54-d21c1536ebc0'"}) + ts.assertErrorResponse(body, []string{"validation failed for flow[uuid=76f0a02f-3b75-4b86-9064-e9195e1b3a02]: validation failed for action[uuid=ad154980-7bf7-4ab8-8728-545fd6378912, type=add_contact_groups]: no such group with uuid '77a1bb5c-92f7-42bc-8a54-d21c1536ebc0'"}) // check we can resume if we include a fixed version of the flow as an asset status, body = ts.testHTTPRequest("POST", "http://localhost:8800/flow/resume", ts.buildResumeRequest(testValidFlowWithWaitAssets, waitingSession, []flows.Event{ diff --git a/flows/actions/add_to_group.go b/flows/actions/add_contact_groups.go similarity index 72% rename from flows/actions/add_to_group.go rename to flows/actions/add_contact_groups.go index dcfd7dd88..57120bef2 100644 --- a/flows/actions/add_to_group.go +++ b/flows/actions/add_contact_groups.go @@ -7,16 +7,16 @@ import ( "github.com/nyaruka/goflow/flows/events" ) -// TypeAddToGroup is our type for the add to group action -const TypeAddToGroup string = "add_to_group" +// TypeAddContactGroups is our type for the add to group action +const TypeAddContactGroups string = "add_contact_groups" -// AddToGroupAction can be used to add a contact to one or more groups. An `contact_groups_added` event will be created +// AddContactGroupsAction can be used to add a contact to one or more groups. An `contact_groups_added` event will be created // for the groups which the contact has been added to. // // ``` // { // "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9", -// "type": "add_to_group", +// "type": "add_contact_groups", // "groups": [{ // "uuid": "2aad21f6-30b7-42c5-bd7f-1b720c154817", // "name": "Survey Audience" @@ -24,23 +24,23 @@ const TypeAddToGroup string = "add_to_group" // } // ``` // -// @action add_to_group -type AddToGroupAction struct { +// @action add_contact_groups +type AddContactGroupsAction struct { BaseAction Groups []*flows.GroupReference `json:"groups" validate:"required,min=1,dive"` } // Type returns the type of this action -func (a *AddToGroupAction) Type() string { return TypeAddToGroup } +func (a *AddContactGroupsAction) Type() string { return TypeAddContactGroups } // Validate validates our action is valid and has all the assets it needs -func (a *AddToGroupAction) Validate(assets flows.SessionAssets) error { +func (a *AddContactGroupsAction) Validate(assets flows.SessionAssets) error { // check we have all groups return a.validateGroups(assets, a.Groups) } // Execute adds our contact to the specified groups -func (a *AddToGroupAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { +func (a *AddContactGroupsAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { contact := run.Contact() if contact == nil { log.Add(events.NewFatalErrorEvent(fmt.Errorf("can't execute action in session without a contact"))) diff --git a/flows/actions/add_label.go b/flows/actions/add_input_labels.go similarity index 68% rename from flows/actions/add_label.go rename to flows/actions/add_input_labels.go index adb37a949..13fc769f6 100644 --- a/flows/actions/add_label.go +++ b/flows/actions/add_input_labels.go @@ -5,17 +5,17 @@ import ( "github.com/nyaruka/goflow/flows/events" ) -// TypeAddLabel is our type for add label actions -const TypeAddLabel string = "add_label" +// TypeAddInputLabels is our type for add label actions +const TypeAddInputLabels string = "add_input_labels" -// AddLabelAction can be used to add a label to the last user input on a flow. An `input_labels_added` event +// AddInputLabelsAction can be used to add labels to the last user input on a flow. An `input_labels_added` event // will be created with the labels added when this action is encountered. If there is // no user input at that point then this action will be ignored. // // ``` // { // "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9", -// "type": "add_label", +// "type": "add_input_labels", // "labels": [{ // "uuid": "b7cf0d83-f1c9-411c-96fd-c511a4cfa86d", // "name": "complaint" @@ -23,23 +23,23 @@ const TypeAddLabel string = "add_label" // } // ``` // -// @action add_label -type AddLabelAction struct { +// @action add_input_labels +type AddInputLabelsAction struct { BaseAction Labels []*flows.LabelReference `json:"labels" validate:"required,min=1,dive"` } // Type returns the type of this action -func (a *AddLabelAction) Type() string { return TypeAddLabel } +func (a *AddInputLabelsAction) Type() string { return TypeAddInputLabels } // Validate validates our action is valid and has all the assets it needs -func (a *AddLabelAction) Validate(assets flows.SessionAssets) error { +func (a *AddInputLabelsAction) Validate(assets flows.SessionAssets) error { // check we have all labels return a.validateLabels(assets, a.Labels) } // Execute runs the labeling action -func (a *AddLabelAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { +func (a *AddInputLabelsAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { // only generate event if run has input input := run.Input() if input == nil { diff --git a/flows/actions/envelope.go b/flows/actions/envelope.go index 28ec9248a..c3669d419 100644 --- a/flows/actions/envelope.go +++ b/flows/actions/envelope.go @@ -11,10 +11,10 @@ func ActionFromEnvelope(envelope *utils.TypedEnvelope) (flows.Action, error) { var action flows.Action switch envelope.Type { - case TypeAddLabel: - action = &AddLabelAction{} - case TypeAddToGroup: - action = &AddToGroupAction{} + case TypeAddInputLabels: + action = &AddInputLabelsAction{} + case TypeAddContactGroups: + action = &AddContactGroupsAction{} case TypeAddURN: action = &AddURNAction{} case TypeSendEmail: diff --git a/flows/actions/remove_from_group.go b/flows/actions/remove_contact_groups.go similarity index 94% rename from flows/actions/remove_from_group.go rename to flows/actions/remove_contact_groups.go index b66b0bd77..8c1083b82 100644 --- a/flows/actions/remove_from_group.go +++ b/flows/actions/remove_contact_groups.go @@ -8,7 +8,7 @@ import ( ) // TypeRemoveFromGroup is our type for our remove from group action -const TypeRemoveFromGroup string = "remove_from_group" +const TypeRemoveFromGroup string = "remove_contact_groups" // RemoveFromGroupAction can be used to remove a contact from one or more groups. A `contact_groups_removed` event will be created // for the groups which the contact is removed from. If no groups are specified, then the contact will be removed from @@ -17,7 +17,7 @@ const TypeRemoveFromGroup string = "remove_from_group" // ``` // { // "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9", -// "type": "remove_from_group", +// "type": "remove_contact_groups", // "groups": [{ // "uuid": "b7cf0d83-f1c9-411c-96fd-c511a4cfa86d", // "name": "Registered Users" @@ -25,7 +25,7 @@ const TypeRemoveFromGroup string = "remove_from_group" // } // ``` // -// @action remove_from_group +// @action remove_contact_groups type RemoveFromGroupAction struct { BaseAction Groups []*flows.GroupReference `json:"groups" validate:"required,min=1,dive"` diff --git a/flows/definition/legacy.go b/flows/definition/legacy.go index 6a0e14e31..1fefbb5fa 100644 --- a/flows/definition/legacy.go +++ b/flows/definition/legacy.go @@ -379,7 +379,7 @@ func migrateAction(baseLanguage utils.Language, a legacyAction, translations *fl labels[i] = label.Migrate() } - return &actions.AddLabelAction{ + return &actions.AddInputLabelsAction{ Labels: labels, BaseAction: actions.NewBaseAction(a.UUID), }, nil @@ -526,7 +526,7 @@ func migrateAction(baseLanguage utils.Language, a legacyAction, translations *fl groups[i] = group.Migrate() } - return &actions.AddToGroupAction{ + return &actions.AddContactGroupsAction{ Groups: groups, BaseAction: actions.NewBaseAction(a.UUID), }, nil diff --git a/flows/definition/testdata/migrations/actions.json b/flows/definition/testdata/migrations/actions.json index 7dab01d61..9ceedebc1 100644 --- a/flows/definition/testdata/migrations/actions.json +++ b/flows/definition/testdata/migrations/actions.json @@ -9,7 +9,7 @@ ] }, "expected_action": { - "type": "add_to_group", + "type": "add_contact_groups", "uuid": "5a4d00aa-807e-44af-9693-64b9fdedd352", "groups": [ {"uuid": "3eca24b7-6313-4f30-870f-f7b948e54b1e", "name": "Testers"}, @@ -28,7 +28,7 @@ ] }, "expected_action": { - "type": "add_label", + "type": "add_input_labels", "uuid": "5a4d00aa-807e-44af-9693-64b9fdedd352", "labels": [ {"uuid": "3eca24b7-6313-4f30-870f-f7b948e54b1e", "name": "Spam"}, @@ -82,7 +82,7 @@ ] }, "expected_action": { - "type": "remove_from_group", + "type": "remove_contact_groups", "uuid": "5a4d00aa-807e-44af-9693-64b9fdedd352", "groups": [ {"uuid": "3eca24b7-6313-4f30-870f-f7b948e54b1e", "name": "Testers"}, diff --git a/flows/engine/assets_test.go b/flows/engine/assets_test.go index a0de5eed1..c9f826f53 100644 --- a/flows/engine/assets_test.go +++ b/flows/engine/assets_test.go @@ -98,24 +98,24 @@ func TestFlowValidation(t *testing.T) { flow, err := session.Assets().GetFlow("76f0a02f-3b75-4b86-9064-e9195e1b3a02") assert.NoError(t, err) - // break the add_label action so references an invalid label - addLabelAction := flow.Nodes()[0].Actions()[0].(*actions.AddLabelAction) + // break the add_input_labels action so references an invalid label + addLabelAction := flow.Nodes()[0].Actions()[0].(*actions.AddInputLabelsAction) addLabelAction.Labels[0].UUID = "xyx" // check that validation fails err = flow.Validate(session.Assets()) - assert.EqualError(t, err, "validation failed for action[uuid=ad154980-7bf7-4ab8-8728-545fd6378912, type=add_label]: no such label with uuid 'xyx'") + assert.EqualError(t, err, "validation failed for action[uuid=ad154980-7bf7-4ab8-8728-545fd6378912, type=add_input_labels]: no such label with uuid 'xyx'") - // fix the add_label action + // fix the add_input_labels action addLabelAction.Labels[0].UUID = "3f65d88a-95dc-4140-9451-943e94e06fea" // break the add_group action so references an invalid group - addGroupAction := flow.Nodes()[0].Actions()[1].(*actions.AddToGroupAction) + addGroupAction := flow.Nodes()[0].Actions()[1].(*actions.AddContactGroupsAction) addGroupAction.Groups[0].UUID = "xyx" // check that validation fails err = flow.Validate(session.Assets()) - assert.EqualError(t, err, "validation failed for action[uuid=ad154980-7bf7-4ab8-8728-545fd6378912, type=add_to_group]: no such group with uuid 'xyx'") + assert.EqualError(t, err, "validation failed for action[uuid=ad154980-7bf7-4ab8-8728-545fd6378912, type=add_contact_groups]: no such group with uuid 'xyx'") // fix the add_group action addGroupAction.Groups[0].UUID = "2aad21f6-30b7-42c5-bd7f-1b720c154817" diff --git a/flows/engine/testdata/assets.json b/flows/engine/testdata/assets.json index 7136407c3..7b0e077ac 100644 --- a/flows/engine/testdata/assets.json +++ b/flows/engine/testdata/assets.json @@ -12,7 +12,7 @@ "actions": [ { "uuid": "ad154980-7bf7-4ab8-8728-545fd6378912", - "type": "add_label", + "type": "add_input_labels", "labels": [ { "uuid": "3f65d88a-95dc-4140-9451-943e94e06fea", @@ -22,7 +22,7 @@ }, { "uuid": "ad154980-7bf7-4ab8-8728-545fd6378912", - "type": "add_to_group", + "type": "add_contact_groups", "groups": [ { "uuid": "2aad21f6-30b7-42c5-bd7f-1b720c154817", From d9585e5baa530a1ff8ba062a903cf6b907d7c378 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 12 Mar 2018 16:11:45 -0500 Subject: [PATCH 2/3] Rename contact actions --- .../testdata/flows/all_actions.json | 8 ++++---- cmd/flowrunner/testdata/flows/brochure.json | 2 +- cmd/flowrunner/testdata/flows/date_parse.json | 2 +- .../testdata/flows/default_result.json | 4 ++-- .../testdata/flows/dynamic_groups.json | 8 ++++---- cmd/flowrunner/testdata/flows/no_contact.json | 2 +- .../testdata/flows/two_questions.json | 2 +- .../{add_urn.go => add_contact_urn.go} | 18 ++++++++--------- flows/actions/envelope.go | 20 +++++++++---------- flows/actions/remove_contact_groups.go | 14 ++++++------- ...rred_channel.go => set_contact_channel.go} | 10 +++++----- ..._contact_field.go => set_contact_field.go} | 18 ++++++++--------- ...ate_contact.go => set_contact_property.go} | 18 ++++++++--------- flows/definition/legacy.go | 14 ++++++------- .../testdata/migrations/actions.json | 10 +++++----- flows/engine/assets_test.go | 16 +++++++-------- flows/engine/testdata/assets.json | 4 ++-- 17 files changed, 85 insertions(+), 85 deletions(-) rename flows/actions/{add_urn.go => add_contact_urn.go} (73%) rename flows/actions/{set_preferred_channel.go => set_contact_channel.go} (61%) rename flows/actions/{save_contact_field.go => set_contact_field.go} (69%) rename flows/actions/{update_contact.go => set_contact_property.go} (70%) diff --git a/cmd/flowrunner/testdata/flows/all_actions.json b/cmd/flowrunner/testdata/flows/all_actions.json index 76323cfb2..5260e1c85 100644 --- a/cmd/flowrunner/testdata/flows/all_actions.json +++ b/cmd/flowrunner/testdata/flows/all_actions.json @@ -38,13 +38,13 @@ }, { "uuid": "ca5138c1-2a26-44c6-a29c-6ef695bc67ee", - "type": "add_urn", + "type": "add_contact_urn", "scheme": "twitter", "path": "@(replace(lower(contact.name), \" \", \"_\"))" }, { "uuid": "7bd8b3bf-0a3c-4928-bc46-df416e77ddf4", - "type": "save_contact_field", + "type": "set_contact_field", "field": { "key": "activation_token", "label": "Activation Token" @@ -147,13 +147,13 @@ }, { "uuid": "f3581032-e122-45ee-8be7-4f3c955d97f8", - "type": "update_contact", + "type": "set_contact_property", "field_name": "language", "value": "eng" }, { "uuid": "7bd8b3bf-0a3c-4928-bc46-df416e77ddf4", - "type": "save_contact_field", + "type": "set_contact_field", "field": { "key": "gender", "label": "Gender" diff --git a/cmd/flowrunner/testdata/flows/brochure.json b/cmd/flowrunner/testdata/flows/brochure.json index 1751340e4..5492890d4 100644 --- a/cmd/flowrunner/testdata/flows/brochure.json +++ b/cmd/flowrunner/testdata/flows/brochure.json @@ -66,7 +66,7 @@ "actions": [ { "uuid": "455ba297-f6d2-45e6-bf3e-c1ef028b55ae", - "type": "update_contact", + "type": "set_contact_property", "field_name": "name", "value": "@run.input.text" }, diff --git a/cmd/flowrunner/testdata/flows/date_parse.json b/cmd/flowrunner/testdata/flows/date_parse.json index e940db196..b640d614f 100644 --- a/cmd/flowrunner/testdata/flows/date_parse.json +++ b/cmd/flowrunner/testdata/flows/date_parse.json @@ -51,7 +51,7 @@ "actions": [ { "uuid": "afd5ac22-2a86-4576-a2c7-715f0bb10194", - "type": "save_contact_field", + "type": "set_contact_field", "field": { "key": "birth_date", "label": "Birth Date" diff --git a/cmd/flowrunner/testdata/flows/default_result.json b/cmd/flowrunner/testdata/flows/default_result.json index cab66de42..e1bc14deb 100644 --- a/cmd/flowrunner/testdata/flows/default_result.json +++ b/cmd/flowrunner/testdata/flows/default_result.json @@ -46,13 +46,13 @@ "uuid": "2929d2fc-2778-4d98-a4bc-73a7345710b0", "actions": [ { - "type": "update_contact", + "type": "set_contact_property", "uuid": "aafb505c-603d-4025-864d-471345ed236d", "field_name": "name", "value": "@run.results.contact_name" }, { - "type": "save_contact_field", + "type": "set_contact_field", "uuid": "aafb505c-603d-4025-864d-471345ed237d", "field": { "key": "first_name", diff --git a/cmd/flowrunner/testdata/flows/dynamic_groups.json b/cmd/flowrunner/testdata/flows/dynamic_groups.json index 4cc1bdb56..34b9f6002 100644 --- a/cmd/flowrunner/testdata/flows/dynamic_groups.json +++ b/cmd/flowrunner/testdata/flows/dynamic_groups.json @@ -12,7 +12,7 @@ "actions": [ { "uuid": "7bd8b3bf-0a3c-4928-bc46-df416e77ddf4", - "type": "save_contact_field", + "type": "set_contact_field", "field": { "key": "gender", "label": "Gender" @@ -21,7 +21,7 @@ }, { "uuid": "ee4cd27e-1296-40fd-ac9c-8cd43e9fb8b5", - "type": "save_contact_field", + "type": "set_contact_field", "field": { "key": "age", "label": "Age" @@ -35,7 +35,7 @@ }, { "uuid": "bc94b3ba-4aad-410a-ae71-1861f69da0fe", - "type": "add_urn", + "type": "add_contact_urn", "scheme": "tel", "path": "+250781234567" }, @@ -46,7 +46,7 @@ }, { "uuid": "4ecf6abd-e8c8-424b-aef6-e904cf5b4fbe", - "type": "save_contact_field", + "type": "set_contact_field", "field": { "key": "age", "label": "Age" diff --git a/cmd/flowrunner/testdata/flows/no_contact.json b/cmd/flowrunner/testdata/flows/no_contact.json index d5d78a34b..68e76f6fc 100644 --- a/cmd/flowrunner/testdata/flows/no_contact.json +++ b/cmd/flowrunner/testdata/flows/no_contact.json @@ -35,7 +35,7 @@ }, { "uuid": "ca5138c1-2a26-44c6-a29c-6ef695bc67ee", - "type": "add_urn", + "type": "add_contact_urn", "scheme": "twitter", "path": "@(replace(lower(contact.name), \" \", \"_\"))" } diff --git a/cmd/flowrunner/testdata/flows/two_questions.json b/cmd/flowrunner/testdata/flows/two_questions.json index 884a891df..09bdd56bb 100644 --- a/cmd/flowrunner/testdata/flows/two_questions.json +++ b/cmd/flowrunner/testdata/flows/two_questions.json @@ -136,7 +136,7 @@ "actions": [ { "uuid": "afd5ac22-2a86-4576-a2c7-715f0bb10194", - "type": "update_contact", + "type": "set_contact_property", "field_name": "language", "value": "fra" }, diff --git a/flows/actions/add_urn.go b/flows/actions/add_contact_urn.go similarity index 73% rename from flows/actions/add_urn.go rename to flows/actions/add_contact_urn.go index 4269e637c..6f3b80f0f 100644 --- a/flows/actions/add_urn.go +++ b/flows/actions/add_contact_urn.go @@ -9,39 +9,39 @@ import ( "github.com/nyaruka/goflow/flows/events" ) -// TypeAddURN is our type for add URN actions -const TypeAddURN string = "add_urn" +// TypeAddContactURN is our type for add URN actions +const TypeAddContactURN string = "add_contact_urn" -// AddURNAction can be used to add a URN to the current contact. An `contact_urn_added` event +// AddContactURNAction can be used to add a URN to the current contact. An `contact_urn_added` event // will be created when this action is encountered. If there is no contact then this // action will be ignored. // // ``` // { // "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9", -// "type": "add_urn", +// "type": "add_contact_urn", // "scheme": "tel", // "path": "@flow.phone_number" // } // ``` // -// @action add_urn -type AddURNAction struct { +// @action add_contact_urn +type AddContactURNAction struct { BaseAction Scheme string `json:"scheme" validate:"urnscheme"` Path string `json:"path" validate:"required"` } // Type returns the type of this action -func (a *AddURNAction) Type() string { return TypeAddURN } +func (a *AddContactURNAction) Type() string { return TypeAddContactURN } // Validate validates our action is valid and has all the assets it needs -func (a *AddURNAction) Validate(assets flows.SessionAssets) error { +func (a *AddContactURNAction) Validate(assets flows.SessionAssets) error { return nil } // Execute runs the labeling action -func (a *AddURNAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { +func (a *AddContactURNAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { // only generate event if run has a contact contact := run.Contact() if contact == nil { diff --git a/flows/actions/envelope.go b/flows/actions/envelope.go index c3669d419..64c775352 100644 --- a/flows/actions/envelope.go +++ b/flows/actions/envelope.go @@ -15,8 +15,8 @@ func ActionFromEnvelope(envelope *utils.TypedEnvelope) (flows.Action, error) { action = &AddInputLabelsAction{} case TypeAddContactGroups: action = &AddContactGroupsAction{} - case TypeAddURN: - action = &AddURNAction{} + case TypeAddContactURN: + action = &AddContactURNAction{} case TypeSendEmail: action = &EmailAction{} case TypeStartFlow: @@ -25,18 +25,18 @@ func ActionFromEnvelope(envelope *utils.TypedEnvelope) (flows.Action, error) { action = &StartSessionAction{} case TypeSendMsg: action = &SendMsgAction{} - case TypeRemoveFromGroup: - action = &RemoveFromGroupAction{} + case TypeRemoveContactGroups: + action = &RemoveContactGroupsAction{} case TypeReply: action = &ReplyAction{} case TypeSaveFlowResult: action = &SaveFlowResultAction{} - case TypeSaveContactField: - action = &SaveContactField{} - case TypeSetPreferredChannel: - action = &PreferredChannelAction{} - case TypeUpdateContact: - action = &UpdateContactAction{} + case TypeSetContactField: + action = &SetContactFieldAction{} + case TypeSetContactChannel: + action = &SetContactChannelAction{} + case TypeSetContactProperty: + action = &SetContactPropertyAction{} case TypeCallWebhook: action = &WebhookAction{} default: diff --git a/flows/actions/remove_contact_groups.go b/flows/actions/remove_contact_groups.go index 8c1083b82..89940e475 100644 --- a/flows/actions/remove_contact_groups.go +++ b/flows/actions/remove_contact_groups.go @@ -7,10 +7,10 @@ import ( "github.com/nyaruka/goflow/flows/events" ) -// TypeRemoveFromGroup is our type for our remove from group action -const TypeRemoveFromGroup string = "remove_contact_groups" +// TypeRemoveContactGroups is our type for our remove from group action +const TypeRemoveContactGroups string = "remove_contact_groups" -// RemoveFromGroupAction can be used to remove a contact from one or more groups. A `contact_groups_removed` event will be created +// RemoveContactGroupsAction can be used to remove a contact from one or more groups. A `contact_groups_removed` event will be created // for the groups which the contact is removed from. If no groups are specified, then the contact will be removed from // all groups. // @@ -26,22 +26,22 @@ const TypeRemoveFromGroup string = "remove_contact_groups" // ``` // // @action remove_contact_groups -type RemoveFromGroupAction struct { +type RemoveContactGroupsAction struct { BaseAction Groups []*flows.GroupReference `json:"groups" validate:"required,min=1,dive"` } // Type returns the type of this action -func (a *RemoveFromGroupAction) Type() string { return TypeRemoveFromGroup } +func (a *RemoveContactGroupsAction) Type() string { return TypeRemoveContactGroups } // Validate validates our action is valid and has all the assets it needs -func (a *RemoveFromGroupAction) Validate(assets flows.SessionAssets) error { +func (a *RemoveContactGroupsAction) Validate(assets flows.SessionAssets) error { // check we have all groups return a.validateGroups(assets, a.Groups) } // Execute runs the action -func (a *RemoveFromGroupAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { +func (a *RemoveContactGroupsAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { contact := run.Contact() if contact == nil { log.Add(events.NewFatalErrorEvent(fmt.Errorf("can't execute action in session without a contact"))) diff --git a/flows/actions/set_preferred_channel.go b/flows/actions/set_contact_channel.go similarity index 61% rename from flows/actions/set_preferred_channel.go rename to flows/actions/set_contact_channel.go index cc86e5a1e..1d8324a0d 100644 --- a/flows/actions/set_preferred_channel.go +++ b/flows/actions/set_contact_channel.go @@ -7,23 +7,23 @@ import ( "github.com/nyaruka/goflow/flows/events" ) -const TypeSetPreferredChannel string = "set_preferred_channel" +const TypeSetContactChannel string = "set_contact_channel" -type PreferredChannelAction struct { +type SetContactChannelAction struct { BaseAction Channel *flows.ChannelReference `json:"channel"` } // Type returns the type of this action -func (a *PreferredChannelAction) Type() string { return TypeSetPreferredChannel } +func (a *SetContactChannelAction) Type() string { return TypeSetContactChannel } // Validate validates our action is valid and has all the assets it needs -func (a *PreferredChannelAction) Validate(assets flows.SessionAssets) error { +func (a *SetContactChannelAction) Validate(assets flows.SessionAssets) error { _, err := assets.GetChannel(a.Channel.UUID) return err } -func (a *PreferredChannelAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { +func (a *SetContactChannelAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { if run.Contact() == nil { log.Add(events.NewFatalErrorEvent(fmt.Errorf("can't execute action in session without a contact"))) return nil diff --git a/flows/actions/save_contact_field.go b/flows/actions/set_contact_field.go similarity index 69% rename from flows/actions/save_contact_field.go rename to flows/actions/set_contact_field.go index b9d25bda1..4bb106481 100644 --- a/flows/actions/save_contact_field.go +++ b/flows/actions/set_contact_field.go @@ -9,39 +9,39 @@ import ( "github.com/nyaruka/goflow/utils" ) -// TypeSaveContactField is the type for our save to contact action -const TypeSaveContactField string = "save_contact_field" +// TypeSetContactField is the type for our set contact field action +const TypeSetContactField string = "set_contact_field" -// SaveContactField can be used to save a value to a contact. The value can be a template and will +// SetContactFieldAction can be used to save a value to a contact. The value can be a template and will // be evaluated during the flow. A `contact_field_changed` event will be created with the corresponding value. // // ``` // { // "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9", -// "type": "save_contact_field", +// "type": "set_contact_field", // "field": {"key": "gender", "label": "Gender"}, // "value": "Male" // } // ``` // -// @action save_contact_field -type SaveContactField struct { +// @action set_contact_field +type SetContactFieldAction struct { BaseAction Field *flows.FieldReference `json:"field" validate:"required"` Value string `json:"value"` } // Type returns the type of this action -func (a *SaveContactField) Type() string { return TypeSaveContactField } +func (a *SetContactFieldAction) Type() string { return TypeSetContactField } // Validate validates our action is valid and has all the assets it needs -func (a *SaveContactField) Validate(assets flows.SessionAssets) error { +func (a *SetContactFieldAction) Validate(assets flows.SessionAssets) error { _, err := assets.GetField(a.Field.Key) return err } // Execute runs this action -func (a *SaveContactField) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { +func (a *SetContactFieldAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { if run.Contact() == nil { log.Add(events.NewFatalErrorEvent(fmt.Errorf("can't execute action in session without a contact"))) return nil diff --git a/flows/actions/update_contact.go b/flows/actions/set_contact_property.go similarity index 70% rename from flows/actions/update_contact.go rename to flows/actions/set_contact_property.go index 134c45336..670099196 100644 --- a/flows/actions/update_contact.go +++ b/flows/actions/set_contact_property.go @@ -10,33 +10,33 @@ import ( "github.com/nyaruka/goflow/utils" ) -// TypeUpdateContact is the type for our update contact action -const TypeUpdateContact string = "update_contact" +// TypeSetContactProperty is the type for our set contact property action +const TypeSetContactProperty string = "set_contact_property" -// UpdateContactAction can be used to update one of the built in fields for a contact of "name" or +// SetContactPropertyAction can be used to update one of the built in fields for a contact of "name" or // "language". An `contact_property_changed` event will be created with the corresponding values. // // ``` // { // "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9", -// "type": "update_contact", +// "type": "set_contact_property", // "field_name": "language", // "value": "eng" // } // ``` // -// @action update_contact -type UpdateContactAction struct { +// @action set_contact_property +type SetContactPropertyAction struct { BaseAction FieldName string `json:"field_name" validate:"required,eq=name|eq=language"` Value string `json:"value"` } // Type returns the type of this action -func (a *UpdateContactAction) Type() string { return TypeUpdateContact } +func (a *SetContactPropertyAction) Type() string { return TypeSetContactProperty } // Validate validates our action is valid and has all the assets it needs -func (a *UpdateContactAction) Validate(assets flows.SessionAssets) error { +func (a *SetContactPropertyAction) Validate(assets flows.SessionAssets) error { // check language is valid if specified if a.FieldName == "language" && a.Value != "" { if _, err := utils.ParseLanguage(a.Value); err != nil { @@ -47,7 +47,7 @@ func (a *UpdateContactAction) Validate(assets flows.SessionAssets) error { } // Execute runs this action -func (a *UpdateContactAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { +func (a *SetContactPropertyAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { if run.Contact() == nil { log.Add(events.NewFatalErrorEvent(fmt.Errorf("can't execute action in session without a contact"))) return nil diff --git a/flows/definition/legacy.go b/flows/definition/legacy.go index 1fefbb5fa..93115694e 100644 --- a/flows/definition/legacy.go +++ b/flows/definition/legacy.go @@ -406,13 +406,13 @@ func migrateAction(baseLanguage utils.Language, a legacyAction, translations *fl }, nil case "lang": - return &actions.UpdateContactAction{ + return &actions.SetContactPropertyAction{ FieldName: "language", Value: string(a.Language), BaseAction: actions.NewBaseAction(a.UUID), }, nil case "channel": - return &actions.PreferredChannelAction{ + return &actions.SetContactChannelAction{ Channel: flows.NewChannelReference(a.Channel, a.Name), BaseAction: actions.NewBaseAction(a.UUID), }, nil @@ -536,7 +536,7 @@ func migrateAction(baseLanguage utils.Language, a legacyAction, translations *fl groups[i] = group.Migrate() } - return &actions.RemoveFromGroupAction{ + return &actions.RemoveContactGroupsAction{ Groups: groups, BaseAction: actions.NewBaseAction(a.UUID), }, nil @@ -551,7 +551,7 @@ func migrateAction(baseLanguage utils.Language, a legacyAction, translations *fl migratedValue = fmt.Sprintf("%s @(word_slice(contact.name, 2, -1))", migratedValue) } - return &actions.UpdateContactAction{ + return &actions.SetContactPropertyAction{ FieldName: "name", Value: migratedValue, BaseAction: actions.NewBaseAction(a.UUID), @@ -560,20 +560,20 @@ func migrateAction(baseLanguage utils.Language, a legacyAction, translations *fl // and another new action for adding a URN if urns.IsValidScheme(a.Field) { - return &actions.AddURNAction{ + return &actions.AddContactURNAction{ Scheme: a.Field, Path: migratedValue, BaseAction: actions.NewBaseAction(a.UUID), }, nil } else if a.Field == "tel_e164" { - return &actions.AddURNAction{ + return &actions.AddContactURNAction{ Scheme: "tel", Path: migratedValue, BaseAction: actions.NewBaseAction(a.UUID), }, nil } - return &actions.SaveContactField{ + return &actions.SetContactFieldAction{ Field: flows.NewFieldReference(flows.FieldKey(a.Field), a.Label), Value: migratedValue, BaseAction: actions.NewBaseAction(a.UUID), diff --git a/flows/definition/testdata/migrations/actions.json b/flows/definition/testdata/migrations/actions.json index 9ceedebc1..0a5cdae07 100644 --- a/flows/definition/testdata/migrations/actions.json +++ b/flows/definition/testdata/migrations/actions.json @@ -66,7 +66,7 @@ "name": "Twilio" }, "expected_action": { - "type": "set_preferred_channel", + "type": "set_contact_channel", "uuid": "5a4d00aa-807e-44af-9693-64b9fdedd352", "channel": {"uuid": "3eca24b7-6313-4f30-870f-f7b948e54b1e", "name": "Twilio"} }, @@ -116,7 +116,7 @@ "name": "French" }, "expected_action": { - "type": "update_contact", + "type": "set_contact_property", "uuid": "5a4d00aa-807e-44af-9693-64b9fdedd352", "field_name": "language", "value": "fra" @@ -187,7 +187,7 @@ "value": "Bob @step.value" }, "expected_action": { - "type": "update_contact", + "type": "set_contact_property", "uuid": "5a4d00aa-807e-44af-9693-64b9fdedd352", "field_name": "name", "value": "Bob @run.input" @@ -203,7 +203,7 @@ "value": "@step.value" }, "expected_action": { - "type": "update_contact", + "type": "set_contact_property", "uuid": "5a4d00aa-807e-44af-9693-64b9fdedd352", "field_name": "name", "value": "@run.input @(word_slice(contact.name, 2, -1))" @@ -219,7 +219,7 @@ "value": "@step.value" }, "expected_action": { - "type": "save_contact_field", + "type": "set_contact_field", "uuid": "5a4d00aa-807e-44af-9693-64b9fdedd352", "field": {"key": "gender", "label": "Gender"}, "value": "@run.input" diff --git a/flows/engine/assets_test.go b/flows/engine/assets_test.go index c9f826f53..76eb3a88f 100644 --- a/flows/engine/assets_test.go +++ b/flows/engine/assets_test.go @@ -120,19 +120,19 @@ func TestFlowValidation(t *testing.T) { // fix the add_group action addGroupAction.Groups[0].UUID = "2aad21f6-30b7-42c5-bd7f-1b720c154817" - // break the save_contact_field action so references an invalid field - saveContactAction := flow.Nodes()[0].Actions()[2].(*actions.SaveContactField) + // break the set_contact_field action so references an invalid field + saveContactAction := flow.Nodes()[0].Actions()[2].(*actions.SetContactFieldAction) saveContactAction.Field.Key = "xyx" // check that validation fails err = flow.Validate(session.Assets()) - assert.EqualError(t, err, "validation failed for action[uuid=7bd8b3bf-0a3c-4928-bc46-df416e77ddf4, type=save_contact_field]: no such field with key 'xyx'") + assert.EqualError(t, err, "validation failed for action[uuid=7bd8b3bf-0a3c-4928-bc46-df416e77ddf4, type=set_contact_field]: no such field with key 'xyx'") - // fix the save_contact_field action + // fix the set_contact_field action saveContactAction.Field.Key = "first_name" - // break the set_preferred_channel action so references an invalid channel - prefChannelAction := flow.Nodes()[0].Actions()[3].(*actions.PreferredChannelAction) + // break the set_contact_channel action so references an invalid channel + prefChannelAction := flow.Nodes()[0].Actions()[3].(*actions.SetContactChannelAction) prefChannelAction.Channel.UUID = "xyx" // can't simulate a missing channel without the asset store trying to fetch it... but we can stuff the wrong thing in the store @@ -140,8 +140,8 @@ func TestFlowValidation(t *testing.T) { // check that validation fails err = flow.Validate(session.Assets()) - assert.EqualError(t, err, "validation failed for action[uuid=3248a064-bc42-4dff-aa0f-93d85de2f600, type=set_preferred_channel]: asset cache contains asset with wrong type") + assert.EqualError(t, err, "validation failed for action[uuid=3248a064-bc42-4dff-aa0f-93d85de2f600, type=set_contact_channel]: asset cache contains asset with wrong type") - // fix the set_preferred_channel action + // fix the set_contact_channel action prefChannelAction.Channel.UUID = "57f1078f-88aa-46f4-a59a-948a5739c03d" } diff --git a/flows/engine/testdata/assets.json b/flows/engine/testdata/assets.json index 7b0e077ac..e49b8f734 100644 --- a/flows/engine/testdata/assets.json +++ b/flows/engine/testdata/assets.json @@ -32,7 +32,7 @@ }, { "uuid": "7bd8b3bf-0a3c-4928-bc46-df416e77ddf4", - "type": "save_contact_field", + "type": "set_contact_field", "field": { "key": "first_name", "label": "First Name" @@ -41,7 +41,7 @@ }, { "uuid": "3248a064-bc42-4dff-aa0f-93d85de2f600", - "type": "set_preferred_channel", + "type": "set_contact_channel", "channel": { "uuid": "57f1078f-88aa-46f4-a59a-948a5739c03d", "name": "Twilio Channel" From 865e1133400959a61c6b83d3f269faa0f8f5f8b8 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 12 Mar 2018 16:25:06 -0500 Subject: [PATCH 3/3] Rename remaining actions --- cmd/docgen/templates/docs.md | 6 +- .../testdata/flows/all_actions.json | 12 +-- cmd/flowrunner/testdata/flows/brochure.json | 4 +- cmd/flowrunner/testdata/flows/date_parse.json | 4 +- .../testdata/flows/default_result.json | 4 +- .../testdata/flows/dynamic_groups.json | 6 +- cmd/flowrunner/testdata/flows/node_loop.json | 2 +- cmd/flowrunner/testdata/flows/subflow.json | 10 +-- .../testdata/flows/subflow_loop.json | 6 +- .../testdata/flows/subflow_other.json | 16 ++-- cmd/flowrunner/testdata/flows/triggered.json | 2 +- .../testdata/flows/two_questions.json | 6 +- .../testdata/flows/webhook_persists.json | 6 +- cmd/flowserver/static/start.json | 2 +- flows/actions/add_contact_groups.go | 2 +- flows/actions/add_contact_urn.go | 2 +- flows/actions/add_input_labels.go | 2 +- flows/actions/call_webhook.go | 12 +-- flows/actions/envelope.go | 30 +++---- flows/actions/remove_contact_groups.go | 2 +- flows/actions/reply.go | 84 ------------------- flows/actions/send_broadcast.go | 59 +++++++++++++ flows/actions/send_email.go | 12 +-- flows/actions/send_msg.go | 59 +++++++++---- flows/actions/set_contact_channel.go | 1 + flows/actions/set_contact_field.go | 2 +- flows/actions/set_contact_property.go | 2 +- ...{save_flow_result.go => set_run_result.go} | 18 ++-- flows/definition/legacy.go | 10 +-- .../testdata/migrations/actions.json | 6 +- 30 files changed, 195 insertions(+), 194 deletions(-) delete mode 100644 flows/actions/reply.go create mode 100644 flows/actions/send_broadcast.go rename flows/actions/{save_flow_result.go => set_run_result.go} (72%) diff --git a/cmd/docgen/templates/docs.md b/cmd/docgen/templates/docs.md index 415612b02..7809babca 100644 --- a/cmd/docgen/templates/docs.md +++ b/cmd/docgen/templates/docs.md @@ -34,7 +34,7 @@ At its simplest, a node can be just a single action with no exits, wait or route "uuid":"5a06445e-d790-4bd3-a10b-b47bdcc9abed", "actions":[{ "uuid": "abc0a2bf-6b4a-4ee0-83e1-1eebae6948ac", - "type": "reply", + "type": "send_msg", "text": "What is your name?" }] } @@ -53,7 +53,7 @@ An exit consists of: "uuid":"5a06445e-d790-4bd3-a10b-b47bdcc9abed", "actions":[{ "uuid": "abc0a2bf-6b4a-4ee0-83e1-1eebae6948ac", - "type": "reply", + "type": "send_msg", "text": "What is your name?" }], "exits": [{ @@ -140,7 +140,7 @@ have been started by a `start_flow` action previously in the flow: Flows do not describe data flow but rather actions and logic branching. As such, variables collected in a flow and the state of the flow is accessed through what is called the Context. The context contains variables representing the current contact in a flow, the last input from that contact as well as the results collected in a flow and any webhook requests made during the flow. Variables in the context may be referred to -within actions by using the `@` symbol. For example, to greet a contact by their name in a reply action, the text of the reply can be `Hi @contact.name!`. +within actions by using the `@` symbol. For example, to greet a contact by their name in a send_msg action, the text of the send_msg can be `Hi @contact.name!`. The `@` symbol can be escaped in templates by repeating it, ie, `Hi @@twitter` would output `Hi @twitter`. diff --git a/cmd/flowrunner/testdata/flows/all_actions.json b/cmd/flowrunner/testdata/flows/all_actions.json index 5260e1c85..337d358bd 100644 --- a/cmd/flowrunner/testdata/flows/all_actions.json +++ b/cmd/flowrunner/testdata/flows/all_actions.json @@ -85,7 +85,7 @@ }, { "uuid": "ac110f56-a66c-4462-921c-b2c6d1c6dadb", - "type": "send_msg", + "type": "send_broadcast", "urns": [ "tel:+12065551212" ], @@ -93,7 +93,7 @@ }, { "uuid": "c0057fd9-be0a-43ea-91df-5c18e14f2c59", - "type": "send_msg", + "type": "send_broadcast", "groups": [ { "uuid": "2aad21f6-30b7-42c5-bd7f-1b720c154817", @@ -117,18 +117,18 @@ }, { "uuid": "f01d693b-2af2-49fb-9e38-146eb00937e9", - "type": "reply", + "type": "send_msg", "text": "Hi @contact.name, are you ready to complete today's survey?" }, { "uuid": "d98c1e02-69df-4f95-8b89-8587a57ae0c3", - "type": "reply", + "type": "send_msg", "text": "This is a message to each of @contact.name's urns.", "all_urns": true }, { "uuid": "62a30ab4-d73c-447d-a989-39c49115153e", - "type": "reply", + "type": "send_msg", "text": "This is a reply with attachments and quick replies", "attachments": [ "image/jpeg:http://s3.amazon.com/bucket/test_en.jpg?a=@contact.fields.state" @@ -140,7 +140,7 @@ }, { "uuid": "5508e6a7-26ce-4b3b-b32e-bb4e2e614f5d", - "type": "save_flow_result", + "type": "set_run_result", "name": "Gender", "value": "m", "category": "Male" diff --git a/cmd/flowrunner/testdata/flows/brochure.json b/cmd/flowrunner/testdata/flows/brochure.json index 5492890d4..58960767b 100644 --- a/cmd/flowrunner/testdata/flows/brochure.json +++ b/cmd/flowrunner/testdata/flows/brochure.json @@ -11,7 +11,7 @@ "uuid": "32bc60ad-5c86-465e-a6b8-049c44ecce49", "actions": [ { - "type": "reply", + "type": "send_msg", "uuid": "9d9290a7-3713-4c22-8821-4af0a64c0821", "text": "Hi! What is your name?" } @@ -82,7 +82,7 @@ }, { "uuid": "605e3486-503d-481c-94f7-cd553f196a8a", - "type": "reply", + "type": "send_msg", "text": "Great, you are @contact.name, thanks for joining!" } ] diff --git a/cmd/flowrunner/testdata/flows/date_parse.json b/cmd/flowrunner/testdata/flows/date_parse.json index b640d614f..8c24d0eb7 100644 --- a/cmd/flowrunner/testdata/flows/date_parse.json +++ b/cmd/flowrunner/testdata/flows/date_parse.json @@ -12,7 +12,7 @@ "actions": [ { "uuid": "e97cd6d5-3354-4dbd-85bc-6c1f87849eec", - "type": "reply", + "type": "send_msg", "text": "Hi @contact.name! When were you born, enter in format yyyy.MM.dd" } ], @@ -60,7 +60,7 @@ }, { "uuid": "d2a4052a-3fa9-4608-ab3e-5b9631440447", - "type": "reply", + "type": "send_msg", "text": "Awesome, you were born on @(FORMAT_DATE(run.results.birth_date, \"MM-dd-yyyy\")) at @(FORMAT_DATE(run.results.birth_date, \"HH:mm\"))" } ] diff --git a/cmd/flowrunner/testdata/flows/default_result.json b/cmd/flowrunner/testdata/flows/default_result.json index e1bc14deb..17c90fa46 100644 --- a/cmd/flowrunner/testdata/flows/default_result.json +++ b/cmd/flowrunner/testdata/flows/default_result.json @@ -17,7 +17,7 @@ "actions": [ { "text": "What is your name?", - "type": "reply", + "type": "send_msg", "uuid": "d3cd8da7-55f2-4bd3-9a0c-efc93c99e498" } ] @@ -61,7 +61,7 @@ "value": "@(WORD(run.results.contact_name, 1))" }, { - "type": "reply", + "type": "send_msg", "uuid": "aafb505c-603d-4025-864d-471345ed237d", "text": "Great, pleased to meet you @contact.fields.first_name" } diff --git a/cmd/flowrunner/testdata/flows/dynamic_groups.json b/cmd/flowrunner/testdata/flows/dynamic_groups.json index 34b9f6002..e69754f42 100644 --- a/cmd/flowrunner/testdata/flows/dynamic_groups.json +++ b/cmd/flowrunner/testdata/flows/dynamic_groups.json @@ -30,7 +30,7 @@ }, { "uuid": "f01d693b-2af2-49fb-9e38-146eb00937e9", - "type": "reply", + "type": "send_msg", "text": "Current groups: @contact.groups" }, { @@ -41,7 +41,7 @@ }, { "uuid": "5bc4894f-9ef4-430e-a040-e688fd2dd578", - "type": "reply", + "type": "send_msg", "text": "Current groups: @contact.groups" }, { @@ -55,7 +55,7 @@ }, { "uuid": "279b0215-c9d5-4a90-b7df-f371812bcc78", - "type": "reply", + "type": "send_msg", "text": "Current groups: @contact.groups" } ] diff --git a/cmd/flowrunner/testdata/flows/node_loop.json b/cmd/flowrunner/testdata/flows/node_loop.json index c5166c226..78c51d8f3 100644 --- a/cmd/flowrunner/testdata/flows/node_loop.json +++ b/cmd/flowrunner/testdata/flows/node_loop.json @@ -11,7 +11,7 @@ "uuid": "32bc60ad-5c86-465e-a6b8-049c44ecce49", "actions": [ { - "type": "reply", + "type": "send_msg", "uuid": "9d9290a7-3713-4c22-8821-4af0a64c0821", "text": "Hi! What is your name?" } diff --git a/cmd/flowrunner/testdata/flows/subflow.json b/cmd/flowrunner/testdata/flows/subflow.json index c112f284a..efe959fd4 100644 --- a/cmd/flowrunner/testdata/flows/subflow.json +++ b/cmd/flowrunner/testdata/flows/subflow.json @@ -12,7 +12,7 @@ "actions": [ { "uuid": "49f6c984-620f-4d9b-98c4-8ead1d1ef4f6", - "type": "reply", + "type": "send_msg", "text": "This is the parent flow" }, { @@ -61,7 +61,7 @@ "actions": [ { "uuid": "5d51eae6-be0f-4cc7-9402-150aa1ed80a1", - "type": "reply", + "type": "send_msg", "text": "Flow succeeded, they said @child.results.name" } ] @@ -71,7 +71,7 @@ "actions": [ { "uuid": "d80b2a5c-3b5c-47cd-b6ea-2f59bf2bb477", - "type": "reply", + "type": "send_msg", "text": "Flow failed" } ] @@ -92,7 +92,7 @@ "actions": [ { "uuid": "e5a03dde-3b2f-4603-b5d0-d927f6bcc361", - "type": "reply", + "type": "send_msg", "text": "What is your name?" } ], @@ -130,7 +130,7 @@ "actions": [ { "uuid": "d63929fe-e999-42ef-abf1-4b281f58891e", - "type": "reply", + "type": "send_msg", "text": "Got it!" } ] diff --git a/cmd/flowrunner/testdata/flows/subflow_loop.json b/cmd/flowrunner/testdata/flows/subflow_loop.json index 1409e246c..2bbfe3b09 100644 --- a/cmd/flowrunner/testdata/flows/subflow_loop.json +++ b/cmd/flowrunner/testdata/flows/subflow_loop.json @@ -12,7 +12,7 @@ "actions": [ { "uuid": "49f6c984-620f-4d9b-98c4-8ead1d1ef4f6", - "type": "reply", + "type": "send_msg", "text": "This is the parent flow" }, { @@ -36,7 +36,7 @@ "actions": [ { "uuid": "5d51eae6-be0f-4cc7-9402-150aa1ed80a1", - "type": "reply", + "type": "send_msg", "text": "Flow succeeded" } ] @@ -57,7 +57,7 @@ "actions": [ { "uuid": "e5a03dde-3b2f-4603-b5d0-d927f6bcc361", - "type": "reply", + "type": "send_msg", "text": "This is the child flow" }, { diff --git a/cmd/flowrunner/testdata/flows/subflow_other.json b/cmd/flowrunner/testdata/flows/subflow_other.json index d1cc20972..6f6020f73 100644 --- a/cmd/flowrunner/testdata/flows/subflow_other.json +++ b/cmd/flowrunner/testdata/flows/subflow_other.json @@ -17,7 +17,7 @@ "actions": [ { "text": "Hi there, let's go to the child.", - "type": "reply", + "type": "send_msg", "uuid": "ac403443-4e93-4127-8b03-469598cd7ae2" } ] @@ -69,7 +69,7 @@ "actions": [ { "text": "Hooray, you did it and said @child.results.answer. Say yes or no!", - "type": "reply", + "type": "send_msg", "uuid": "54b10283-9863-4edf-abfa-705cf24a64fc" } ] @@ -86,7 +86,7 @@ "actions": [ { "text": "Nope, that's neither", - "type": "reply", + "type": "send_msg", "uuid": "27442755-3d94-499f-97d5-a9409ab83b67" } ] @@ -150,7 +150,7 @@ "actions": [ { "text": "All Done! You said @child.results.answer in the child and @run.results.answer here.", - "type": "reply", + "type": "send_msg", "uuid": "bd387e9c-1ea9-49c8-a292-858d8a23a2d0" } ] @@ -177,7 +177,7 @@ "actions": [ { "text": "Welcome to the child, say yes or no!", - "type": "reply", + "type": "send_msg", "uuid": "9b3d32d4-aa6c-44e0-95cd-92117c67738f" } ] @@ -241,7 +241,7 @@ "actions": [ { "text": "Nope, that's neither.", - "type": "reply", + "type": "send_msg", "uuid": "ef6b42af-9751-4120-972c-e60771904dd2" } ] @@ -258,7 +258,7 @@ "actions": [ { "text": "You said yes", - "type": "reply", + "type": "send_msg", "uuid": "7d17e71a-2967-4dbb-86a4-eda028aca38a" } ] @@ -275,7 +275,7 @@ "actions": [ { "text": "You said no", - "type": "reply", + "type": "send_msg", "uuid": "9b03c1fc-c74f-4e27-9349-24df53c6ce96" } ] diff --git a/cmd/flowrunner/testdata/flows/triggered.json b/cmd/flowrunner/testdata/flows/triggered.json index e8bfdd7df..5fe67bbc6 100644 --- a/cmd/flowrunner/testdata/flows/triggered.json +++ b/cmd/flowrunner/testdata/flows/triggered.json @@ -13,7 +13,7 @@ "actions": [ { "uuid": "e97cd6d5-3354-4dbd-85bc-6c1f87849eec", - "type": "reply", + "type": "send_msg", "text": "Hi @contact.name you were started in this flow by @parent.contact.name from the '@parent.flow' flow. He is from @parent.contact.fields.state and is aged @parent.results.age." } ] diff --git a/cmd/flowrunner/testdata/flows/two_questions.json b/cmd/flowrunner/testdata/flows/two_questions.json index 09bdd56bb..a57d64751 100644 --- a/cmd/flowrunner/testdata/flows/two_questions.json +++ b/cmd/flowrunner/testdata/flows/two_questions.json @@ -81,7 +81,7 @@ "actions": [ { "uuid": "e97cd6d5-3354-4dbd-85bc-6c1f87849eec", - "type": "reply", + "type": "send_msg", "text": "Hi @contact.name! What is your favorite color? (red/blue) Your number is @contact.urn" } ], @@ -142,7 +142,7 @@ }, { "uuid": "d2a4052a-3fa9-4608-ab3e-5b9631440447", - "type": "reply", + "type": "send_msg", "text": "@(TITLE(run.results.favorite_color.category_localized)) it is! What is your favorite soda? (pepsi/coke)" } ], @@ -204,7 +204,7 @@ }, { "uuid": "0a8467eb-911a-41db-8101-ccf415c48e6a", - "type": "reply", + "type": "send_msg", "text": "Great, you are done and like @run.results.soda! Webhook status was @run.webhook.status_code" } ] diff --git a/cmd/flowrunner/testdata/flows/webhook_persists.json b/cmd/flowrunner/testdata/flows/webhook_persists.json index 986e7a524..10bc7cf3b 100644 --- a/cmd/flowrunner/testdata/flows/webhook_persists.json +++ b/cmd/flowrunner/testdata/flows/webhook_persists.json @@ -19,7 +19,7 @@ { "uuid": "59cee8f1-ed9e-453e-bc17-6f1996e959d0", "text": "This is the first message.", - "type": "reply" + "type": "send_msg" } ] }, @@ -76,7 +76,7 @@ { "uuid": "8453e418-03ec-40a0-935f-d757cd2ab075", "text": "The status is @run.webhook.json.ok. Send something", - "type": "reply" + "type": "send_msg" } ] }, @@ -112,7 +112,7 @@ { "uuid": "09cd20fb-9a8a-49a2-9c98-fac728c35300", "text": "The status is now @run.webhook.json.ok", - "type": "reply" + "type": "send_msg" } ] } diff --git a/cmd/flowserver/static/start.json b/cmd/flowserver/static/start.json index c6d967c8e..aaf34272a 100644 --- a/cmd/flowserver/static/start.json +++ b/cmd/flowserver/static/start.json @@ -13,7 +13,7 @@ "actions": [ { "uuid": "accca030-7963-4660-92e9-5b86d5f21d05", - "type": "reply", + "type": "send_msg", "text": "You said '@run.input.text'" } ] diff --git a/flows/actions/add_contact_groups.go b/flows/actions/add_contact_groups.go index 57120bef2..14c384e2e 100644 --- a/flows/actions/add_contact_groups.go +++ b/flows/actions/add_contact_groups.go @@ -7,7 +7,7 @@ import ( "github.com/nyaruka/goflow/flows/events" ) -// TypeAddContactGroups is our type for the add to group action +// TypeAddContactGroups is our type for the add to groups action const TypeAddContactGroups string = "add_contact_groups" // AddContactGroupsAction can be used to add a contact to one or more groups. An `contact_groups_added` event will be created diff --git a/flows/actions/add_contact_urn.go b/flows/actions/add_contact_urn.go index 6f3b80f0f..78253eea6 100644 --- a/flows/actions/add_contact_urn.go +++ b/flows/actions/add_contact_urn.go @@ -9,7 +9,7 @@ import ( "github.com/nyaruka/goflow/flows/events" ) -// TypeAddContactURN is our type for add URN actions +// TypeAddContactURN is our type for the add URN action const TypeAddContactURN string = "add_contact_urn" // AddContactURNAction can be used to add a URN to the current contact. An `contact_urn_added` event diff --git a/flows/actions/add_input_labels.go b/flows/actions/add_input_labels.go index 13fc769f6..0b8afdd8c 100644 --- a/flows/actions/add_input_labels.go +++ b/flows/actions/add_input_labels.go @@ -5,7 +5,7 @@ import ( "github.com/nyaruka/goflow/flows/events" ) -// TypeAddInputLabels is our type for add label actions +// TypeAddInputLabels is the type for the add label action const TypeAddInputLabels string = "add_input_labels" // AddInputLabelsAction can be used to add labels to the last user input on a flow. An `input_labels_added` event diff --git a/flows/actions/call_webhook.go b/flows/actions/call_webhook.go index 4b3961331..d04e52aeb 100644 --- a/flows/actions/call_webhook.go +++ b/flows/actions/call_webhook.go @@ -11,10 +11,10 @@ import ( "github.com/nyaruka/goflow/utils" ) -// TypeCallWebhook is the type for our webhook action +// TypeCallWebhook is the type for the call webhook action const TypeCallWebhook string = "call_webhook" -// WebhookAction can be used to call an external service and insert the results in @run.webhook +// CallWebhookAction can be used to call an external service and insert the results in @run.webhook // context variable. The body, header and url fields may be templates and will be evaluated at runtime. // // A `webhook_called` event will be created based on the results of the HTTP call. @@ -32,7 +32,7 @@ const TypeCallWebhook string = "call_webhook" // ``` // // @action call_webhook -type WebhookAction struct { +type CallWebhookAction struct { BaseAction Method string `json:"method" validate:"required,http_method"` URL string `json:"url" validate:"required"` @@ -41,15 +41,15 @@ type WebhookAction struct { } // Type returns the type of this action -func (a *WebhookAction) Type() string { return TypeCallWebhook } +func (a *CallWebhookAction) Type() string { return TypeCallWebhook } // Validate validates our action is valid and has all the assets it needs -func (a *WebhookAction) Validate(assets flows.SessionAssets) error { +func (a *CallWebhookAction) Validate(assets flows.SessionAssets) error { return nil } // Execute runs this action -func (a *WebhookAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { +func (a *CallWebhookAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { // substitute any variables in our url url, err := excellent.EvaluateTemplateAsString(run.Environment(), run.Context(), a.URL, true) if err != nil { diff --git a/flows/actions/envelope.go b/flows/actions/envelope.go index 64c775352..093564e55 100644 --- a/flows/actions/envelope.go +++ b/flows/actions/envelope.go @@ -17,28 +17,28 @@ func ActionFromEnvelope(envelope *utils.TypedEnvelope) (flows.Action, error) { action = &AddContactGroupsAction{} case TypeAddContactURN: action = &AddContactURNAction{} + case TypeCallWebhook: + action = &CallWebhookAction{} + case TypeRemoveContactGroups: + action = &RemoveContactGroupsAction{} + case TypeSendBroadcast: + action = &SendBroadcastAction{} case TypeSendEmail: - action = &EmailAction{} - case TypeStartFlow: - action = &StartFlowAction{} - case TypeStartSession: - action = &StartSessionAction{} + action = &SendEmailAction{} case TypeSendMsg: action = &SendMsgAction{} - case TypeRemoveContactGroups: - action = &RemoveContactGroupsAction{} - case TypeReply: - action = &ReplyAction{} - case TypeSaveFlowResult: - action = &SaveFlowResultAction{} - case TypeSetContactField: - action = &SetContactFieldAction{} case TypeSetContactChannel: action = &SetContactChannelAction{} + case TypeSetContactField: + action = &SetContactFieldAction{} case TypeSetContactProperty: action = &SetContactPropertyAction{} - case TypeCallWebhook: - action = &WebhookAction{} + case TypeSetRunResult: + action = &SetRunResultAction{} + case TypeStartFlow: + action = &StartFlowAction{} + case TypeStartSession: + action = &StartSessionAction{} default: return nil, fmt.Errorf("unknown action type: %s", envelope.Type) } diff --git a/flows/actions/remove_contact_groups.go b/flows/actions/remove_contact_groups.go index 89940e475..8db3517d0 100644 --- a/flows/actions/remove_contact_groups.go +++ b/flows/actions/remove_contact_groups.go @@ -7,7 +7,7 @@ import ( "github.com/nyaruka/goflow/flows/events" ) -// TypeRemoveContactGroups is our type for our remove from group action +// TypeRemoveContactGroups is the type for the remove from groups action const TypeRemoveContactGroups string = "remove_contact_groups" // RemoveContactGroupsAction can be used to remove a contact from one or more groups. A `contact_groups_removed` event will be created diff --git a/flows/actions/reply.go b/flows/actions/reply.go deleted file mode 100644 index 42d5e2575..000000000 --- a/flows/actions/reply.go +++ /dev/null @@ -1,84 +0,0 @@ -package actions - -import ( - "fmt" - "github.com/nyaruka/gocommon/urns" - "github.com/nyaruka/goflow/flows" - "github.com/nyaruka/goflow/flows/events" -) - -// TypeReply is the type for reply actions -const TypeReply string = "reply" - -// ReplyAction can be used to reply to the current contact in a flow. The text field may contain templates. -// -// A `broadcast_created` event will be created with the evaluated text. -// -// ``` -// { -// "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9", -// "type": "reply", -// "text": "Hi @contact.name, are you ready to complete today's survey?", -// "attachments": [], -// "all_urns": false -// } -// ``` -// -// @action reply -type ReplyAction struct { - BaseAction - Text string `json:"text"` - Attachments []string `json:"attachments"` - QuickReplies []string `json:"quick_replies,omitempty"` - AllURNs bool `json:"all_urns,omitempty"` -} - -type msgDestination struct { - urn urns.URN - channel flows.Channel -} - -// Type returns the type of this action -func (a *ReplyAction) Type() string { return TypeReply } - -// Validate validates our action is valid and has all the assets it needs -func (a *ReplyAction) Validate(assets flows.SessionAssets) error { - return nil -} - -// Execute runs this action -func (a *ReplyAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { - if run.Contact() == nil { - log.Add(events.NewFatalErrorEvent(fmt.Errorf("can't execute action in session without a contact"))) - return nil - } - - evaluatedText, evaluatedAttachments, evaluatedQuickReplies := a.evaluateMessage(run, step, a.Text, a.Attachments, a.QuickReplies, log) - - channelSet, err := run.Session().Assets().GetChannelSet() - if err != nil { - return err - } - - destinations := []msgDestination{} - - for _, u := range run.Contact().URNs() { - channel := channelSet.GetForURN(u) - if channel != nil { - destinations = append(destinations, msgDestination{urn: u.URN, channel: channel}) - - // if we're not sending to all URNs we just need the first sendable URN - if !a.AllURNs { - break - } - } - } - - // create a new message for each URN+channel destination - for _, dest := range destinations { - msg := flows.NewMsgOut(dest.urn, dest.channel, evaluatedText, evaluatedAttachments, evaluatedQuickReplies) - log.Add(events.NewMsgCreatedEvent(msg)) - } - - return nil -} diff --git a/flows/actions/send_broadcast.go b/flows/actions/send_broadcast.go new file mode 100644 index 000000000..6a1c764e4 --- /dev/null +++ b/flows/actions/send_broadcast.go @@ -0,0 +1,59 @@ +package actions + +import ( + "github.com/nyaruka/gocommon/urns" + "github.com/nyaruka/goflow/flows" + "github.com/nyaruka/goflow/flows/events" +) + +// TypeSendBroadcast is the type for the send broadcast action +const TypeSendBroadcast string = "send_broadcast" + +// SendBroadcastAction can be used to send a message to one or more contacts. It accepts a list of URNs, a list of groups +// and a list of contacts. +// +// The URNs and text fields may be templates. A `send_broadcast` event will be created for each unique urn, contact and group +// with the evaluated text. +// +// ``` +// { +// "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9", +// "type": "send_broadcast", +// "urns": ["tel:+12065551212"], +// "text": "Hi @contact.name, are you ready to complete today's survey?" +// } +// ``` +// +// @action send_broadcast +type SendBroadcastAction struct { + BaseAction + Text string `json:"text"` + Attachments []string `json:"attachments"` + QuickReplies []string `json:"quick_replies,omitempty"` + URNs []urns.URN `json:"urns,omitempty"` + Contacts []*flows.ContactReference `json:"contacts,omitempty" validate:"dive"` + Groups []*flows.GroupReference `json:"groups,omitempty" validate:"dive"` + LegacyVars []string `json:"legacy_vars,omitempty"` +} + +// Type returns the type of this action +func (a *SendBroadcastAction) Type() string { return TypeSendBroadcast } + +// Validate validates our action is valid and has all the assets it needs +func (a *SendBroadcastAction) Validate(assets flows.SessionAssets) error { + return a.validateGroups(assets, a.Groups) +} + +// Execute runs this action +func (a *SendBroadcastAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { + evaluatedText, evaluatedAttachments, evaluatedQuickReplies := a.evaluateMessage(run, step, a.Text, a.Attachments, a.QuickReplies, log) + + urnList, contactRefs, groupRefs, err := a.resolveContactsAndGroups(run, step, a.URNs, a.Contacts, a.Groups, a.LegacyVars, log) + if err != nil { + return err + } + + log.Add(events.NewBroadcastCreatedEvent(evaluatedText, evaluatedAttachments, evaluatedQuickReplies, urnList, contactRefs, groupRefs)) + + return nil +} diff --git a/flows/actions/send_email.go b/flows/actions/send_email.go index 11a22714e..7fec97711 100644 --- a/flows/actions/send_email.go +++ b/flows/actions/send_email.go @@ -10,10 +10,10 @@ import ( "github.com/nyaruka/goflow/flows/events" ) -// TypeSendEmail is our type for the email action +// TypeSendEmail is the type for the send email action const TypeSendEmail string = "send_email" -// EmailAction can be used to send an email to one or more recipients. The subject, body and addresses +// SendEmailAction can be used to send an email to one or more recipients. The subject, body and addresses // can all contain expressions. // // A `email_created` event will be created for each email address. @@ -29,7 +29,7 @@ const TypeSendEmail string = "send_email" // ``` // // @action send_email -type EmailAction struct { +type SendEmailAction struct { BaseAction Addresses []string `json:"addresses" validate:"required,min=1"` Subject string `json:"subject" validate:"required"` @@ -37,15 +37,15 @@ type EmailAction struct { } // Type returns the type of this action -func (a *EmailAction) Type() string { return TypeSendEmail } +func (a *SendEmailAction) Type() string { return TypeSendEmail } // Validate validates our action is valid and has all the assets it needs -func (a *EmailAction) Validate(assets flows.SessionAssets) error { +func (a *SendEmailAction) Validate(assets flows.SessionAssets) error { return nil } // Execute creates the email events -func (a *EmailAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { +func (a *SendEmailAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { subject, err := excellent.EvaluateTemplateAsString(run.Environment(), run.Context(), a.Subject, false) if err != nil { log.Add(events.NewErrorEvent(err)) diff --git a/flows/actions/send_msg.go b/flows/actions/send_msg.go index 9be133c61..1506b2a95 100644 --- a/flows/actions/send_msg.go +++ b/flows/actions/send_msg.go @@ -1,39 +1,41 @@ package actions import ( + "fmt" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/events" ) -// TypeSendMsg is the type for msg actions +// TypeSendMsg is the type for the send message action const TypeSendMsg string = "send_msg" -// SendMsgAction can be used to send a message to one or more contacts. It accepts a list of URNs, a list of groups -// and a list of contacts. +// SendMsgAction can be used to reply to the current contact in a flow. The text field may contain templates. // -// The URNs and text fields may be templates. A `send_msg` event will be created for each unique urn, contact and group -// with the evaluated text. +// A `broadcast_created` event will be created with the evaluated text. // // ``` // { // "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9", // "type": "send_msg", -// "urns": ["tel:+12065551212"], -// "text": "Hi @contact.name, are you ready to complete today's survey?" +// "text": "Hi @contact.name, are you ready to complete today's survey?", +// "attachments": [], +// "all_urns": false // } // ``` // // @action send_msg type SendMsgAction struct { BaseAction - Text string `json:"text"` - Attachments []string `json:"attachments"` - QuickReplies []string `json:"quick_replies,omitempty"` - URNs []urns.URN `json:"urns,omitempty"` - Contacts []*flows.ContactReference `json:"contacts,omitempty" validate:"dive"` - Groups []*flows.GroupReference `json:"groups,omitempty" validate:"dive"` - LegacyVars []string `json:"legacy_vars,omitempty"` + Text string `json:"text"` + Attachments []string `json:"attachments"` + QuickReplies []string `json:"quick_replies,omitempty"` + AllURNs bool `json:"all_urns,omitempty"` +} + +type msgDestination struct { + urn urns.URN + channel flows.Channel } // Type returns the type of this action @@ -41,19 +43,42 @@ func (a *SendMsgAction) Type() string { return TypeSendMsg } // Validate validates our action is valid and has all the assets it needs func (a *SendMsgAction) Validate(assets flows.SessionAssets) error { - return a.validateGroups(assets, a.Groups) + return nil } // Execute runs this action func (a *SendMsgAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { + if run.Contact() == nil { + log.Add(events.NewFatalErrorEvent(fmt.Errorf("can't execute action in session without a contact"))) + return nil + } + evaluatedText, evaluatedAttachments, evaluatedQuickReplies := a.evaluateMessage(run, step, a.Text, a.Attachments, a.QuickReplies, log) - urnList, contactRefs, groupRefs, err := a.resolveContactsAndGroups(run, step, a.URNs, a.Contacts, a.Groups, a.LegacyVars, log) + channelSet, err := run.Session().Assets().GetChannelSet() if err != nil { return err } - log.Add(events.NewBroadcastCreatedEvent(evaluatedText, evaluatedAttachments, evaluatedQuickReplies, urnList, contactRefs, groupRefs)) + destinations := []msgDestination{} + + for _, u := range run.Contact().URNs() { + channel := channelSet.GetForURN(u) + if channel != nil { + destinations = append(destinations, msgDestination{urn: u.URN, channel: channel}) + + // if we're not sending to all URNs we just need the first sendable URN + if !a.AllURNs { + break + } + } + } + + // create a new message for each URN+channel destination + for _, dest := range destinations { + msg := flows.NewMsgOut(dest.urn, dest.channel, evaluatedText, evaluatedAttachments, evaluatedQuickReplies) + log.Add(events.NewMsgCreatedEvent(msg)) + } return nil } diff --git a/flows/actions/set_contact_channel.go b/flows/actions/set_contact_channel.go index 1d8324a0d..e05c5ee19 100644 --- a/flows/actions/set_contact_channel.go +++ b/flows/actions/set_contact_channel.go @@ -7,6 +7,7 @@ import ( "github.com/nyaruka/goflow/flows/events" ) +// TypeSetContactChannel is the type for the set contact channel action const TypeSetContactChannel string = "set_contact_channel" type SetContactChannelAction struct { diff --git a/flows/actions/set_contact_field.go b/flows/actions/set_contact_field.go index 4bb106481..d86f24c8c 100644 --- a/flows/actions/set_contact_field.go +++ b/flows/actions/set_contact_field.go @@ -9,7 +9,7 @@ import ( "github.com/nyaruka/goflow/utils" ) -// TypeSetContactField is the type for our set contact field action +// TypeSetContactField is the type for the set contact field action const TypeSetContactField string = "set_contact_field" // SetContactFieldAction can be used to save a value to a contact. The value can be a template and will diff --git a/flows/actions/set_contact_property.go b/flows/actions/set_contact_property.go index 670099196..5e8db17a3 100644 --- a/flows/actions/set_contact_property.go +++ b/flows/actions/set_contact_property.go @@ -10,7 +10,7 @@ import ( "github.com/nyaruka/goflow/utils" ) -// TypeSetContactProperty is the type for our set contact property action +// TypeSetContactProperty is the type for the set contact property action const TypeSetContactProperty string = "set_contact_property" // SetContactPropertyAction can be used to update one of the built in fields for a contact of "name" or diff --git a/flows/actions/save_flow_result.go b/flows/actions/set_run_result.go similarity index 72% rename from flows/actions/save_flow_result.go rename to flows/actions/set_run_result.go index 7b396baf8..c287959f8 100644 --- a/flows/actions/save_flow_result.go +++ b/flows/actions/set_run_result.go @@ -7,10 +7,10 @@ import ( "github.com/nyaruka/goflow/utils" ) -// TypeSaveFlowResult is our type for the save result action -const TypeSaveFlowResult string = "save_flow_result" +// TypeSetRunResult is the type for the set run result action +const TypeSetRunResult string = "set_run_result" -// SaveFlowResultAction can be used to save a result for a flow. The result will be available in the context +// SetRunResultAction can be used to save a result for a flow. The result will be available in the context // for the run as @run.results.[name]. The optional category can be used as a way of categorizing results, // this can be useful for reporting or analytics. // @@ -20,15 +20,15 @@ const TypeSaveFlowResult string = "save_flow_result" // ``` // { // "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9", -// "type": "save_flow_result", +// "type": "set_run_result", // "name": "Gender", // "value": "m", // "category": "Male" // } // ``` // -// @action save_flow_result -type SaveFlowResultAction struct { +// @action set_run_result +type SetRunResultAction struct { BaseAction Name string `json:"name" validate:"required"` Value string `json:"value" validate:"required"` @@ -36,15 +36,15 @@ type SaveFlowResultAction struct { } // Type returns the type of this action -func (a *SaveFlowResultAction) Type() string { return TypeSaveFlowResult } +func (a *SetRunResultAction) Type() string { return TypeSetRunResult } // Validate validates our action is valid and has all the assets it needs -func (a *SaveFlowResultAction) Validate(assets flows.SessionAssets) error { +func (a *SetRunResultAction) Validate(assets flows.SessionAssets) error { return nil } // Execute runs this action -func (a *SaveFlowResultAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { +func (a *SetRunResultAction) Execute(run flows.FlowRun, step flows.Step, log flows.EventLog) error { // get our localized value if any template := run.GetText(utils.UUID(a.UUID()), "value", a.Value) value, err := excellent.EvaluateTemplateAsString(run.Environment(), run.Context(), template, false) diff --git a/flows/definition/legacy.go b/flows/definition/legacy.go index 93115694e..6270804b8 100644 --- a/flows/definition/legacy.go +++ b/flows/definition/legacy.go @@ -398,7 +398,7 @@ func migrateAction(baseLanguage utils.Language, a legacyAction, translations *fl migratedEmails[e], _ = excellent.MigrateTemplate(email, excellent.ExtraAsFunction) } - return &actions.EmailAction{ + return &actions.SendEmailAction{ Subject: migratedSubject, Body: migratedBody, Addresses: migratedEmails, @@ -487,7 +487,7 @@ func migrateAction(baseLanguage utils.Language, a legacyAction, translations *fl } if a.Type == "reply" { - return &actions.ReplyAction{ + return &actions.SendMsgAction{ BaseAction: actions.NewBaseAction(a.UUID), Text: migratedText, Attachments: attachments, @@ -510,7 +510,7 @@ func migrateAction(baseLanguage utils.Language, a legacyAction, translations *fl variables = append(variables, migratedVar) } - return &actions.SendMsgAction{ + return &actions.SendBroadcastAction{ BaseAction: actions.NewBaseAction(a.UUID), Text: migratedText, Attachments: attachments, @@ -586,7 +586,7 @@ func migrateAction(baseLanguage utils.Language, a legacyAction, translations *fl headers[header.Name] = header.Value } - return &actions.WebhookAction{ + return &actions.CallWebhookAction{ Method: a.Action, URL: migratedURL, Headers: headers, @@ -838,7 +838,7 @@ func migrateRuleSet(lang utils.Language, r legacyRuleSet, translations *flowTran } node.actions = []flows.Action{ - &actions.WebhookAction{ + &actions.CallWebhookAction{ BaseAction: actions.NewBaseAction(flows.ActionUUID(utils.NewUUID())), URL: migratedURL, Method: config.Action, diff --git a/flows/definition/testdata/migrations/actions.json b/flows/definition/testdata/migrations/actions.json index 0a5cdae07..201e4509b 100644 --- a/flows/definition/testdata/migrations/actions.json +++ b/flows/definition/testdata/migrations/actions.json @@ -134,7 +134,7 @@ "send_all": false }, "expected_action": { - "type": "reply", + "type": "send_msg", "uuid": "5a4d00aa-807e-44af-9693-64b9fdedd352", "text": "Do you still live in @contact.fields.city?", "attachments": [] @@ -161,7 +161,7 @@ "send_all": true }, "expected_action": { - "type": "reply", + "type": "send_msg", "uuid": "5a4d00aa-807e-44af-9693-64b9fdedd352", "text": "Do you still live in @contact.fields.city?", "attachments": ["image/jpeg:http://s3.amazon.com/bucket/test_en.jpg?a=@contact.fields.age"], @@ -252,7 +252,7 @@ ] }, "expected_action": { - "type": "send_msg", + "type": "send_broadcast", "uuid": "5a4d00aa-807e-44af-9693-64b9fdedd352", "text": "Do you still live in @contact.fields.city?", "attachments": ["image/jpeg:http://s3.amazon.com/bucket/test_en.jpg?a=@contact.fields.age"],