diff --git a/flows/actions/set_run_result.go b/flows/actions/set_run_result.go index 4b16c4bcb..0f647e06c 100644 --- a/flows/actions/set_run_result.go +++ b/flows/actions/set_run_result.go @@ -32,9 +32,9 @@ type SetRunResultAction struct { baseAction universalAction - Name string `json:"name" validate:"required,max=128"` + Name string `json:"name" validate:"required,max=64"` Value string `json:"value" engine:"evaluated"` - Category string `json:"category,omitempty" engine:"localized" validate:"max=128"` + Category string `json:"category,omitempty" engine:"localized" validate:"max=36"` } // NewSetRunResult creates a new set run result action diff --git a/flows/actions/testdata/set_run_result.json b/flows/actions/testdata/set_run_result.json index 61004a76b..4a422465b 100644 --- a/flows/actions/testdata/set_run_result.json +++ b/flows/actions/testdata/set_run_result.json @@ -10,17 +10,6 @@ }, "read_error": "field 'name' is required" }, - { - "description": "Read fails when category is too long", - "action": { - "uuid": "ad154980-7bf7-4ab8-8728-545fd6378912", - "type": "set_run_result", - "name": "Foo", - "value": "bar", - "category": "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" - }, - "read_error": "field 'category' must be less than or equal to 128" - }, { "description": "Error event and action skipped if value contains expression error", "action": { diff --git a/flows/definition/flow.go b/flows/definition/flow.go index dd2319a53..06e6dd25f 100644 --- a/flows/definition/flow.go +++ b/flows/definition/flow.go @@ -21,7 +21,7 @@ import ( ) // CurrentSpecVersion is the flow spec version supported by this library -var CurrentSpecVersion = semver.MustParse("13.5.0") +var CurrentSpecVersion = semver.MustParse("13.6.0") // IsVersionSupported checks the given version is supported func IsVersionSupported(v *semver.Version) bool { diff --git a/flows/definition/migrations/13_x.go b/flows/definition/migrations/13_x.go index 300d25b26..de76caded 100644 --- a/flows/definition/migrations/13_x.go +++ b/flows/definition/migrations/13_x.go @@ -1,13 +1,17 @@ package migrations import ( + "strings" + "github.com/Masterminds/semver" "github.com/nyaruka/gocommon/i18n" + "github.com/nyaruka/gocommon/stringsx" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/excellent/refactor" ) func init() { + registerMigration(semver.MustParse("13.6.0"), Migrate13_6) registerMigration(semver.MustParse("13.5.0"), Migrate13_5) registerMigration(semver.MustParse("13.4.0"), Migrate13_4) registerMigration(semver.MustParse("13.3.0"), Migrate13_3) @@ -15,6 +19,56 @@ func init() { registerMigration(semver.MustParse("13.1.0"), Migrate13_1) } +// Migrate13_6 ensures that names of results and categories respect definition limits. +// +// @version 13_6 "13.6" +func Migrate13_6(f Flow, cfg *Config) (Flow, error) { + const maxResultName = 64 + const maxCategoryName = 36 + + truncate := func(s string, max int) string { + return strings.TrimSpace(stringsx.Truncate(s, max)) // so we don't leave trailing spaces + } + + for _, node := range f.Nodes() { + for _, action := range node.Actions() { + if action.Type() == "set_run_result" { + name, _ := action["name"].(string) + category, _ := action["category"].(string) + + if len(name) > maxResultName { + action["name"] = truncate(name, maxResultName) + } + if len(category) > maxCategoryName { + action["category"] = truncate(category, maxCategoryName) + } + } + } + + router := node.Router() + if router != nil { + resultName, _ := router["result_name"].(string) + categories, _ := router["categories"].([]any) + + if len(resultName) > maxResultName { + router["result_name"] = truncate(resultName, maxResultName) + } + + for _, cat := range categories { + category, _ := cat.(map[string]any) + if category != nil { + name, _ := category["name"].(string) + + if len(name) > maxCategoryName { + category["name"] = truncate(name, maxCategoryName) + } + } + } + } + } + return f, nil +} + // Migrate13_5 converts the `templating` object in [action:send_msg] actions to use a merged list of variables. // // @version 13_5 "13.5" diff --git a/flows/definition/migrations/specdata/templates.json b/flows/definition/migrations/specdata/templates.json index 0a1ba083b..173dbacf2 100644 --- a/flows/definition/migrations/specdata/templates.json +++ b/flows/definition/migrations/specdata/templates.json @@ -1,4 +1,93 @@ { + "13.6.0": { + "actions": { + "add_contact_groups": [ + ".groups[*].name_match" + ], + "add_contact_urn": [ + ".path" + ], + "add_input_labels": [ + ".labels[*].name_match" + ], + "call_classifier": [ + ".input" + ], + "call_resthook": [], + "call_webhook": [ + ".body", + ".headers.*", + ".url" + ], + "enter_flow": [], + "open_ticket": [ + ".assignee.email_match", + ".body" + ], + "play_audio": [ + ".audio_url" + ], + "remove_contact_groups": [ + ".groups[*].name_match" + ], + "request_optin": [], + "say_msg": [ + ".text" + ], + "send_broadcast": [ + ".attachments[*]", + ".contact_query", + ".groups[*].name_match", + ".legacy_vars[*]", + ".quick_replies[*]", + ".text" + ], + "send_email": [ + ".addresses[*]", + ".body", + ".subject" + ], + "send_msg": [ + ".attachments[*]", + ".quick_replies[*]", + ".template_variables[*]", + ".text" + ], + "set_contact_channel": [], + "set_contact_field": [ + ".value" + ], + "set_contact_language": [ + ".language" + ], + "set_contact_name": [ + ".name" + ], + "set_contact_status": [], + "set_contact_timezone": [ + ".timezone" + ], + "set_run_result": [ + ".value" + ], + "start_session": [ + ".contact_query", + ".groups[*].name_match", + ".legacy_vars[*]" + ], + "transfer_airtime": [] + }, + "routers": { + "random": [ + ".operand", + ".cases[*].arguments[*]" + ], + "switch": [ + ".operand", + ".cases[*].arguments[*]" + ] + } + }, "13.5.0": { "actions": { "add_contact_groups": [ diff --git a/flows/definition/migrations/testdata/migrations/13.6.0.json b/flows/definition/migrations/testdata/migrations/13.6.0.json new file mode 100644 index 000000000..ee8545269 --- /dev/null +++ b/flows/definition/migrations/testdata/migrations/13.6.0.json @@ -0,0 +1,302 @@ +[ + { + "description": "set_run_result action with name and category that exceed limit", + "original": { + "uuid": "25a2d8b2-ae7c-4fed-964a-506fb8c3f0c0", + "name": "Test Flow", + "spec_version": "13.5.0", + "language": "eng", + "type": "messaging", + "nodes": [ + { + "uuid": "32bc60ad-5c86-465e-a6b8-049c44ecce49", + "actions": [ + { + "uuid": "9d9290a7-3713-4c22-8821-4af0a64c0821", + "type": "set_run_result", + "name": "My result name That is too long for goflow why do people do this to me", + "value": "Values can be long that is fine with me 12345678901234567890123456789012345678901234567890", + "category": "Once again this too long why people why just use something short" + } + ], + "exits": [ + { + "uuid": "2d481ce6-efcf-4898-a825-f76208e32f2a" + } + ] + } + ] + }, + "migrated": { + "uuid": "25a2d8b2-ae7c-4fed-964a-506fb8c3f0c0", + "name": "Test Flow", + "spec_version": "13.6.0", + "language": "eng", + "type": "messaging", + "nodes": [ + { + "uuid": "32bc60ad-5c86-465e-a6b8-049c44ecce49", + "actions": [ + { + "uuid": "9d9290a7-3713-4c22-8821-4af0a64c0821", + "type": "set_run_result", + "name": "My result name That is too long for goflow why do people do this", + "value": "Values can be long that is fine with me 12345678901234567890123456789012345678901234567890", + "category": "Once again this too long why people" + } + ], + "exits": [ + { + "uuid": "2d481ce6-efcf-4898-a825-f76208e32f2a" + } + ] + } + ] + } + }, + { + "description": "set_run_result action with no value or category (valid for clearing)", + "original": { + "uuid": "25a2d8b2-ae7c-4fed-964a-506fb8c3f0c0", + "name": "Test Flow", + "spec_version": "13.5.0", + "language": "eng", + "type": "messaging", + "nodes": [ + { + "uuid": "32bc60ad-5c86-465e-a6b8-049c44ecce49", + "actions": [ + { + "uuid": "9d9290a7-3713-4c22-8821-4af0a64c0821", + "type": "set_run_result", + "name": "My Result" + } + ], + "exits": [ + { + "uuid": "2d481ce6-efcf-4898-a825-f76208e32f2a" + } + ] + } + ] + }, + "migrated": { + "uuid": "25a2d8b2-ae7c-4fed-964a-506fb8c3f0c0", + "name": "Test Flow", + "spec_version": "13.6.0", + "language": "eng", + "type": "messaging", + "nodes": [ + { + "uuid": "32bc60ad-5c86-465e-a6b8-049c44ecce49", + "actions": [ + { + "uuid": "9d9290a7-3713-4c22-8821-4af0a64c0821", + "type": "set_run_result", + "name": "My Result" + } + ], + "exits": [ + { + "uuid": "2d481ce6-efcf-4898-a825-f76208e32f2a" + } + ] + } + ] + } + }, + { + "description": "switch router with result name and category that exceed limit", + "original": { + "uuid": "25a2d8b2-ae7c-4fed-964a-506fb8c3f0c0", + "name": "Test Flow", + "spec_version": "13.5.0", + "language": "eng", + "type": "messaging", + "nodes": [ + { + "uuid": "3dcccbb4-d29c-41dd-a01f-16d814c9ab82", + "router": { + "type": "switch", + "wait": { + "type": "msg" + }, + "categories": [ + { + "uuid": "37d8813f-1402-4ad2-9cc2-e9054a96525b", + "name": "Toooooooooooooooooooooooooooooo longgggggggggggggggggggggggg", + "exit_uuid": "fc2fcd23-7c4a-44bd-a8c6-6c88e6ed09f8" + }, + { + "uuid": "0680b01f-ba0b-48f4-a688-d2f963130126", + "name": "Other", + "exit_uuid": "43accf99-4940-44f7-926b-a8b35d9403d6" + } + ], + "default_category_uuid": "0680b01f-ba0b-48f4-a688-d2f963130126", + "result_name": "Names should be catchy and short pleaseeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "operand": "@input.text", + "cases": [ + { + "uuid": "5d6abc80-39e7-4620-9988-a2447bffe526", + "type": "has_text", + "category_uuid": "37d8813f-1402-4ad2-9cc2-e9054a96525b" + } + ] + }, + "exits": [ + { + "uuid": "fc2fcd23-7c4a-44bd-a8c6-6c88e6ed09f8" + }, + { + "uuid": "43accf99-4940-44f7-926b-a8b35d9403d6" + } + ] + } + ] + }, + "migrated": { + "uuid": "25a2d8b2-ae7c-4fed-964a-506fb8c3f0c0", + "name": "Test Flow", + "spec_version": "13.6.0", + "language": "eng", + "type": "messaging", + "nodes": [ + { + "uuid": "3dcccbb4-d29c-41dd-a01f-16d814c9ab82", + "router": { + "type": "switch", + "wait": { + "type": "msg" + }, + "categories": [ + { + "uuid": "37d8813f-1402-4ad2-9cc2-e9054a96525b", + "name": "Toooooooooooooooooooooooooooooo long", + "exit_uuid": "fc2fcd23-7c4a-44bd-a8c6-6c88e6ed09f8" + }, + { + "uuid": "0680b01f-ba0b-48f4-a688-d2f963130126", + "name": "Other", + "exit_uuid": "43accf99-4940-44f7-926b-a8b35d9403d6" + } + ], + "default_category_uuid": "0680b01f-ba0b-48f4-a688-d2f963130126", + "result_name": "Names should be catchy and short pleaseeeeeeeeeeeeeeeeeeeeeeeeee", + "operand": "@input.text", + "cases": [ + { + "uuid": "5d6abc80-39e7-4620-9988-a2447bffe526", + "type": "has_text", + "category_uuid": "37d8813f-1402-4ad2-9cc2-e9054a96525b" + } + ] + }, + "exits": [ + { + "uuid": "fc2fcd23-7c4a-44bd-a8c6-6c88e6ed09f8" + }, + { + "uuid": "43accf99-4940-44f7-926b-a8b35d9403d6" + } + ] + } + ] + } + }, + { + "description": "switch router with no result name and missing category name", + "original": { + "uuid": "25a2d8b2-ae7c-4fed-964a-506fb8c3f0c0", + "name": "Test Flow", + "spec_version": "13.5.0", + "language": "eng", + "type": "messaging", + "nodes": [ + { + "uuid": "3dcccbb4-d29c-41dd-a01f-16d814c9ab82", + "router": { + "type": "switch", + "wait": { + "type": "msg" + }, + "categories": [ + { + "uuid": "37d8813f-1402-4ad2-9cc2-e9054a96525b", + "exit_uuid": "fc2fcd23-7c4a-44bd-a8c6-6c88e6ed09f8" + }, + { + "uuid": "0680b01f-ba0b-48f4-a688-d2f963130126", + "name": "Other", + "exit_uuid": "43accf99-4940-44f7-926b-a8b35d9403d6" + } + ], + "default_category_uuid": "0680b01f-ba0b-48f4-a688-d2f963130126", + "operand": "@input.text", + "cases": [ + { + "uuid": "5d6abc80-39e7-4620-9988-a2447bffe526", + "type": "has_text", + "category_uuid": "37d8813f-1402-4ad2-9cc2-e9054a96525b" + } + ] + }, + "exits": [ + { + "uuid": "fc2fcd23-7c4a-44bd-a8c6-6c88e6ed09f8" + }, + { + "uuid": "43accf99-4940-44f7-926b-a8b35d9403d6" + } + ] + } + ] + }, + "migrated": { + "uuid": "25a2d8b2-ae7c-4fed-964a-506fb8c3f0c0", + "name": "Test Flow", + "spec_version": "13.6.0", + "language": "eng", + "type": "messaging", + "nodes": [ + { + "uuid": "3dcccbb4-d29c-41dd-a01f-16d814c9ab82", + "router": { + "type": "switch", + "wait": { + "type": "msg" + }, + "categories": [ + { + "uuid": "37d8813f-1402-4ad2-9cc2-e9054a96525b", + "exit_uuid": "fc2fcd23-7c4a-44bd-a8c6-6c88e6ed09f8" + }, + { + "uuid": "0680b01f-ba0b-48f4-a688-d2f963130126", + "name": "Other", + "exit_uuid": "43accf99-4940-44f7-926b-a8b35d9403d6" + } + ], + "default_category_uuid": "0680b01f-ba0b-48f4-a688-d2f963130126", + "operand": "@input.text", + "cases": [ + { + "uuid": "5d6abc80-39e7-4620-9988-a2447bffe526", + "type": "has_text", + "category_uuid": "37d8813f-1402-4ad2-9cc2-e9054a96525b" + } + ] + }, + "exits": [ + { + "uuid": "fc2fcd23-7c4a-44bd-a8c6-6c88e6ed09f8" + }, + { + "uuid": "43accf99-4940-44f7-926b-a8b35d9403d6" + } + ] + } + ] + } + } +] \ No newline at end of file diff --git a/flows/definition/testdata/TestChangeLanguage_change_language_to_ara.snap b/flows/definition/testdata/TestChangeLanguage_change_language_to_ara.snap index a6babf773..27eae8a3a 100644 --- a/flows/definition/testdata/TestChangeLanguage_change_language_to_ara.snap +++ b/flows/definition/testdata/TestChangeLanguage_change_language_to_ara.snap @@ -1,7 +1,7 @@ { "uuid": "19cad1f2-9110-4271-98d4-1b968bf19410", "name": "Change Language", - "spec_version": "13.5.0", + "spec_version": "13.6.0", "language": "ara", "type": "messaging", "revision": 16, diff --git a/flows/definition/testdata/TestChangeLanguage_change_language_to_kin.snap b/flows/definition/testdata/TestChangeLanguage_change_language_to_kin.snap index 274cab364..212441359 100644 --- a/flows/definition/testdata/TestChangeLanguage_change_language_to_kin.snap +++ b/flows/definition/testdata/TestChangeLanguage_change_language_to_kin.snap @@ -1,7 +1,7 @@ { "uuid": "19cad1f2-9110-4271-98d4-1b968bf19410", "name": "Change Language", - "spec_version": "13.5.0", + "spec_version": "13.6.0", "language": "kin", "type": "messaging", "revision": 16, diff --git a/flows/definition/testdata/TestChangeLanguage_change_language_to_spa.snap b/flows/definition/testdata/TestChangeLanguage_change_language_to_spa.snap index 8dcb0de46..8ef69b280 100644 --- a/flows/definition/testdata/TestChangeLanguage_change_language_to_spa.snap +++ b/flows/definition/testdata/TestChangeLanguage_change_language_to_spa.snap @@ -1,7 +1,7 @@ { "uuid": "19cad1f2-9110-4271-98d4-1b968bf19410", "name": "Change Language", - "spec_version": "13.5.0", + "spec_version": "13.6.0", "language": "spa", "type": "messaging", "revision": 16, diff --git a/flows/definition/testdata/change_language.json b/flows/definition/testdata/change_language.json index 3e36c766e..8bf868db8 100644 --- a/flows/definition/testdata/change_language.json +++ b/flows/definition/testdata/change_language.json @@ -3,7 +3,7 @@ { "uuid": "19cad1f2-9110-4271-98d4-1b968bf19410", "name": "Change Language", - "spec_version": "13.5.0", + "spec_version": "13.6.0", "language": "eng", "type": "messaging", "revision": 16, diff --git a/flows/routers/category.go b/flows/routers/category.go index 04f8d502e..055921ecf 100644 --- a/flows/routers/category.go +++ b/flows/routers/category.go @@ -35,7 +35,7 @@ var _ flows.Category = (*Category)(nil) type categoryEnvelope struct { UUID flows.CategoryUUID `json:"uuid" validate:"required,uuid4"` - Name string `json:"name,omitempty"` + Name string `json:"name,omitempty" validate:"max=36"` ExitUUID flows.ExitUUID `json:"exit_uuid,omitempty" validate:"required,uuid4"` } diff --git a/flows/routers/switch.go b/flows/routers/switch.go index 0a0f3cada..e3475badb 100644 --- a/flows/routers/switch.go +++ b/flows/routers/switch.go @@ -129,7 +129,7 @@ func (r *SwitchRouter) Route(run flows.Run, step flows.Step, log flows.EventCall } // find first matching case - match, categoryUUID, extra, err := r.matchCase(run, step, operand, log) + match, categoryUUID, extra, err := r.matchCase(run, operand, log) if err != nil { return "", "", err } @@ -150,7 +150,7 @@ func (r *SwitchRouter) Route(run flows.Run, step flows.Step, log flows.EventCall return exit, operandAsStr, err } -func (r *SwitchRouter) matchCase(run flows.Run, step flows.Step, operand types.XValue, log flows.EventCallback) (string, flows.CategoryUUID, *types.XObject, error) { +func (r *SwitchRouter) matchCase(run flows.Run, operand types.XValue, log flows.EventCallback) (string, flows.CategoryUUID, *types.XObject, error) { for _, c := range r.cases { test := strings.ToLower(c.Type)