diff --git a/flows/actions/send_msg.go b/flows/actions/send_msg.go index 03f6ff6f..945801e5 100644 --- a/flows/actions/send_msg.go +++ b/flows/actions/send_msg.go @@ -7,6 +7,7 @@ import ( "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/events" + "math/rand" ) func init() { @@ -83,7 +84,27 @@ func (a *SendMsgAction) Execute(run flows.Run, step flows.Step, logModifier flow evaluatedText, evaluatedAttachments, evaluatedQuickReplies := a.evaluateMessage(run, nil, a.Text, a.Attachments, a.QuickReplies, logEvent) - destinations := run.Contact().ResolveDestinations(a.AllURNs) + var destinations []flows.Destination + flowChannels := run.Flow().Channels() + if len(flowChannels) > 0 { + //<*((==< + destinations = run.Contact().ResolveURNDestinations(a.AllURNs, flowChannels) + if len(destinations) == 0 { + theRandomChannelUUID := run.Flow().ChannelUUIDs()[rand.Intn(len(flowChannels))] + //pick URN from contact based on channel we have + if theURN, theChannel := run.Contact().ResolveURN(theRandomChannelUUID); theURN != nil && theChannel != nil { + destinations = append(destinations, flows.Destination{URN: theURN, Channel: theChannel}) + } else { + logEvent(events.NewErrorf("no matching URN for contact [%s] with channel [%s]", + run.Contact().UUID(), + theRandomChannelUUID, + )) + return nil + } + } + } else { + destinations = run.Contact().ResolveDestinations(a.AllURNs) + } sa := run.Session().Assets() diff --git a/flows/contact.go b/flows/contact.go index c66918a7..1541f9c7 100644 --- a/flows/contact.go +++ b/flows/contact.go @@ -379,6 +379,34 @@ func (c *Contact) ResolveDestinations(all bool) []Destination { return destinations } +// ResolveURNDestinations resolves possible URN/channel destinations //<*((==< +func (c *Contact) ResolveURNDestinations(all bool, flowChannels map[assets.ChannelUUID]*assets.ChannelReference) []Destination { + var destinations []Destination + for _, u := range c.urns { + channel := c.assets.Channels().GetForURN(u, assets.ChannelRoleSend) + if channel != nil { + if _, found := flowChannels[channel.UUID()]; found { + destinations = append(destinations, Destination{URN: u, Channel: channel}) + if !all { + break + } + } + } + } + return destinations +} + +// ResolveURN resolves possible URN given channel to use //<*((==< +func (c *Contact) ResolveURN(aChannelID assets.ChannelUUID) (*ContactURN, *Channel) { + for _, u := range c.urns { + channel := c.assets.Channels().Get(aChannelID) + if channel != nil && channel.SupportsScheme(u.URN().Scheme()) { + return u, channel + } + } + return nil, nil +} + // PreferredURN gets the preferred URN for this contact, i.e. the URN we would use for sending func (c *Contact) PreferredURN() *ContactURN { destinations := c.ResolveDestinations(false) diff --git a/flows/definition/flow.go b/flows/definition/flow.go index 6da7f163..fefe38be 100644 --- a/flows/definition/flow.go +++ b/flows/definition/flow.go @@ -38,6 +38,8 @@ type flow struct { expireAfterMinutes int localization flows.Localization nodes []flows.Node + channels []assets.ChannelUUID //<*((==< + channelMap map[assets.ChannelUUID]*assets.ChannelReference //<*((==< // optional properties not used by engine itself ui json.RawMessage @@ -50,7 +52,7 @@ type flow struct { } // NewFlow creates a new flow -func NewFlow(uuid assets.FlowUUID, name string, language envs.Language, flowType flows.FlowType, revision int, expireAfterMinutes int, localization flows.Localization, nodes []flows.Node, ui json.RawMessage, a assets.Flow) (flows.Flow, error) { +func NewFlow(uuid assets.FlowUUID, name string, language envs.Language, flowType flows.FlowType, revision int, expireAfterMinutes int, localization flows.Localization, nodes []flows.Node, ui json.RawMessage, a assets.Flow, channels []assets.ChannelUUID) (flows.Flow, error) { f := &flow{ uuid: uuid, name: name, @@ -64,11 +66,17 @@ func NewFlow(uuid assets.FlowUUID, name string, language envs.Language, flowType nodeMap: make(map[flows.NodeUUID]flows.Node, len(nodes)), ui: ui, asset: a, + channels: channels, } for _, node := range f.nodes { f.nodeMap[node.UUID()] = node } + //<*((==< + f.channelMap = make(map[assets.ChannelUUID]*assets.ChannelReference, len(f.channels)) + for i := range f.channels { + f.channelMap[f.channels[i]] = assets.NewChannelReference(f.channels[i], string("Name Of: "+f.channels[i])) + } if err := f.validate(); err != nil { return nil, err @@ -88,6 +96,8 @@ func (f *flow) Nodes() []flows.Node { return f.nodes } func (f *flow) Localization() flows.Localization { return f.localization } func (f *flow) UI() json.RawMessage { return f.ui } func (f *flow) GetNode(uuid flows.NodeUUID) flows.Node { return f.nodeMap[uuid] } +func (f *flow) ChannelUUIDs() []assets.ChannelUUID { return f.channels } //<*((==< +func (f *flow) Channels() map[assets.ChannelUUID]*assets.ChannelReference { return f.channelMap } //<*((==< func (f *flow) validate() error { // track UUIDs used by nodes and actions to ensure that they are unique @@ -315,6 +325,8 @@ type flowEnvelope struct { Localization localization `json:"localization"` Nodes []*node `json:"nodes"` UI json.RawMessage `json:"_ui,omitempty"` + //<*((==< + ChannelUUIDs []assets.ChannelUUID `json:"channels,omitempty"` } // ReadFlow reads a flow definition from the passed in byte array, migrating it to the spec version of the engine if necessary @@ -355,7 +367,7 @@ func readFlow(data json.RawMessage, mc *migrations.Config, a assets.Flow) (flows e.Localization = make(localization) } - return NewFlow(e.UUID, e.Name, e.Language, e.Type, e.Revision, e.ExpireAfterMinutes, e.Localization, nodes, e.UI, a) + return NewFlow(e.UUID, e.Name, e.Language, e.Type, e.Revision, e.ExpireAfterMinutes, e.Localization, nodes, e.UI, a, e.ChannelUUIDs) } // MarshalJSON marshals this flow into JSON diff --git a/flows/definition/flow_test.go b/flows/definition/flow_test.go index e056a7dd..5d64c334 100644 --- a/flows/definition/flow_test.go +++ b/flows/definition/flow_test.go @@ -273,6 +273,7 @@ func TestNewFlow(t *testing.T) { }, nil, // no UI nil, // no asset + nil, ) require.NoError(t, err) diff --git a/flows/interfaces.go b/flows/interfaces.go index cac81f2d..bad6cd2c 100644 --- a/flows/interfaces.go +++ b/flows/interfaces.go @@ -136,6 +136,8 @@ type Flow interface { UI() json.RawMessage Nodes() []Node GetNode(uuid NodeUUID) Node + ChannelUUIDs() []assets.ChannelUUID //<*((==< + Channels() map[assets.ChannelUUID]*assets.ChannelReference //<*((==< Asset() assets.Flow Reference() *assets.FlowReference