From 5087133ddefe8ad24e98e63d1b20a9ee10294de0 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 5 Feb 2018 14:11:12 -0500 Subject: [PATCH 1/2] Migrate @parent.contact and @child.contact correctly --- excellent/legacy.go | 22 ++++++++++++++++++++-- excellent/legacy_test.go | 17 ++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/excellent/legacy.go b/excellent/legacy.go index 8fcc3a053..4d21c3206 100644 --- a/excellent/legacy.go +++ b/excellent/legacy.go @@ -64,6 +64,16 @@ func (v varMapper) Resolve(key string) interface{} { // is it a fixed base item? value, ok := v.baseVars[key] if ok { + asVarMapper, isVarMapper := value.(varMapper) + if isVarMapper { + return &varMapper{ + base: fmt.Sprintf("%s.%s", strings.Join(newPath, "."), asVarMapper.base), + baseVars: asVarMapper.baseVars, + arbitraryNesting: asVarMapper.arbitraryNesting, + arbitraryVars: asVarMapper.arbitraryVars, + } + } + newPath = append(newPath, value.(string)) return strings.Join(newPath, ".") } @@ -172,13 +182,21 @@ func newRootVarMapper() vars { }, }, "parent": varMapper{ - base: "parent.results", + base: "parent", + baseVars: map[string]interface{}{ + "contact": contact, + }, + arbitraryNesting: "results", arbitraryVars: map[string]interface{}{ "category": "category_localized", }, }, "child": varMapper{ - base: "child.results", + base: "child", + baseVars: map[string]interface{}{ + "contact": contact, + }, + arbitraryNesting: "results", arbitraryVars: map[string]interface{}{ "category": "category_localized", }, diff --git a/excellent/legacy_test.go b/excellent/legacy_test.go index 82af7aa13..4e8226ade 100644 --- a/excellent/legacy_test.go +++ b/excellent/legacy_test.go @@ -13,7 +13,7 @@ type testTemplate struct { func TestTranslate(t *testing.T) { var tests = []testTemplate{ - // variables + // contact variables {old: "@contact", new: "@contact"}, {old: "@contact.first_name", new: "@contact.first_name"}, {old: "@contact.name", new: "@contact.name"}, @@ -25,10 +25,19 @@ func TestTranslate(t *testing.T) { {old: "@contact.mailto", new: "@(format_urn(contact.urns.mailto))"}, {old: "@contact.uuid", new: "@contact.uuid"}, {old: "@contact.blerg", new: "@contact.fields.blerg"}, - {old: "@child.blerg", new: "@child.results.blerg"}, - {old: "@parent.blerg", new: "@parent.results.blerg"}, + + // run variables {old: "@flow.blerg", new: "@run.results.blerg"}, {old: "@flow.blerg.category", new: "@run.results.blerg.category_localized"}, + {old: "@child.blerg", new: "@child.results.blerg"}, + {old: "@child.contact", new: "@child.contact"}, + {old: "@child.contact.age", new: "@child.contact.fields.age"}, + {old: "@parent.blerg", new: "@parent.results.blerg"}, + {old: "@parent.blerg.category", new: "@parent.results.blerg.category_localized"}, + {old: "@parent.contact", new: "@parent.contact"}, + {old: "@parent.contact.name", new: "@parent.contact.name"}, + + // input {old: "@step", new: "@run.input"}, {old: "@step.value", new: "@run.input"}, {old: "@step.text", new: "@run.input.text"}, @@ -37,6 +46,8 @@ func TestTranslate(t *testing.T) { {old: "@step.contact", new: "@contact"}, {old: "@step.contact.name", new: "@contact.name"}, {old: "@step.contact.age", new: "@contact.fields.age"}, + + // dates {old: "@date.now", new: "@(now())"}, {old: "@date.today", new: "@(today())"}, {old: "@date.tomorrow", new: "@(tomorrow())"}, From 53cef17393201ae24bcaed45203ee5d97c06af18 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 5 Feb 2018 15:29:43 -0500 Subject: [PATCH 2/2] Add support for migrating more complex URN variables --- excellent/legacy.go | 206 +++++++++++++++++++++------------------ excellent/legacy_test.go | 15 ++- 2 files changed, 123 insertions(+), 98 deletions(-) diff --git a/excellent/legacy.go b/excellent/legacy.go index 4d21c3206..ed8cc8649 100644 --- a/excellent/legacy.go +++ b/excellent/legacy.go @@ -3,10 +3,8 @@ package excellent import ( "bytes" "fmt" - "log" "strconv" "strings" - "time" "github.com/antlr/antlr4/runtime/Go/antlr" "github.com/nyaruka/gocommon/urns" @@ -15,41 +13,43 @@ import ( ) var topLevelScopes = []string{"contact", "child", "parent", "run"} -var times = map[string]time.Duration{} -func timeTrack(start time.Time, name string) { - elapsed := time.Since(start) - times[name] += elapsed - log.Printf("%s took %s (%s)", name, elapsed, times[name]) -} +type varMapper struct { + // subitems that should be replaced completely with the given strings + substitutions map[string]string -type vars map[string]interface{} + // base for fixed subitems, e.g. "contact" + base string -func (v vars) Resolve(key string) interface{} { - value, ok := v[key] - if ok { - return value - } - return key -} + // recognized fixed subitems, e.g. "name" or "uuid" + baseVars map[string]interface{} -func (v vars) Default() interface{} { - return v["__default__"] -} + // nesting for arbitrary subitems, e.g. contact fields or run results + arbitraryNesting string -func (v vars) String() string { - return fmt.Sprintf("%s", v["__default__"]) + // mapper for each arbitrary item + arbitraryVars map[string]interface{} } -type varMapper struct { - substitutions map[string]string - base string - baseVars vars - arbitraryNesting string - arbitraryVars vars +// returns a copy of this mapper with a prefix applied to the previous base +func (v *varMapper) rebase(prefix string) *varMapper { + var newBase string + if prefix != "" { + newBase = fmt.Sprintf("%s.%s", prefix, v.base) + } else { + newBase = v.base + } + return &varMapper{ + substitutions: v.substitutions, + base: newBase, + baseVars: v.baseVars, + arbitraryNesting: v.arbitraryNesting, + arbitraryVars: v.arbitraryVars, + } } -func (v varMapper) Resolve(key string) interface{} { +func (v *varMapper) Resolve(key string) interface{} { + // is this a complete substitution? if substitute, ok := v.substitutions[key]; ok { return substitute @@ -64,16 +64,16 @@ func (v varMapper) Resolve(key string) interface{} { // is it a fixed base item? value, ok := v.baseVars[key] if ok { - asVarMapper, isVarMapper := value.(varMapper) + // subitem may be a mapper itself + asVarMapper, isVarMapper := value.(*varMapper) if isVarMapper { - return &varMapper{ - base: fmt.Sprintf("%s.%s", strings.Join(newPath, "."), asVarMapper.base), - baseVars: asVarMapper.baseVars, - arbitraryNesting: asVarMapper.arbitraryNesting, - arbitraryVars: asVarMapper.arbitraryVars, + if len(newPath) > 0 { + return asVarMapper.rebase(strings.Join(newPath, ".")) } + return asVarMapper } + // or a simple string in which case we add to the end of the path and return that newPath = append(newPath, value.(string)) return strings.Join(newPath, ".") } @@ -93,14 +93,17 @@ func (v varMapper) Resolve(key string) interface{} { } return strings.Join(newPath, ".") - } -func (v varMapper) Default() interface{} { +func (v *varMapper) Default() interface{} { return v.base } -func (v varMapper) String() string { +func (v *varMapper) String() string { + sub, exists := v.substitutions["__default__"] + if exists { + return sub + } return v.base } @@ -152,16 +155,10 @@ var functionTemplates = map[string]functionTemplate{ "time": {name: "time", params: "(%s %s %s)"}, } -func newRootVarMapper() vars { - - urnSubstitutions := make(map[string]string) - for scheme := range urns.ValidSchemes { - urnSubstitutions[scheme] = fmt.Sprintf("format_urn(contact.urns.%s)", scheme) - } +func newRootVarMapper() *varMapper { - contact := varMapper{ - substitutions: urnSubstitutions, - base: "contact", + contact := &varMapper{ + base: "contact", baseVars: map[string]interface{}{ "uuid": "uuid", "name": "name", @@ -173,58 +170,81 @@ func newRootVarMapper() vars { arbitraryNesting: "fields", } - return vars{ - "contact": contact, - "flow": varMapper{ - base: "run.results", - arbitraryVars: map[string]interface{}{ - "category": "category_localized", + for scheme := range urns.ValidSchemes { + contact.baseVars[scheme] = &varMapper{ + substitutions: map[string]string{ + "__default__": fmt.Sprintf("format_urn(contact.urns.%s)", scheme), + "display": fmt.Sprintf("format_urn(contact.urns.%s)", scheme), + "scheme": fmt.Sprintf("contact.urns.%s.0.scheme", scheme), + "path": fmt.Sprintf("contact.urns.%s.0.path", scheme), + "urn": fmt.Sprintf("contact.urns.%s.0", scheme), }, - }, - "parent": varMapper{ - base: "parent", - baseVars: map[string]interface{}{ - "contact": contact, + base: fmt.Sprintf("urns.%s", scheme), + } + } + + return &varMapper{ + baseVars: map[string]interface{}{ + "contact": contact, + "flow": &varMapper{ + base: "run.results", + arbitraryVars: map[string]interface{}{ + "category": "category_localized", + }, }, - arbitraryNesting: "results", - arbitraryVars: map[string]interface{}{ - "category": "category_localized", + "parent": &varMapper{ + base: "parent", + baseVars: map[string]interface{}{ + "contact": contact, + }, + arbitraryNesting: "results", + arbitraryVars: map[string]interface{}{ + "category": "category_localized", + }, }, - }, - "child": varMapper{ - base: "child", - baseVars: map[string]interface{}{ - "contact": contact, + "child": &varMapper{ + base: "child", + baseVars: map[string]interface{}{ + "contact": contact, + }, + arbitraryNesting: "results", + arbitraryVars: map[string]interface{}{ + "category": "category_localized", + }, }, - arbitraryNesting: "results", - arbitraryVars: map[string]interface{}{ - "category": "category_localized", + "step": &varMapper{ + substitutions: map[string]string{ + "__default__": "run.input", + "value": "run.input", + "text": "run.input.text", + "attachments": "run.input.attachments", + "time": "run.input.created_on", + }, + baseVars: map[string]interface{}{ + "contact": contact, + }, + }, + "channel": &varMapper{ + substitutions: map[string]string{ + "__default__": "contact.channel.address", + "name": "contact.channel.name", + "tel": "contact.channel.address", + "tel_e164": "contact.channel.address", + }, + }, + "date": &varMapper{ + substitutions: map[string]string{ + "__default__": "now()", + "now": "now()", + "today": "today()", + "tomorrow": "tomorrow()", + "yesterday": "yesterday()", + }, + }, + "extra": &varMapper{ + base: "run.webhook", + arbitraryNesting: "json", }, - }, - "step": vars{ - "__default__": "run.input", - "value": "run.input", - "text": "run.input.text", - "attachments": "run.input.attachments", - "time": "run.input.created_on", - "contact": contact, - }, - "channel": vars{ - "__default__": "contact.channel.address", - "name": "contact.channel.name", - "tel": "contact.channel.address", - "tel_e164": "contact.channel.address", - }, - "date": vars{ - "now": "now()", - "today": "today()", - "tomorrow": "tomorrow()", - "yesterday": "yesterday()", - "__default__": "now()", - }, - "extra": varMapper{ - base: "run.webhook", - arbitraryNesting: "json", }, } } diff --git a/excellent/legacy_test.go b/excellent/legacy_test.go index 4e8226ade..07731768d 100644 --- a/excellent/legacy_test.go +++ b/excellent/legacy_test.go @@ -9,22 +9,27 @@ type testTemplate struct { new string } -// TestThis func TestTranslate(t *testing.T) { var tests = []testTemplate{ // contact variables {old: "@contact", new: "@contact"}, - {old: "@contact.first_name", new: "@contact.first_name"}, + {old: "@contact.uuid", new: "@contact.uuid"}, {old: "@contact.name", new: "@contact.name"}, + {old: "@contact.first_name", new: "@contact.first_name"}, + {old: "@contact.blerg", new: "@contact.fields.blerg"}, + + // contact URN variables {old: "@contact.tel", new: "@(format_urn(contact.urns.tel))"}, + {old: "@contact.tel.display", new: "@(format_urn(contact.urns.tel))"}, + {old: "@contact.tel.scheme", new: "@contact.urns.tel.0.scheme"}, + {old: "@contact.tel.path", new: "@contact.urns.tel.0.path"}, + {old: "@contact.tel.urn", new: "@contact.urns.tel.0"}, {old: "@contact.tel_e164", new: "@contact.urns.tel.0.path"}, {old: "@contact.telegram", new: "@(format_urn(contact.urns.telegram))"}, {old: "@contact.twitter", new: "@(format_urn(contact.urns.twitter))"}, {old: "@contact.facebook", new: "@(format_urn(contact.urns.facebook))"}, {old: "@contact.mailto", new: "@(format_urn(contact.urns.mailto))"}, - {old: "@contact.uuid", new: "@contact.uuid"}, - {old: "@contact.blerg", new: "@contact.fields.blerg"}, // run variables {old: "@flow.blerg", new: "@run.results.blerg"}, @@ -48,11 +53,11 @@ func TestTranslate(t *testing.T) { {old: "@step.contact.age", new: "@contact.fields.age"}, // dates + {old: "@date", new: "@(now())"}, {old: "@date.now", new: "@(now())"}, {old: "@date.today", new: "@(today())"}, {old: "@date.tomorrow", new: "@(tomorrow())"}, {old: "@date.yesterday", new: "@(yesterday())"}, - {old: "@date", new: "@(now())"}, // variables in parens {old: "@(contact.tel)", new: "@(format_urn(contact.urns.tel))"},