From b1277289762ee301625fb335bc2726e4ed05c846 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 17 Apr 2024 12:11:47 -0500 Subject: [PATCH] Use variables in templating instead of params --- handlers/dialog360/handler_test.go | 15 ++-- handlers/meta/whataspp_test.go | 83 ++++++++++----------- handlers/meta/whatsapp/templates.go | 16 ++++- handlers/meta/whatsapp/templates_test.go | 28 +++++--- handlers/whatsapp_legacy/handler.go | 12 +++- handlers/whatsapp_legacy/handler_test.go | 91 +++++++++++++++++------- msg.go | 19 ++--- 7 files changed, 167 insertions(+), 97 deletions(-) diff --git a/handlers/dialog360/handler_test.go b/handlers/dialog360/handler_test.go index 61c22df38..4eca75dea 100644 --- a/handlers/dialog360/handler_test.go +++ b/handlers/dialog360/handler_test.go @@ -420,11 +420,16 @@ var SendTestCasesD3C = []OutgoingTestCase{ ExpectedExtIDs: []string{"157b5e14568e8"}, }, { - Label: "Template Send", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "eng", - MsgTemplating: `{"template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "components": [{"type": "body", "params": [{"type":"text", "value":"Chef"}, {"type": "text" , "value": "tomorrow"}]}], "language": "en_US"}`, + Label: "Template Send", + MsgText: "templated message", + MsgURN: "whatsapp:250788123123", + MsgLocale: "eng", + MsgTemplating: `{ + "template": {"uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3", "name": "revive_issue"}, + "components": [{"type": "body", "name": "body", "variables": {"1": 0, "2": 1}}], + "variables": [{"type":"text", "value":"Chef"}, {"type": "text" , "value": "tomorrow"}], + "language": "en_US" + }`, MockResponses: map[string][]*httpx.MockResponse{ "https://waba-v2.360dialog.io/messages": { httpx.NewMockResponse(200, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), diff --git a/handlers/meta/whataspp_test.go b/handlers/meta/whataspp_test.go index 44e243047..cef934d16 100644 --- a/handlers/meta/whataspp_test.go +++ b/handlers/meta/whataspp_test.go @@ -389,11 +389,21 @@ var whatsappOutgoingTests = []OutgoingTestCase{ ExpectedExtIDs: []string{"157b5e14568e8"}, }, { - Label: "Template Send", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "eng", - MsgTemplating: `{"template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "components": [{"type":"body", "params": [{"type":"text", "value":"Chef"}, {"type": "text" , "value": "tomorrow"}]}], "language": "en_US"}`, + Label: "Template Send", + MsgText: "templated message", + MsgURN: "whatsapp:250788123123", + MsgLocale: "eng", + MsgTemplating: `{ + "template": {"uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3", "name": "revive_issue"}, + "components": [ + {"type": "body", "name": "body", "variables": {"1": 0, "2": 1}} + ], + "variables": [ + {"type": "text", "value": "Chef"}, + {"type": "text" , "value": "tomorrow"} + ], + "language": "en_US" + }`, MockResponses: map[string][]*httpx.MockResponse{ "*/12345_ID/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -405,11 +415,14 @@ var whatsappOutgoingTests = []OutgoingTestCase{ ExpectedExtIDs: []string{"157b5e14568e8"}, }, { - Label: "Template Send, no variables", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "eng", - MsgTemplating: `{"template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "components": [], "variables": [], "language": "en_US"}`, + Label: "Template Send, no variables", + MsgText: "templated message", + MsgURN: "whatsapp:250788123123", + MsgLocale: "eng", + MsgTemplating: `{ + "template": {"uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3", "name": "revive_issue"}, + "language": "en_US" + }`, MockResponses: map[string][]*httpx.MockResponse{ "*/12345_ID/messages": { httpx.NewMockResponse(200, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -425,43 +438,21 @@ var whatsappOutgoingTests = []OutgoingTestCase{ MsgText: "templated message", MsgURN: "whatsapp:250788123123", MsgLocale: "eng", - MsgTemplating: `{"template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, + MsgTemplating: `{ + "template": {"uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3", "name": "revive_issue"}, "components": [ - { - "type": "body", - "name": "body", - "params": [ - { - "type": "text", - "value": "Ryan Lewis" - }, - { - "type": "text", - "value": "niño" - } - ] - }, - { - "type": "button/quick_reply", - "name": "button.0", - "params": [ - { - "type": "text", - "value": "Sip" - } - ] - }, - { - "type": "button/url", - "name": "button.1", - "params": [ - { - "type": "url", - "value": "id00231" - } - ] - } - ], "language": "en_US"}`, + {"type": "body", "name": "body", "variables": {"1": 0, "2": 1}}, + {"type": "button/quick_reply", "name": "button.0", "variables": {"1": 2}}, + {"type": "button/url", "name": "button.1", "variables": {"1": 3}} + ], + "variables": [ + {"type": "text", "value": "Ryan Lewis"}, + {"type": "text", "value": "niño"}, + {"type": "text", "value": "Sip"}, + {"type": "text", "value": "id00231"} + ], + "language": "en_US" + }`, MockResponses: map[string][]*httpx.MockResponse{ "*/12345_ID/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), diff --git a/handlers/meta/whatsapp/templates.go b/handlers/meta/whatsapp/templates.go index ab55e8895..7b889814d 100644 --- a/handlers/meta/whatsapp/templates.go +++ b/handlers/meta/whatsapp/templates.go @@ -1,9 +1,11 @@ package whatsapp import ( + "sort" "strings" "github.com/nyaruka/courier" + "golang.org/x/exp/maps" ) func GetTemplatePayload(templating *courier.Templating) *Template { @@ -14,12 +16,20 @@ func GetTemplatePayload(templating *courier.Templating) *Template { } for _, comp := range templating.Components { + // get the variables used by this component in order of their names 1, 2 etc + compParams := make([]courier.TemplatingVariable, 0, len(comp.Variables)) + varNames := maps.Keys(comp.Variables) + sort.Strings(varNames) + for _, varName := range varNames { + compParams = append(compParams, templating.Variables[comp.Variables[varName]]) + } + var component *Component if comp.Type == "header" { component = &Component{Type: comp.Type} - for _, p := range comp.Params { + for _, p := range compParams { if p.Type == "image" { component.Params = append(component.Params, &Param{Type: p.Type, Image: &struct { Link string "json:\"link,omitempty\"" @@ -39,13 +49,13 @@ func GetTemplatePayload(templating *courier.Templating) *Template { } else if comp.Type == "body" { component = &Component{Type: comp.Type} - for _, p := range comp.Params { + for _, p := range compParams { component.Params = append(component.Params, &Param{Type: p.Type, Text: p.Value}) } } else if strings.HasPrefix(comp.Type, "button/") { component = &Component{Type: "button", Index: strings.TrimPrefix(comp.Name, "button."), SubType: strings.TrimPrefix(comp.Type, "button/"), Params: []*Param{}} - for _, p := range comp.Params { + for _, p := range compParams { if comp.Type == "button/url" { component.Params = append(component.Params, &Param{Type: "text", Text: p.Value}) } else { diff --git a/handlers/meta/whatsapp/templates_test.go b/handlers/meta/whatsapp/templates_test.go index 89a1a835a..c45ccaaf9 100644 --- a/handlers/meta/whatsapp/templates_test.go +++ b/handlers/meta/whatsapp/templates_test.go @@ -19,8 +19,7 @@ func TestGetTemplatePayload(t *testing.T) { templating: `{ "template": {"uuid": "4ed5000f-5c94-4143-9697-b7cbd230a381", "name": "Update"}, "namespace": "12345", - "language": "en", - "params": {} + "language": "en" }`, expected: &whatsapp.Template{ Name: "Update", @@ -36,13 +35,18 @@ func TestGetTemplatePayload(t *testing.T) { { "type": "header", "name": "header", - "params": [{"type": "text", "value": "Welcome"}] + "variables": {"1": 0} }, { "type": "body", "name": "body", - "params": [{"type": "text", "value": "Hello"}, {"type": "text", "value": "Bob"}] + "variables": {"1": 1, "2": 2} } + ], + "variables": [ + {"type": "text", "value": "Welcome"}, + {"type": "text", "value": "Hello"}, + {"type": "text", "value": "Bob"} ] }`, expected: &whatsapp.Template{ @@ -62,18 +66,24 @@ func TestGetTemplatePayload(t *testing.T) { { "type": "button/quick_reply", "name": "button.0", - "params": [{"type": "text", "value": "Yes"}, {"type": "text", "value": "Bob"}] + "variables": {"1": 0, "2": 1} }, { "type": "button/quick_reply", "name": "button.1", - "params" : [{"type": "text", "value": "No"}] + "variables": {"1": 2} }, { "type": "button/url", "name": "button.2", - "params": [{"type": "url", "value": "id0023"}] + "variables": {"1": 3} } + ], + "variables": [ + {"type": "text", "value": "Yes"}, + {"type": "text", "value": "Bob"}, + {"type": "text", "value": "No"}, + {"type": "text", "value": "id0023"} ] }`, expected: &whatsapp.Template{ @@ -88,13 +98,13 @@ func TestGetTemplatePayload(t *testing.T) { }, } - for _, tc := range tcs { + for i, tc := range tcs { templating := &courier.Templating{} jsonx.MustUnmarshal([]byte(tc.templating), templating) msg := test.NewMockMsg(1, "87995844-2017-4ba0-bc73-f3da75b32f9b", nil, "tel:+1234567890", "hi", nil).WithTemplating(templating) actual := whatsapp.GetTemplatePayload(msg.Templating()) - assert.Equal(t, tc.expected, actual) + assert.Equal(t, tc.expected, actual, "%d: template payload mismatch", i) } } diff --git a/handlers/whatsapp_legacy/handler.go b/handlers/whatsapp_legacy/handler.go index e1bbcc657..18d8f836e 100644 --- a/handlers/whatsapp_legacy/handler.go +++ b/handlers/whatsapp_legacy/handler.go @@ -8,6 +8,7 @@ import ( "log/slog" "net/http" "net/url" + "sort" "strconv" "strings" "time" @@ -24,6 +25,7 @@ import ( "github.com/nyaruka/redisx" "github.com/patrickmn/go-cache" "github.com/pkg/errors" + "golang.org/x/exp/maps" "golang.org/x/mod/semver" ) @@ -716,9 +718,17 @@ func buildPayloads(msg courier.MsgOut, h *handler, clog *courier.ChannelLog) ([] payload.Template.Language.Code = langCode for _, comp := range msg.Templating().Components { + // get the variables used by this component in order of their names 1, 2 etc + compParams := make([]courier.TemplatingVariable, 0, len(comp.Variables)) + varNames := maps.Keys(comp.Variables) + sort.Strings(varNames) + for _, varName := range varNames { + compParams = append(compParams, msg.Templating().Variables[comp.Variables[varName]]) + } + if comp.Type == "body" { component := &Component{Type: "body"} - for _, p := range comp.Params { + for _, p := range compParams { component.Parameters = append(component.Parameters, Param{Type: p.Type, Text: p.Value}) } payload.Template.Components = append(payload.Template.Components, *component) diff --git a/handlers/whatsapp_legacy/handler_test.go b/handlers/whatsapp_legacy/handler_test.go index 9775ecf4d..a3f00529a 100644 --- a/handlers/whatsapp_legacy/handler_test.go +++ b/handlers/whatsapp_legacy/handler_test.go @@ -761,11 +761,21 @@ var defaultSendTestCases = []OutgoingTestCase{ ExpectedExtIDs: []string{"157b5e14568e8"}, }, { - Label: "Template Send", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "eng", - MsgTemplating: `{"template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "components": [{"type":"body", "params": [{"type":"text", "value":"Chef"}, {"type": "text" , "value": "tomorrow"}]}]}`, + Label: "Template Send", + MsgText: "templated message", + MsgURN: "whatsapp:250788123123", + MsgLocale: "eng", + MsgTemplating: `{ + "template": {"uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3", "name": "revive_issue"}, + "components": [ + {"type": "body", "name": "body", "variables": {"1": 0, "2": 1}} + ], + "variables": [ + {"type": "text", "value": "Chef"}, + {"type": "text" , "value": "tomorrow"} + ], + "language": "en_US" + }`, MockResponses: map[string][]*httpx.MockResponse{ "*/v1/messages": { httpx.NewMockResponse(200, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -779,11 +789,14 @@ var defaultSendTestCases = []OutgoingTestCase{ ExpectedExtIDs: []string{"157b5e14568e8"}, }, { - Label: "Template Send no variables", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "eng", - MsgTemplating: `{"template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "components": []}`, + Label: "Template Send no variables", + MsgText: "templated message", + MsgURN: "whatsapp:250788123123", + MsgLocale: "eng", + MsgTemplating: `{ + "template": {"uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3", "name": "revive_issue"}, + "language": "en_US" + }`, MockResponses: map[string][]*httpx.MockResponse{ "*/v1/messages": { httpx.NewMockResponse(200, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -797,11 +810,20 @@ var defaultSendTestCases = []OutgoingTestCase{ ExpectedExtIDs: []string{"157b5e14568e8"}, }, { - Label: "Template Country Language", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "eng-US", - MsgTemplating: `{"template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "components": [{"type":"body", "params": [{"type":"text", "value":"Chef"}, {"type": "text" , "value": "tomorrow"}]}]}`, + Label: "Template no language", + MsgText: "templated message", + MsgURN: "whatsapp:250788123123", + MsgLocale: "eng-US", + MsgTemplating: `{ + "template": {"uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3", "name": "revive_issue"}, + "components": [ + {"type": "body", "name": "body", "variables": {"1": 0, "2": 1}} + ], + "variables": [ + {"type": "text", "value": "Chef"}, + {"type": "text", "value": "tomorrow"} + ] + }`, MockResponses: map[string][]*httpx.MockResponse{ "*/v1/messages": { httpx.NewMockResponse(200, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -815,11 +837,21 @@ var defaultSendTestCases = []OutgoingTestCase{ ExpectedExtIDs: []string{"157b5e14568e8"}, }, { - Label: "Template Namespace", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "eng-US", - MsgTemplating: `{"template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "namespace": "wa_template_namespace", "components": [{"type":"body", "params": [{"type":"text", "value":"Chef"}, {"type": "text" , "value": "tomorrow"}]}]}`, + Label: "Template Namespace", + MsgText: "templated message", + MsgURN: "whatsapp:250788123123", + MsgLocale: "eng-US", + MsgTemplating: `{ + "template": {"uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3", "name": "revive_issue"}, + "namespace": "wa_template_namespace", + "components": [ + {"type": "body", "name": "body", "variables": {"1": 0, "2": 1}} + ], + "variables": [ + {"type": "text", "value": "Chef"}, + {"type": "text", "value": "tomorrow"} + ] + }`, MockResponses: map[string][]*httpx.MockResponse{ "*/v1/messages": { httpx.NewMockResponse(200, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -833,11 +865,20 @@ var defaultSendTestCases = []OutgoingTestCase{ ExpectedExtIDs: []string{"157b5e14568e8"}, }, { - Label: "Template Invalid Language", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "bnt", - MsgTemplating: `{"template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "components": [{"type":"body", "params": [{"type":"text", "value":"Chef"}, {"type": "text" , "value": "tomorrow"}]}]}`, + Label: "Template Invalid Language", + MsgText: "templated message", + MsgURN: "whatsapp:250788123123", + MsgLocale: "bnt", + MsgTemplating: `{ + "template": {"uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3", "name": "revive_issue"}, + "components": [ + {"type": "body", "name": "body", "variables": {"1": 0, "2": 1}} + ], + "variables": [ + {"type": "text", "value": "Chef"}, + {"type": "text", "value": "tomorrow"} + ] + }`, MockResponses: map[string][]*httpx.MockResponse{ "*/v1/messages": { httpx.NewMockResponse(200, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), diff --git a/msg.go b/msg.go index 010793665..6d6424ece 100644 --- a/msg.go +++ b/msg.go @@ -53,6 +53,11 @@ const ( MsgOriginChat MsgOrigin = "chat" ) +type TemplatingVariable struct { + Type string `json:"type"` + Value string `json:"value"` +} + type Templating struct { Template struct { Name string `json:"name" validate:"required"` @@ -60,15 +65,13 @@ type Templating struct { } `json:"template" validate:"required,dive"` Namespace string `json:"namespace"` Components []struct { - Type string `json:"type"` - Name string `json:"name"` - Params []struct { - Type string `json:"type"` - Value string `json:"value"` - } `json:"params"` + Type string `json:"type"` + Name string `json:"name"` + Variables map[string]int `json:"variables"` } `json:"components"` - Language string `json:"language"` - ExternalID string `json:"external_id"` + Variables []TemplatingVariable `json:"variables"` + Language string `json:"language"` + ExternalID string `json:"external_id"` } //-----------------------------------------------------------------------------