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

Optin action and event #1185

Merged
merged 5 commits into from
Sep 14, 2023
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: CI
on: [push, pull_request]
env:
go-version: "1.19.x"
go-version: "1.20.x"
jobs:
test:
name: Test
Expand Down
8 changes: 8 additions & 0 deletions assets/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ const (
ChannelRoleUSSD ChannelRole = "ussd"
)

// ChannelFeature is a feature that a channel supports
type ChannelFeature string

const (
ChannelFeatureOptIns ChannelFeature = "optins"
)

// Channel is something that can send/receive messages.
//
// {
Expand All @@ -40,6 +47,7 @@ type Channel interface {
Address() string
Schemes() []string
Roles() []ChannelRole
Features() []ChannelFeature
Country() i18n.Country
MatchPrefixes() []string
AllowInternational() bool
Expand Down
24 changes: 15 additions & 9 deletions assets/static/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,26 @@ import (

// Channel is a JSON serializable implementation of a channel asset
type Channel struct {
UUID_ assets.ChannelUUID `json:"uuid" validate:"required,uuid"`
Name_ string `json:"name"`
Address_ string `json:"address"`
Schemes_ []string `json:"schemes" validate:"min=1"`
Roles_ []assets.ChannelRole `json:"roles" validate:"min=1,dive,eq=send|eq=receive|eq=call|eq=answer|eq=ussd"`
Country_ i18n.Country `json:"country,omitempty"`
MatchPrefixes_ []string `json:"match_prefixes,omitempty"`
AllowInternational_ bool `json:"allow_international,omitempty"`
UUID_ assets.ChannelUUID `json:"uuid" validate:"required,uuid"`
Name_ string `json:"name"`
Address_ string `json:"address"`
Schemes_ []string `json:"schemes" validate:"min=1"`
Roles_ []assets.ChannelRole `json:"roles" validate:"min=1,dive,eq=send|eq=receive|eq=call|eq=answer|eq=ussd"`
Features_ []assets.ChannelFeature `json:"features,omitempty"`
Country_ i18n.Country `json:"country,omitempty"`
MatchPrefixes_ []string `json:"match_prefixes,omitempty"`
AllowInternational_ bool `json:"allow_international,omitempty"`
}

// NewChannel creates a new channel
func NewChannel(uuid assets.ChannelUUID, name string, address string, schemes []string, roles []assets.ChannelRole) assets.Channel {
func NewChannel(uuid assets.ChannelUUID, name string, address string, schemes []string, roles []assets.ChannelRole, features []assets.ChannelFeature) assets.Channel {
return &Channel{
UUID_: uuid,
Name_: name,
Address_: address,
Schemes_: schemes,
Roles_: roles,
Features_: features,
AllowInternational_: true,
}
}
Expand All @@ -38,6 +40,7 @@ func NewTelChannel(uuid assets.ChannelUUID, name string, address string, roles [
Address_: address,
Schemes_: []string{urns.TelScheme},
Roles_: roles,
Features_: []assets.ChannelFeature{},
Country_: country,
MatchPrefixes_: matchPrefixes,
AllowInternational_: allowInternational,
Expand All @@ -59,6 +62,9 @@ func (c *Channel) Schemes() []string { return c.Schemes_ }
// Roles returns the roles of this channel
func (c *Channel) Roles() []assets.ChannelRole { return c.Roles_ }

// Features returnsthe featurs this channel supports
func (c *Channel) Features() []assets.ChannelFeature { return c.Features_ }

// Country returns this channel's associated country code (if any)
func (c *Channel) Country() i18n.Country { return c.Country_ }

Expand Down
2 changes: 2 additions & 0 deletions assets/static/channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ func TestChannel(t *testing.T) {
"+234151",
[]string{"tel"},
[]assets.ChannelRole{assets.ChannelRoleSend},
[]assets.ChannelFeature{assets.ChannelFeatureOptIns},
)
assert.Equal(t, assets.ChannelUUID("ffffffff-9b24-92e1-ffff-ffffb207cdb4"), channel.UUID())
assert.Equal(t, "Android", channel.Name())
assert.Equal(t, "+234151", channel.Address())
assert.Equal(t, []string{"tel"}, channel.Schemes())
assert.Equal(t, []assets.ChannelRole{assets.ChannelRoleSend}, channel.Roles())
assert.Equal(t, []assets.ChannelFeature{assets.ChannelFeatureOptIns}, channel.Features())
assert.Equal(t, i18n.NilCountry, channel.Country())
assert.Nil(t, channel.MatchPrefixes())
assert.True(t, channel.AllowInternational())
Expand Down
38 changes: 27 additions & 11 deletions flows/actions/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,15 @@ import (
"github.com/stretchr/testify/require"
)

var contactJSON = `{
var defaultContactJSON = []byte(`{
"uuid": "5d76d86b-3bb9-4d5a-b822-c9d86f5d8e4f",
"name": "Ryan Lewis",
"language": "eng",
"timezone": "America/Guayaquil",
"urns": [],
"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"}
Expand All @@ -53,7 +56,7 @@ var contactJSON = `{
}
},
"created_on": "2018-06-20T11:40:30.123456789-00:00"
}`
}`)

func TestActionTypes(t *testing.T) {
assetsJSON, err := os.ReadFile("testdata/_assets.json")
Expand Down Expand Up @@ -81,7 +84,7 @@ func testActionType(t *testing.T, assetsJSON json.RawMessage, typeName string) {
HTTPMocks *httpx.MockRequestor `json:"http_mocks,omitempty"`
SMTPError string `json:"smtp_error,omitempty"`
NoContact bool `json:"no_contact,omitempty"`
NoURNs bool `json:"no_urns,omitempty"`
Contact json.RawMessage `json:"contact,omitempty"`
HasTicket bool `json:"has_ticket,omitempty"`
NoInput bool `json:"no_input,omitempty"`
RedactURNs bool `json:"redact_urns,omitempty"`
Expand Down Expand Up @@ -164,15 +167,14 @@ func testActionType(t *testing.T, assetsJSON json.RawMessage, typeName string) {
// optionally load our contact
var contact *flows.Contact
if !tc.NoContact {
contact, err = flows.ReadContact(sa, json.RawMessage(contactJSON), assets.PanicOnMissing)
contactJSON := defaultContactJSON
if tc.Contact != nil {
contactJSON = tc.Contact
}

contact, err = flows.ReadContact(sa, contactJSON, assets.PanicOnMissing)
require.NoError(t, err)

// optionally give our contact some URNs and a ticket
if !tc.NoURNs {
channel := sa.Channels().Get("57f1078f-88aa-46f4-a59a-948a5739c03d")
contact.AddURN(urns.URN("tel:+12065551212?channel=57f1078f-88aa-46f4-a59a-948a5739c03d&id=123"), channel)
contact.AddURN(urns.URN("twitterid:54784326227#nyaruka"), nil)
}
if tc.HasTicket {
ticketer := sa.Ticketers().Get("d605bb96-258d-4097-ad0a-080937db2212")
topic := sa.Topics().Get("0d9a2c56-6fc2-4f27-93c5-a6322e26b740")
Expand Down Expand Up @@ -568,6 +570,20 @@ func TestConstructors(t *testing.T) {
"body": "So I was thinking..."
}`,
},
{
actions.NewSendOptIn(
actionUUID,
assets.NewOptInReference("248be71d-78e9-4d71-a6c4-9981d369e5cb", "Joke Of The Day"),
),
`{
"type": "send_optin",
"uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
"optin": {
"uuid": "248be71d-78e9-4d71-a6c4-9981d369e5cb",
"name": "Joke Of The Day"
}
}`,
},
{
actions.NewSendMsg(
actionUUID,
Expand Down
55 changes: 55 additions & 0 deletions flows/actions/send_optin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package actions

import (
"github.com/nyaruka/goflow/assets"
"github.com/nyaruka/goflow/flows"
"github.com/nyaruka/goflow/flows/events"
)

func init() {
registerType(TypeSendOptIn, func() flows.Action { return &SendOptInAction{} })
}

// TypeSendOptIn is the type for the send optin action
const TypeSendOptIn string = "send_optin"

// SendOptInAction can be used to send an optin to the contact if the channel supports that.
//
// An [event:optin_sent] event will be created if the optin was sent.
//
// {
// "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9",
// "type": "send_optin",
// "optin": {
// "uuid": "248be71d-78e9-4d71-a6c4-9981d369e5cb",
// "name": "Joke Of The Day"
// }
// }
//
// @action send_optin
type SendOptInAction struct {
baseAction
onlineAction

OptIn *assets.OptInReference `json:"optin" validate:"required,dive"`
}

// NewSendOptIn creates a new send optin action
func NewSendOptIn(uuid flows.ActionUUID, optIn *assets.OptInReference) *SendOptInAction {
return &SendOptInAction{
baseAction: newBaseAction(TypeSendOptIn, uuid),
OptIn: optIn,
}
}

// Execute creates the optin events
func (a *SendOptInAction) Execute(run flows.Run, step flows.Step, logModifier flows.ModifierCallback, logEvent flows.EventCallback) error {
optIn := run.Session().Assets().OptIns().Get(a.OptIn.UUID)
destinations := run.Contact().ResolveDestinations(false)

if len(destinations) > 0 && destinations[0].Channel.HasFeature(assets.ChannelFeatureOptIns) {
logEvent(events.NewOptInSent(optIn))
}

return nil
}
21 changes: 21 additions & 0 deletions flows/actions/testdata/_assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,21 @@
"receive"
]
},
{
"uuid": "4bb288a0-7fca-4da1-abe8-59a593aff648",
"name": "Facebook Channel",
"address": "235326346322111",
"schemes": [
"facebook"
],
"roles": [
"send",
"receive"
],
"features": [
"optins"
]
},
{
"uuid": "8e21f093-99aa-413b-b55b-758b54308fcb",
"name": "Twitter Channel",
Expand Down Expand Up @@ -205,6 +220,12 @@
"name": "Spam"
}
],
"optins": [
{
"uuid": "248be71d-78e9-4d71-a6c4-9981d369e5cb",
"name": "Joke Of The Day"
}
],
"resthooks": [
{
"slug": "new-registration",
Expand Down
11 changes: 10 additions & 1 deletion flows/actions/testdata/call_resthook.json
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,16 @@
}
]
},
"no_urns": true,
"contact": {
"uuid": "5d76d86b-3bb9-4d5a-b822-c9d86f5d8e4f",
"name": "Ryan Lewis",
"language": "eng",
"timezone": "America/Guayaquil",
"urns": [],
"groups": [],
"fields": {},
"created_on": "2018-06-20T11:40:30.123456789-00:00"
},
"no_input": true,
"action": {
"type": "call_resthook",
Expand Down
11 changes: 10 additions & 1 deletion flows/actions/testdata/send_msg.json
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,16 @@
},
{
"description": "Msg created event even if contact has no sendable URNs",
"no_urns": true,
"contact": {
"uuid": "5d76d86b-3bb9-4d5a-b822-c9d86f5d8e4f",
"name": "Ryan Lewis",
"language": "eng",
"timezone": "America/Guayaquil",
"urns": [],
"groups": [],
"fields": {},
"created_on": "2018-06-20T11:40:30.123456789-00:00"
},
"action": {
"type": "send_msg",
"uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
Expand Down
48 changes: 48 additions & 0 deletions flows/actions/testdata/send_optin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
[
{
"description": "NOOP when channel doesn't support optins feature",
"action": {
"type": "send_optin",
"uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
"optin": {
"uuid": "248be71d-78e9-4d71-a6c4-9981d369e5cb",
"name": "Joke Of The Day"
}
},
"events": []
},
{
"description": "Event created when channel does support optins feature",
"contact": {
"uuid": "5d76d86b-3bb9-4d5a-b822-c9d86f5d8e4f",
"name": "Ryan Lewis",
"language": "eng",
"timezone": "America/Guayaquil",
"urns": [
"facebook:1234567890"
],
"groups": [],
"fields": {},
"created_on": "2018-06-20T11:40:30.123456789-00:00"
},
"action": {
"type": "send_optin",
"uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
"optin": {
"uuid": "248be71d-78e9-4d71-a6c4-9981d369e5cb",
"name": "Joke Of The Day"
}
},
"events": [
{
"type": "optin_sent",
"created_on": "2018-10-18T14:20:30.000123456Z",
"step_uuid": "59d74b86-3e2f-4a93-aece-b05d2fdcde0c",
"optin": {
"name": "Joke Of The Day",
"uuid": "248be71d-78e9-4d71-a6c4-9981d369e5cb"
}
}
]
}
]
11 changes: 10 additions & 1 deletion flows/actions/testdata/transfer_airtime.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,16 @@
},
{
"description": "Error and failed transfer if contact has no tel urn",
"no_urns": true,
"contact": {
"uuid": "5d76d86b-3bb9-4d5a-b822-c9d86f5d8e4f",
"name": "Ryan Lewis",
"language": "eng",
"timezone": "America/Guayaquil",
"urns": [],
"groups": [],
"fields": {},
"created_on": "2018-06-20T11:40:30.123456789-00:00"
},
"action": {
"type": "transfer_airtime",
"uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
Expand Down
Loading