diff --git a/flows/actions/send_msg.go b/flows/actions/send_msg.go index d6b9a44c0..fe53115aa 100644 --- a/flows/actions/send_msg.go +++ b/flows/actions/send_msg.go @@ -175,7 +175,7 @@ func (a *SendMsgAction) getTemplateMsg(run flows.Run, urn urns.URN, channelRef * } compTemplating := &flows.TemplatingComponent{Type: comp.Type(), Params: params} - previewContent, _ := compTemplating.Preview(comp) + previewContent := compTemplating.Preview(comp) if previewContent != "" { if comp.Type() == "header" || comp.Type() == "body" || comp.Type() == "footer" { diff --git a/flows/actions/testdata/_assets.json b/flows/actions/testdata/_assets.json index 8f14d2115..b87f56907 100644 --- a/flows/actions/testdata/_assets.json +++ b/flows/actions/testdata/_assets.json @@ -259,9 +259,9 @@ "locale": "eng-US", "components": [ { - "content": "Hi {{1}}, who's an excellent {{2}}?", "type": "body", "name": "body", + "content": "Hi {{1}}, who's an excellent {{2}}?", "params": [ { "type": "text" @@ -281,9 +281,9 @@ "locale": "spa", "components": [ { - "content": "Hola {{1}}, quien es un {{2}} excelente?", "type": "body", "name": "body", + "content": "Hola {{1}}, quien es un {{2}} excelente?", "params": [ { "type": "text" @@ -309,9 +309,9 @@ "locale": "eng", "components": [ { - "content": "Hi there, it's time to get up!", "type": "body", "name": "body", + "content": "Hi there, it's time to get up!", "params": [] } ] @@ -330,9 +330,9 @@ "locale": "eng-US", "components": [ { - "content": "", "type": "header", "name": "header", + "content": "", "params": [ { "type": "image" @@ -340,9 +340,9 @@ ] }, { - "content": "Hey {{1}}, your gender is saved as {{2}}.", "type": "body", "name": "body", + "content": "Hey {{1}}, your gender is saved as {{2}}.", "params": [ { "type": "text" @@ -353,9 +353,9 @@ ] }, { - "content": "{{1}}", "type": "button/quick_reply", "name": "button.1", + "content": "{{1}}", "params": [ { "type": "text" @@ -372,9 +372,9 @@ "locale": "spa", "components": [ { - "content": "", "type": "header", "name": "header", + "content": "", "params": [ { "type": "image" @@ -382,9 +382,9 @@ ] }, { - "content": "Hola, {{1}}, tu género está guardado como {{2}}.", "type": "body", "name": "body", + "content": "Hola, {{1}}, tu género está guardado como {{2}}.", "params": [ { "type": "text" @@ -395,9 +395,9 @@ ] }, { - "content": "{{1}}", "type": "button/quick_reply", "name": "button.0", + "content": "{{1}}", "params": [ { "type": "text" @@ -405,9 +405,9 @@ ] }, { - "content": "No", "type": "button/quick_reply", "name": "button.1", + "content": "No", "params": [] } ] diff --git a/flows/msg.go b/flows/msg.go index 465ddcbf0..330f94929 100644 --- a/flows/msg.go +++ b/flows/msg.go @@ -7,7 +7,6 @@ import ( "fmt" "regexp" "slices" - "strconv" "strings" "github.com/go-playground/validator/v10" @@ -183,42 +182,17 @@ type TemplatingComponent struct { var templateVariableRegex = regexp.MustCompile(`{{(\d+)}}`) -func getTemplateVariableCount(s string) int { - count := 0 - for _, m := range templateVariableRegex.FindAllStringSubmatch(s, -1) { - if v, _ := strconv.Atoi(m[1]); v > count { - count = v - } - } - return count -} - -// Preview returns the content and display for given template component using these templating params -func (tc *TemplatingComponent) Preview(c assets.TemplateComponent) (string, string) { +// Preview returns the content of the given template component rendered using these templating params +func (tc *TemplatingComponent) Preview(c assets.TemplateComponent) string { content := c.Content() - display := c.Display() - numContentParams := getTemplateVariableCount(content) - numDisplayParams := getTemplateVariableCount(display) - - // replace {{?}} placeholders in component content - for i := 0; i < numContentParams; i++ { - value := "" - if i < len(tc.Params) { - value = tc.Params[i].Value - } - content = strings.ReplaceAll(content, fmt.Sprintf("{{%d}}", i+1), value) - } - // replace {{?}} placeholders in component display using any remaining param values - for i := 0; i < numDisplayParams; i++ { - value := "" - if (numContentParams + i) < len(tc.Params) { - value = tc.Params[numContentParams+i].Value - } - display = strings.ReplaceAll(display, fmt.Sprintf("{{%d}}", i+1), value) + for i, p := range tc.Params { + content = strings.ReplaceAll(content, fmt.Sprintf("{{%d}}", i+1), p.Value) } - return content, display + content = templateVariableRegex.ReplaceAllString(content, "") + + return content } // MsgTemplating represents any substituted message template that should be applied when sending this message diff --git a/flows/msg_test.go b/flows/msg_test.go index 873b974b0..b088c8423 100644 --- a/flows/msg_test.go +++ b/flows/msg_test.go @@ -175,6 +175,10 @@ func TestMsgTemplating(t *testing.T) { require.NoError(t, err) test.AssertEqualJSON(t, []byte(`{ + "template":{ + "name":"Affirmation", + "uuid":"61602f3e-f603-4c70-8a8f-c477505bf4bf" + }, "namespace":"0162a7f4_dfe4_4c96_be07_854d5dba3b2b", "params": { "body": [ @@ -188,68 +192,59 @@ func TestMsgTemplating(t *testing.T) { } ] }, - "components":[{ - "type": "body", - "params":[ + "components":[ { - "type": "text", - "value": "Ryan Lewis" - }, - { - "type": "text", - "value": "boy" + "type": "body", + "params":[ + { + "type": "text", + "value": "Ryan Lewis" + }, + { + "type": "text", + "value": "boy" + } + ] } - ] - }], - "template":{ - "name":"Affirmation", - "uuid":"61602f3e-f603-4c70-8a8f-c477505bf4bf" - } + ] }`), marshaled, "JSON mismatch") } func TestTemplatingComponentPreview(t *testing.T) { tcs := []struct { - templating *flows.TemplatingComponent - component assets.TemplateComponent - expectedContent string - expectedDisplay string + templating *flows.TemplatingComponent + component assets.TemplateComponent + expected string }{ { // 0: no params - component: static.NewTemplateComponent("body", "body", "Hello", "", []*static.TemplateParam{}), - templating: &flows.TemplatingComponent{Type: "body", Params: []flows.TemplatingParam{}}, - expectedContent: "Hello", - expectedDisplay: "", + component: static.NewTemplateComponent("body", "body", "Hello", "", []*static.TemplateParam{}), + templating: &flows.TemplatingComponent{Type: "body", Params: []flows.TemplatingParam{}}, + expected: "Hello", }, { // 1: two params on component and two params in templating - component: static.NewTemplateComponent("body", "body", "Hello {{1}} {{2}}", "", []*static.TemplateParam{{Type_: "text"}, {Type_: "text"}}), - templating: &flows.TemplatingComponent{Type: "body", Params: []flows.TemplatingParam{{Type: "text", Value: "Dr"}, {Type: "text", Value: "Bob"}}}, - expectedContent: "Hello Dr Bob", - expectedDisplay: "", + component: static.NewTemplateComponent("body", "body", "Hello {{1}} {{2}}", "", []*static.TemplateParam{{Type_: "text"}, {Type_: "text"}}), + templating: &flows.TemplatingComponent{Type: "body", Params: []flows.TemplatingParam{{Type: "text", Value: "Dr"}, {Type: "text", Value: "Bob"}}}, + expected: "Hello Dr Bob", }, { // 2: one less param in templating than on component - component: static.NewTemplateComponent("body", "body", "Hello {{1}} {{2}}", "", []*static.TemplateParam{{Type_: "text"}, {Type_: "text"}}), - templating: &flows.TemplatingComponent{Type: "body", Params: []flows.TemplatingParam{{Type: "text", Value: "Dr"}}}, - expectedContent: "Hello Dr ", - expectedDisplay: "", + component: static.NewTemplateComponent("body", "body", "Hello {{1}} {{2}}", "", []*static.TemplateParam{{Type_: "text"}, {Type_: "text"}}), + templating: &flows.TemplatingComponent{Type: "body", Params: []flows.TemplatingParam{{Type: "text", Value: "Dr"}}}, + expected: "Hello Dr ", }, { // 3 - component: static.NewTemplateComponent("button/quick_reply", "button.0", "{{1}}", "", []*static.TemplateParam{{Type_: "text"}}), - templating: &flows.TemplatingComponent{Type: "button/quick_reply", Params: []flows.TemplatingParam{{Type: "text", Value: "Yes"}}}, - expectedContent: "Yes", - expectedDisplay: "", + component: static.NewTemplateComponent("button/quick_reply", "button.0", "{{1}}", "", []*static.TemplateParam{{Type_: "text"}}), + templating: &flows.TemplatingComponent{Type: "button/quick_reply", Params: []flows.TemplatingParam{{Type: "text", Value: "Yes"}}}, + expected: "Yes", }, { // 4: one param for content, one for display - component: static.NewTemplateComponent("button/url", "button.0", "example.com?p={{1}}", "{{1}}", []*static.TemplateParam{{Type_: "text"}}), - templating: &flows.TemplatingComponent{Type: "button/url", Params: []flows.TemplatingParam{{Type: "text", Value: "123"}, {Type: "text", Value: "Go"}}}, - expectedContent: "example.com?p=123", - expectedDisplay: "Go", + component: static.NewTemplateComponent("button/url", "button.0", "example.com?p={{1}}", "{{1}}", []*static.TemplateParam{{Type_: "text"}}), + templating: &flows.TemplatingComponent{Type: "button/url", Params: []flows.TemplatingParam{{Type: "text", Value: "123"}, {Type: "text", Value: "Go"}}}, + expected: "example.com?p=123", }, } for i, tc := range tcs { - actualContent, actualDisplay := tc.templating.Preview(tc.component) - assert.Equal(t, tc.expectedContent, actualContent, "content mismatch in test %d", i) - assert.Equal(t, tc.expectedDisplay, actualDisplay, "display mismatch in test %d", i) + actualContent := tc.templating.Preview(tc.component) + assert.Equal(t, tc.expected, actualContent, "content mismatch in test %d", i) } }