Skip to content

Commit

Permalink
Extend Get* methods to allow getting fields from self-describing data (
Browse files Browse the repository at this point in the history
…close #9)
  • Loading branch information
TiganeteaRobert committed May 9, 2022
1 parent c7b7997 commit be924a7
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 11 deletions.
82 changes: 76 additions & 6 deletions analytics/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import (
"github.com/pkg/errors"
)

const eventLength int = 131
const (
eventLength int = 131
EmptyFieldErr string = `Field is empty`
)

type KeyVal struct {
Key string
Expand Down Expand Up @@ -179,10 +182,8 @@ func (event ParsedEvent) ToJsonWithGeo() ([]byte, error) {
return jsonified, nil
}

// GetValue returns the value for a provided atomic field, without processing the rest of the event.
// For unstruct_event, it returns a map of only the data for the unstruct event.
func (event ParsedEvent) GetValue(field string) (interface{}, error) {

// getParsedValue gets a field's value from an event after parsing it with its specific ParseFunction
func (event ParsedEvent) getParsedValue(field string) ([]KeyVal, error) {
if len(event) != eventLength {
return nil, errors.New(fmt.Sprintf("Cannot get value - wrong number of fields provided: %v", len(event)))
}
Expand All @@ -191,12 +192,24 @@ func (event ParsedEvent) GetValue(field string) (interface{}, error) {
return nil, errors.New(fmt.Sprintf("Key %s not a valid atomic field", field))
}
if event[index] == "" {
return nil, errors.New(fmt.Sprintf("Field %s is empty", field))
return nil, errors.New(EmptyFieldErr)
}
kvPairs, err := enrichedEventFieldTypes[index].ParseFunction(enrichedEventFieldTypes[index].Key, event[index])
if err != nil {
return nil, err
}

return kvPairs, nil
}

// GetValue returns the value for a provided atomic field, without processing the rest of the event.
// For unstruct_event, it returns a map of only the data for the unstruct event.
func (event ParsedEvent) GetValue(field string) (interface{}, error) {
kvPairs, err := event.getParsedValue(field)
if err != nil {
return nil, err
}

if field == "contexts" || field == "derived_contexts" || field == "unstruct_event" {
// TODO: DRY HERE TOO?
output := make(map[string]interface{})
Expand All @@ -205,8 +218,65 @@ func (event ParsedEvent) GetValue(field string) (interface{}, error) {
}
return output, nil
}

return kvPairs[0].Value, nil
}

// GetUnstructEventValue returns the value for a provided atomic field inside an event's unstruct_event field
func (event ParsedEvent) GetUnstructEventValue(path ...interface{}) (interface{}, error) {
fullPath := append([]interface{}{`data`, `data`}, path...)

el := json.Get([]byte(event[indexMap["unstruct_event"]]), fullPath...)
return el.GetInterface(), el.LastError()
}

// GetContextValue returns the value for a provided atomic field inside an event's contexts or derived_contexts
func (event ParsedEvent) GetContextValue(contextName string, path ...interface{}) (interface{}, error) {
contextNames := []string{`contexts`, `derived_contexts`}
var contexts []interface{}
for _, c := range contextNames {
kvPairs, err := event.getParsedValue(c)
if err != nil && err.Error() != EmptyFieldErr {
return nil, err
}
// extract the key/value pairs of the event path into a map
eventMap := make(map[string]interface{})
for _, pair := range kvPairs {
eventMap[pair.Key] = pair.Value
}
contexts = append(contexts, eventMap)
}

var output []interface{}
b := make([]interface{}, len(path))
for idx := range path {
b[idx] = path[idx]
}

// iterate through all contextNames and extract the requested path to the output slice
for _, ctx := range contexts {
for key, contextSlice := range ctx.(map[string]interface{}) {
if key == contextName {
for _, ctxValues := range contextSlice.([]interface{}) {
ctxValuesMap := ctxValues.(map[string]interface{})
// output whole context if path is not defined
if len(path) == 0 {
output = append(output, ctxValuesMap)
continue
}
j, err := json.Marshal(ctxValuesMap)
if err != nil {
return nil, err
}
el := json.Get(j, b...)
if el.LastError() == nil {
output = append(output, el.GetInterface())
}
}
}
}
}
return output, nil
}

// GetSubsetMap returns a map of a subset of the event, containing only the atomic fields provided, without processing the rest of the event.
Expand Down
38 changes: 37 additions & 1 deletion analytics/transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ func TestGetValue(t *testing.T) {
// correct value contexts
contextsValue, err := fullEvent.GetValue("contexts")
assert.Nil(err)
assert.Equal(contextsMap, contextsValue)
assert.Equal(multipleContextsMap, contextsValue)

// incorrect field name
failureValue, err := fullEvent.GetValue("not_a_field")
Expand All @@ -315,6 +315,42 @@ func TestGetValue(t *testing.T) {
assert.NotNil(err)
}

func TestGetUnstructEventValue(t *testing.T) {
assert := assert.New(t)

// correct value unstruct field
unstructValue, err := fullEvent.GetUnstructEventValue(`elementClasses`, 0)
assert.Nil(err)
assert.Equal(`foreground`, unstructValue)

unstructValue, err = fullEvent.GetUnstructEventValue(`elementClassesBoo`, 0)
assert.NotNil(err)
assert.Nil(unstructValue)

unstructValue, err = fullEvent.GetUnstructEventValue(`elementId`)
assert.Nil(err)
assert.Equal(`exampleLink`, unstructValue)
}

func TestGetContextValue(t *testing.T) {
assert := assert.New(t)

// correct value contexts
contextsValue, err := fullEvent.GetContextValue(`contexts_org_schema_web_page_1`, "breadcrumb", 0)
assert.Nil(err)
assert.Equal(contextsArray, contextsValue)

// correct value contexts
contextsValue, err = fullEvent.GetContextValue(`contexts_org_schema_web_page_1`)
assert.Nil(err)
assert.Equal(wholeContextMap, contextsValue)

// correct value contexts
contextsValue, err = fullEvent.GetContextValue(`contexts_org_schema_web_page_1`, "breadcrumb", 3)
assert.Nil(err)
assert.Equal([]interface{}(nil), contextsValue)
}

func BenchmarkGetValue(b *testing.B) {
for i := 0; i < b.N; i++ {
fullEvent.GetValue("app_id")
Expand Down
9 changes: 5 additions & 4 deletions analytics/vars_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,10 +285,11 @@ var eventMapWithoutGeo = copyWithoutGeo(eventMapWithGeo)

var unstructMap = map[string]interface{}{"unstruct_event_com_snowplowanalytics_snowplow_link_click_1": eventMapWithGeo["unstruct_event_com_snowplowanalytics_snowplow_link_click_1"]}

var contextsMap = map[string]interface{}{
"contexts_org_w3_performance_timing_1": eventMapWithGeo["contexts_org_w3_performance_timing_1"],
"contexts_org_schema_web_page_1": eventMapWithGeo["contexts_org_schema_web_page_1"],
}
var contextsArray = []interface{}{"blog"}

var multipleContextsMap = map[string]interface{}{"contexts_org_schema_web_page_1": []interface{}{map[string]interface{}{"author": "Fred Blundun", "breadcrumb": []interface{}{"blog", "releases"}, "datePublished": "2014-11-06T00:00:00Z", "genre": "blog", "inLanguage": "en-US", "keywords": []interface{}{"snowplow", "javascript", "tracker", "event"}}}, "contexts_org_w3_performance_timing_1": []interface{}{map[string]interface{}{"connectEnd": 1.415358090183e+12, "connectStart": 1.415358090103e+12, "domComplete": 0.0, "domContentLoadedEventEnd": 1.415358091309e+12, "domContentLoadedEventStart": 1.415358090968e+12, "domInteractive": 1.415358090886e+12, "domLoading": 1.41535809027e+12, "domainLookupEnd": 1.415358090102e+12, "domainLookupStart": 1.415358090102e+12, "fetchStart": 1.41535808987e+12, "loadEventEnd": 0.0, "loadEventStart": 0.0, "navigationStart": 1.415358089861e+12, "redirectEnd": 0.0, "redirectStart": 0.0, "requestStart": 1.415358090183e+12, "responseEnd": 1.415358090265e+12, "responseStart": 1.415358090265e+12, "unloadEventEnd": 1.415358090287e+12, "unloadEventStart": 1.41535809027e+12}}}

var wholeContextMap = []interface{}{map[string]interface{}{"author": "Fred Blundun", "breadcrumb": []interface{}{"blog", "releases"}, "datePublished": "2014-11-06T00:00:00Z", "genre": "blog", "inLanguage": "en-US", "keywords": []interface{}{"snowplow", "javascript", "tracker", "event"}}}

var subsetMap = map[string]interface{}{
"app_id": eventMapWithGeo["app_id"],
Expand Down

0 comments on commit be924a7

Please sign in to comment.