Skip to content

Commit

Permalink
Pass env into CollateEquals and CollateTransform
Browse files Browse the repository at this point in the history
  • Loading branch information
rowanseymour committed Aug 18, 2023
1 parent 950f85e commit 6edb219
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 69 deletions.
2 changes: 1 addition & 1 deletion assets/location.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ import "github.com/nyaruka/goflow/envs"
// @asset location
type LocationHierarchy interface {
FindByPath(path envs.LocationPath) *envs.Location
FindByName(name string, level envs.LocationLevel, parent *envs.Location) []*envs.Location
FindByName(env envs.Environment, name string, level envs.LocationLevel, parent *envs.Location) []*envs.Location
}
13 changes: 13 additions & 0 deletions envs/collate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package envs

import "strings"

// CollateEquals returns true if the given strings are equal in the given environment's collation
func CollateEquals(env Environment, s, t string) bool {
return CollateTransform(env, s) == CollateTransform(env, t)
}

// CollateTransform transforms the given string into it's form to be used for collation.
func CollateTransform(env Environment, s string) string {
return strings.ToLower(s)
}
28 changes: 15 additions & 13 deletions envs/location_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ var locationHierarchyJSON = `
}`

func TestLocationHierarchy(t *testing.T) {
hierarchy, err := envs.ReadLocationHierarchy(json.RawMessage(locationHierarchyJSON))
env := envs.NewBuilder().Build()

hierarchy, err := envs.ReadLocationHierarchy(env, json.RawMessage(locationHierarchyJSON))
assert.NoError(t, err)

rwanda := hierarchy.Root()
Expand Down Expand Up @@ -90,18 +92,18 @@ func TestLocationHierarchy(t *testing.T) {
assert.Equal(t, gasabo, ndera.Parent())
assert.Equal(t, 0, len(ndera.Children()))

assert.Equal(t, []*envs.Location{rwanda}, hierarchy.FindByName("RWaNdA", envs.LocationLevel(0), nil))
assert.Equal(t, []*envs.Location{kigali}, hierarchy.FindByName("kigari", envs.LocationLevel(1), nil))
assert.Equal(t, []*envs.Location{kigali}, hierarchy.FindByName("rwanda > kigali city", envs.LocationLevel(1), nil))
assert.Equal(t, []*envs.Location{kigali}, hierarchy.FindByName("kigari", envs.LocationLevel(1), rwanda))
assert.Equal(t, []*envs.Location{gasabo}, hierarchy.FindByName("GASABO", envs.LocationLevel(2), nil))
assert.Equal(t, []*envs.Location{gasabo}, hierarchy.FindByName("GASABO", envs.LocationLevel(2), kigali))
assert.Equal(t, []*envs.Location{ndera}, hierarchy.FindByName("RWANDA > kigali city > gasabo > ndera", envs.LocationLevel(3), nil))

assert.Equal(t, []*envs.Location{}, hierarchy.FindByName("boston", envs.LocationLevel(1), nil)) // no such name
assert.Equal(t, []*envs.Location{}, hierarchy.FindByName("kigari", envs.LocationLevel(8), nil)) // no such level
assert.Equal(t, []*envs.Location{}, hierarchy.FindByName("kigari", envs.LocationLevel(2), nil)) // wrong level
assert.Equal(t, []*envs.Location{}, hierarchy.FindByName("kigari", envs.LocationLevel(2), gasabo)) // wrong parent
assert.Equal(t, []*envs.Location{rwanda}, hierarchy.FindByName(env, "RWaNdA", envs.LocationLevel(0), nil))
assert.Equal(t, []*envs.Location{kigali}, hierarchy.FindByName(env, "kigari", envs.LocationLevel(1), nil))
assert.Equal(t, []*envs.Location{kigali}, hierarchy.FindByName(env, "rwanda > kigali city", envs.LocationLevel(1), nil))
assert.Equal(t, []*envs.Location{kigali}, hierarchy.FindByName(env, "kigari", envs.LocationLevel(1), rwanda))
assert.Equal(t, []*envs.Location{gasabo}, hierarchy.FindByName(env, "GASABO", envs.LocationLevel(2), nil))
assert.Equal(t, []*envs.Location{gasabo}, hierarchy.FindByName(env, "GASABO", envs.LocationLevel(2), kigali))
assert.Equal(t, []*envs.Location{ndera}, hierarchy.FindByName(env, "RWANDA > kigali city > gasabo > ndera", envs.LocationLevel(3), nil))

assert.Equal(t, []*envs.Location{}, hierarchy.FindByName(env, "boston", envs.LocationLevel(1), nil)) // no such name
assert.Equal(t, []*envs.Location{}, hierarchy.FindByName(env, "kigari", envs.LocationLevel(8), nil)) // no such level
assert.Equal(t, []*envs.Location{}, hierarchy.FindByName(env, "kigari", envs.LocationLevel(2), nil)) // wrong level
assert.Equal(t, []*envs.Location{}, hierarchy.FindByName(env, "kigari", envs.LocationLevel(2), gasabo)) // wrong parent

assert.Equal(t, rwanda, hierarchy.FindByPath(envs.LocationPath("RWANDA")))
assert.Equal(t, kigali, hierarchy.FindByPath("RWANDA > KIGALI CITY"))
Expand Down
39 changes: 22 additions & 17 deletions envs/locations.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ type LocationPath string

// LocationResolver is used to resolve locations from names or hierarchical paths
type LocationResolver interface {
FindLocations(string, LocationLevel, *Location) []*Location
FindLocationsFuzzy(string, LocationLevel, *Location) []*Location
FindLocations(Environment, string, LocationLevel, *Location) []*Location
FindLocationsFuzzy(Environment, string, LocationLevel, *Location) []*Location
LookupLocation(LocationPath) *Location
}

Expand Down Expand Up @@ -118,12 +118,14 @@ func (p locationPathLookup) lookup(path LocationPath) *Location { return p[path.
// location names aren't always unique in a given level - i.e. you can have two wards with the same name, but different parents
type locationNameLookup map[string][]*Location

func (n locationNameLookup) addLookup(name string, location *Location) {
name = strings.ToLower(name)
func (n locationNameLookup) addLookup(env Environment, name string, location *Location) {
name = CollateTransform(env, name)
n[name] = append(n[name], location)
}

func (n locationNameLookup) lookup(name string) []*Location { return n[strings.ToLower(name)] }
func (n locationNameLookup) lookup(env Environment, name string) []*Location {
return n[CollateTransform(env, name)]
}

// LocationHierarchy is a hierarical tree of locations
type LocationHierarchy struct {
Expand All @@ -135,14 +137,14 @@ type LocationHierarchy struct {
}

// NewLocationHierarchy cretes a new location hierarchy
func NewLocationHierarchy(root *Location, numLevels int) *LocationHierarchy {
func NewLocationHierarchy(env Environment, root *Location, numLevels int) *LocationHierarchy {
h := &LocationHierarchy{}
h.initializeFromRoot(root, numLevels)
h.initializeFromRoot(env, root, numLevels)
return h
}

// NewLocationHierarchy cretes a new location hierarchy
func (h *LocationHierarchy) initializeFromRoot(root *Location, numLevels int) {
func (h *LocationHierarchy) initializeFromRoot(env Environment, root *Location, numLevels int) {
h.root = root
h.levelLookups = make([]locationNameLookup, numLevels)
h.pathLookup = make(locationPathLookup)
Expand All @@ -160,17 +162,17 @@ func (h *LocationHierarchy) initializeFromRoot(root *Location, numLevels int) {
}

h.pathLookup.addLookup(location.path, location)
h.addNameLookups(location)
h.addNameLookups(env, location)
})
}

func (h *LocationHierarchy) addNameLookups(location *Location) {
func (h *LocationHierarchy) addNameLookups(env Environment, location *Location) {
lookups := h.levelLookups[int(location.level)]
lookups.addLookup(location.name, location)
lookups.addLookup(env, location.name, location)

// include any aliases as names too
for _, alias := range location.aliases {
lookups.addLookup(alias, location)
lookups.addLookup(env, alias, location)
}
}

Expand All @@ -180,7 +182,7 @@ func (h *LocationHierarchy) Root() *Location {
}

// FindByName looks for all locations in the hierarchy with the given level and name or alias
func (h *LocationHierarchy) FindByName(name string, level LocationLevel, parent *Location) []*Location {
func (h *LocationHierarchy) FindByName(env Environment, name string, level LocationLevel, parent *Location) []*Location {

// try it as a path first if it looks possible
if level == 0 || IsPossibleLocationPath(name) {
Expand All @@ -191,7 +193,7 @@ func (h *LocationHierarchy) FindByName(name string, level LocationLevel, parent
}

if int(level) < len(h.levelLookups) {
matches := h.levelLookups[int(level)].lookup(name)
matches := h.levelLookups[int(level)].lookup(env, name)
if matches != nil {
// if a parent is specified, filter the matches by it
if parent != nil {
Expand Down Expand Up @@ -221,8 +223,11 @@ func (h *LocationHierarchy) UnmarshalJSON(data []byte) error {
return err
}

// TODO this method is only used to load static assets and they're only used for testing.. but this isn't ideal
env := NewBuilder().Build()

root := locationFromEnvelope(&le, LocationLevel(0), nil)
h.initializeFromRoot(root, 4)
h.initializeFromRoot(env, root, 4)
return nil
}

Expand Down Expand Up @@ -253,13 +258,13 @@ func locationFromEnvelope(envelope *locationEnvelope, currentLevel LocationLevel
}

// ReadLocationHierarchy reads a location hierarchy from the given JSON
func ReadLocationHierarchy(data json.RawMessage) (*LocationHierarchy, error) {
func ReadLocationHierarchy(env Environment, data json.RawMessage) (*LocationHierarchy, error) {
var le locationEnvelope
if err := utils.UnmarshalAndValidate(data, &le); err != nil {
return nil, err
}

root := locationFromEnvelope(&le, LocationLevel(0), nil)

return NewLocationHierarchy(root, 4), nil
return NewLocationHierarchy(env, root, 4), nil
}
14 changes: 7 additions & 7 deletions flows/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ type assetLocationResolver struct {
}

// FindLocations returns locations with the matching name (case-insensitive), level and parent (optional)
func (r *assetLocationResolver) FindLocations(name string, level envs.LocationLevel, parent *envs.Location) []*envs.Location {
return r.locations.FindByName(name, level, parent)
func (r *assetLocationResolver) FindLocations(env envs.Environment, name string, level envs.LocationLevel, parent *envs.Location) []*envs.Location {
return r.locations.FindByName(env, name, level, parent)
}

// FindLocationsFuzzy returns matching locations like FindLocations but attempts the following strategies
Expand All @@ -48,31 +48,31 @@ func (r *assetLocationResolver) FindLocations(name string, level envs.LocationLe
// 2. Match with punctuation removed
// 3. Split input into words and try to match each word
// 4. Try to match pairs of words
func (r *assetLocationResolver) FindLocationsFuzzy(text string, level envs.LocationLevel, parent *envs.Location) []*envs.Location {
func (r *assetLocationResolver) FindLocationsFuzzy(env envs.Environment, text string, level envs.LocationLevel, parent *envs.Location) []*envs.Location {
// try matching name exactly
if locations := r.FindLocations(text, level, parent); len(locations) > 0 {
if locations := r.FindLocations(env, text, level, parent); len(locations) > 0 {
return locations
}

// try with punctuation removed
stripped := strings.TrimSpace(regexp.MustCompile(`[\s\p{P}]+`).ReplaceAllString(text, ""))
if locations := r.FindLocations(stripped, level, parent); len(locations) > 0 {
if locations := r.FindLocations(env, stripped, level, parent); len(locations) > 0 {
return locations
}

// try on each tokenized word
re := regexp.MustCompile(`[\p{L}\d]+(-[\p{L}\d]+)*`)
words := re.FindAllString(text, -1)
for _, word := range words {
if locations := r.FindLocations(word, level, parent); len(locations) > 0 {
if locations := r.FindLocations(env, word, level, parent); len(locations) > 0 {
return locations
}
}

// try with each pair of words
for i := 0; i < len(words)-1; i++ {
wordPair := strings.Join(words[i:i+2], " ")
if locations := r.FindLocations(wordPair, level, parent); len(locations) > 0 {
if locations := r.FindLocations(env, wordPair, level, parent); len(locations) > 0 {
return locations
}
}
Expand Down
2 changes: 1 addition & 1 deletion flows/environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func TestAssetsEnvironment(t *testing.T) {
kigali := aenv.LocationResolver().LookupLocation("Rwanda > Kigali City")
assert.Equal(t, "Kigali City", kigali.Name())

matches := aenv.LocationResolver().FindLocationsFuzzy("gisozi town", flows.LocationLevelWard, nil)
matches := aenv.LocationResolver().FindLocationsFuzzy(env, "gisozi town", flows.LocationLevelWard, nil)
assert.Equal(t, 1, len(matches))
assert.Equal(t, "Gisozi", matches[0].Name())
}
Expand Down
6 changes: 3 additions & 3 deletions flows/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,15 +233,15 @@ func (f FieldValues) Parse(env envs.Environment, fields *FieldAssets, field *Fie
if field.Type() == assets.FieldTypeWard {
parent := f.getFirstLocationValue(env, fields, assets.FieldTypeDistrict)
if parent != nil {
matchingLocations = locations.FindLocationsFuzzy(rawValue, LocationLevelWard, parent)
matchingLocations = locations.FindLocationsFuzzy(env, rawValue, LocationLevelWard, parent)
}
} else if field.Type() == assets.FieldTypeDistrict {
parent := f.getFirstLocationValue(env, fields, assets.FieldTypeState)
if parent != nil {
matchingLocations = locations.FindLocationsFuzzy(rawValue, LocationLevelDistrict, parent)
matchingLocations = locations.FindLocationsFuzzy(env, rawValue, LocationLevelDistrict, parent)
}
} else if field.Type() == assets.FieldTypeState {
matchingLocations = locations.FindLocationsFuzzy(rawValue, LocationLevelState, nil)
matchingLocations = locations.FindLocationsFuzzy(env, rawValue, LocationLevelState, nil)
}

if len(matchingLocations) > 0 {
Expand Down
Loading

0 comments on commit 6edb219

Please sign in to comment.