From 83fbbd03d4fc8710a8b83c06f30297151f664bb8 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 25 Jan 2024 14:44:00 -0500 Subject: [PATCH] Include template params in template (flow def expressions) inspection --- flows/actions/send_msg.go | 18 ++++++++++--- flows/actions/testdata/send_msg.json | 40 +++++++++++++--------------- flows/inspect/templates.go | 26 +++++++++++------- flows/interfaces.go | 4 +++ utils/misc.go | 2 +- 5 files changed, 55 insertions(+), 35 deletions(-) diff --git a/flows/actions/send_msg.go b/flows/actions/send_msg.go index ce46f487e..2dc06c0bc 100644 --- a/flows/actions/send_msg.go +++ b/flows/actions/send_msg.go @@ -9,6 +9,7 @@ import ( "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/events" + "github.com/nyaruka/goflow/utils" ) func init() { @@ -57,8 +58,19 @@ type TemplateParams struct { Values map[string][]string } -// LocalizationUUID gets the UUID which identifies this object for localization -func (p *TemplateParams) LocalizationUUID() uuids.UUID { return uuids.UUID(p.UUID) } +func (p *TemplateParams) EnumerateTemplates(localization flows.Localization, include func(i18n.Language, string)) { + for _, comp := range utils.SortedKeys(p.Values) { + for _, v := range p.Values[comp] { + include(i18n.NilLanguage, v) + } + for _, lang := range localization.Languages() { + lvals := localization.GetItemTranslation(lang, p.UUID, comp) + for _, v := range lvals { + include(lang, v) + } + } + } +} func (p *TemplateParams) MarshalJSON() ([]byte, error) { if p == nil { @@ -100,7 +112,7 @@ func (p *TemplateParams) UnmarshalJSON(d []byte) error { type Templating struct { UUID uuids.UUID `json:"uuid" validate:"required,uuid4"` Template *assets.TemplateReference `json:"template" validate:"required"` - Variables []string `json:"variables" engine:"localized,evaluated"` + Variables []string `json:"variables,omitempty" engine:"localized,evaluated"` Params *TemplateParams `json:"params,omitempty"` } diff --git a/flows/actions/testdata/send_msg.json b/flows/actions/testdata/send_msg.json index 965f9c9f5..78cafdd3e 100644 --- a/flows/actions/testdata/send_msg.json +++ b/flows/actions/testdata/send_msg.json @@ -423,10 +423,6 @@ "uuid": "5722e1fd-fe32-4e74-ac78-3cf41a6adb7e", "name": "affirmation" }, - "variables": [ - "@contact.name", - "boy" - ], "params": { "body": [ "@contact.name", @@ -480,9 +476,7 @@ "boy" ], "localizables": [ - "Hi Ryan Lewis, who's a good boy?", - "@contact.name", - "boy" + "Hi Ryan Lewis, who's a good boy?" ], "inspection": { "dependencies": [ @@ -510,10 +504,6 @@ "uuid": "5722e1fd-fe32-4e74-ac78-3cf41a6adb7e", "name": "affirmation" }, - "variables": [ - "@contact.name", - "boy" - ], "params": { "body": [ "@contact.name", @@ -578,14 +568,14 @@ "templates": [ "Hi Ryan Lewis, who's a good boy?", "@contact.name", + "niño", + "@contact.name", "boy", "@contact.name", "niño" ], "localizables": [ - "Hi Ryan Lewis, who's a good boy?", - "@contact.name", - "boy" + "Hi Ryan Lewis, who's a good boy?" ], "inspection": { "dependencies": [ @@ -602,7 +592,7 @@ } }, { - "description": "Msg with template but no variables", + "description": "Msg with template but no params or variables", "action": { "type": "send_msg", "uuid": "ad154980-7bf7-4ab8-8728-545fd6378912", @@ -612,8 +602,7 @@ "template": { "uuid": "2edc8dfd-aef0-41cf-a900-8a71bdb00900", "name": "wakeup" - }, - "variables": [] + } } }, "events": [ @@ -671,8 +660,7 @@ "template": { "uuid": "2edc8dfd-aef0-41cf-a900-8a71bdb00900", "name": "wakeup" - }, - "variables": null + } } }, "localization": { @@ -808,7 +796,7 @@ } }, { - "description": "Use template translation with component params", + "description": "Use template translation with non body component params", "action": { "type": "send_msg", "uuid": "ad154980-7bf7-4ab8-8728-545fd6378912", @@ -826,7 +814,6 @@ "uuid": "ce00c80e-991a-4c03-b373-3273c23ee042", "name": "gender_update" }, - "variables": null, "params": { "body": [ "@contact.name", @@ -935,7 +922,16 @@ "Yes", "No", "Si", - "No" + "No", + "@contact.name", + "boy", + "@contact.name", + "niño", + "Yeah", + "Sip", + "Nope", + "http://templates.com/red.jpg", + "http://templates.com/rojo.jpg" ], "localizables": [ "Hey Ryan Lewis, your gender is saved as boy.", diff --git a/flows/inspect/templates.go b/flows/inspect/templates.go index d6df275f0..d51f60421 100644 --- a/flows/inspect/templates.go +++ b/flows/inspect/templates.go @@ -17,18 +17,26 @@ func Templates(s any, localization flows.Localization, include func(i18n.Languag } func templateValues(v reflect.Value, localization flows.Localization, include func(i18n.Language, string)) { - walk(v, nil, func(sv reflect.Value, fv reflect.Value, ef *EngineField) { - if ef.Evaluated { - extractTemplates(fv, i18n.NilLanguage, include) + walk(v, + func(v reflect.Value) { + te, ok := v.Interface().(flows.TemplateEnumerator) + if ok { + te.EnumerateTemplates(localization, include) + } + }, + func(sv reflect.Value, fv reflect.Value, ef *EngineField) { + if ef.Evaluated { + extractTemplates(fv, i18n.NilLanguage, include) - // if this field is also localized, each translation is a template and needs to be included - if ef.Localized && localization != nil { - localizable := sv.Interface().(flows.Localizable) + // if this field is also localized, each translation is a template and needs to be included + if ef.Localized && localization != nil { + localizable := sv.Interface().(flows.Localizable) - Translations(localization, localizable.LocalizationUUID(), ef.JSONName, include) + Translations(localization, localizable.LocalizationUUID(), ef.JSONName, include) + } } - } - }) + }, + ) } func Translations(localization flows.Localization, itemUUID uuids.UUID, property string, include func(i18n.Language, string)) { diff --git a/flows/interfaces.go b/flows/interfaces.go index 9a32aeae0..b50881295 100644 --- a/flows/interfaces.go +++ b/flows/interfaces.go @@ -122,6 +122,10 @@ type Localizable interface { LocalizationUUID() uuids.UUID } +type TemplateEnumerator interface { + EnumerateTemplates(Localization, func(i18n.Language, string)) +} + // Flow describes the ordered logic of actions and routers type Flow interface { Contextable diff --git a/utils/misc.go b/utils/misc.go index 736b28cfb..28afeb98a 100644 --- a/utils/misc.go +++ b/utils/misc.go @@ -35,7 +35,7 @@ func Set[K constraints.Ordered](s []K) map[K]bool { } // SortedKeys returns the keys of a set in lexical order -func SortedKeys[K constraints.Ordered](m map[K]bool) []K { +func SortedKeys[K constraints.Ordered, V any](m map[K]V) []K { keys := maps.Keys(m) slices.Sort(keys) return keys