Skip to content

Commit

Permalink
feat: Implement transform to add Tags to Event
Browse files Browse the repository at this point in the history
closes #455 & #369

Signed-off-by: lenny <[email protected]>
  • Loading branch information
lenny committed Sep 4, 2020
1 parent 337bfa7 commit acb6ca3
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 12 deletions.
38 changes: 38 additions & 0 deletions appsdk/configurable.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const (
ClientID = "clientid"
Topic = "topic"
AuthMode = "authmode"
Tags = "tags"
)

// AppFunctionsSDKConfigurable contains the helper functions that return the function pointers for building the configurable function pipeline.
Expand Down Expand Up @@ -523,3 +524,40 @@ func (dynamic AppFunctionsSDKConfigurable) MQTTSecretSend(parameters map[string]
transform := transforms.NewMQTTSecretSender(mqttConfig, persistOnError)
return transform.MQTTSend
}

// AddTags adds the configured list of tags to Events passed to the transform.
// This function is a configuration function and returns a function pointer.
func (dynamic AppFunctionsSDKConfigurable) AddTags(parameters map[string]string) appcontext.AppFunction {
tagsSpec, ok := parameters[Tags]
if !ok {
dynamic.Sdk.LoggingClient.Error(fmt.Sprintf("Could not find '%s' parameter", Tags))
return nil
}

tagKeyValues := util.DeleteEmptyAndTrim(strings.FieldsFunc(tagsSpec, util.SplitComma))

tags := make(map[string]string)
for _, tag := range tagKeyValues {
keyValue := util.DeleteEmptyAndTrim(strings.FieldsFunc(tag, util.SplitColon))
if len(keyValue) != 2 {
dynamic.Sdk.LoggingClient.Error(fmt.Sprintf("Bad Tags specification format. Expect comma separated list of 'key:value'. Got `%s`", tagsSpec))
return nil
}

if len(keyValue[0]) == 0 {
dynamic.Sdk.LoggingClient.Error(fmt.Sprintf("Tag key missing. Got '%s'", tag))
return nil
}
if len(keyValue[1]) == 0 {
dynamic.Sdk.LoggingClient.Error(fmt.Sprintf("Tag value missing. Got '%s'", tag))
return nil
}

tags[keyValue[0]] = keyValue[1]
}

transform := transforms.NewTags(tags)
dynamic.Sdk.LoggingClient.Debug("Add Tags", Tags, fmt.Sprintf("%v", tags))

return transform.AddTags
}
33 changes: 33 additions & 0 deletions appsdk/configurable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,36 @@ func TestConfigurableMQTTSecretSend(t *testing.T) {
trx := configurable.MQTTSecretSend(params)
assert.NotNil(t, trx, "return result from MQTTSend should not be nil")
}

func TestAppFunctionsSDKConfigurable_AddTags(t *testing.T) {
configurable := AppFunctionsSDKConfigurable{
Sdk: &AppFunctionsSDK{
LoggingClient: lc,
},
}

tests := []struct {
Name string
ParamName string
TagsSpec string
ExpectNil bool
}{
{"Good - non-empty list", Tags, "GatewayId:HoustonStore000123,Latitude:29.630771,Longitude:-95.377603", false},
{"Good - empty list", Tags, "", false},
{"Bad - No : separator", Tags, "GatewayId HoustonStore000123, Latitude:29.630771,Longitude:-95.377603", true},
{"Bad - Missing value", Tags, "GatewayId:,Latitude:29.630771,Longitude:-95.377603", true},
{"Bad - Missing key", Tags, "GatewayId:HoustonStore000123,:29.630771,Longitude:-95.377603", true},
{"Bad - Missing key & value", Tags, ":,:,:", true},
{"Bad - No Tags parameter", "NotTags", ":,:,:", true},
}

for _, testCase := range tests {
t.Run(testCase.Name, func(t *testing.T) {
params := make(map[string]string)
params[testCase.ParamName] = testCase.TagsSpec

transform := configurable.AddTags(params)
assert.Equal(t, testCase.ExpectNil, transform == nil)
})
}
}
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/diegoholiveira/jsonlogic v1.0.1-0.20200220175622-ab7989be08b9
github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/edgexfoundry/go-mod-bootstrap v0.0.37
github.com/edgexfoundry/go-mod-core-contracts v0.1.72
github.com/edgexfoundry/go-mod-core-contracts v0.1.74
github.com/edgexfoundry/go-mod-messaging v0.1.21
github.com/edgexfoundry/go-mod-registry v0.1.21
github.com/edgexfoundry/go-mod-secrets v0.0.19
Expand All @@ -24,3 +24,5 @@ require (
github.com/xdg/stringprep v1.0.0 // indirect
go.mongodb.org/mongo-driver v1.1.1
)

replace github.com/edgexfoundry/go-mod-core-contracts => ../go-mod-core-contracts
16 changes: 16 additions & 0 deletions pkg/transforms/batch.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
//
// Copyright (c) 2020 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package transforms

import (
Expand Down
15 changes: 6 additions & 9 deletions pkg/transforms/conversion.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2019 Intel Corporation
// Copyright (c) 2020 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -18,8 +18,8 @@ package transforms

import (
"encoding/json"
"encoding/xml"
"errors"
"fmt"

"github.com/edgexfoundry/app-functions-sdk-go/appcontext"
"github.com/edgexfoundry/go-mod-core-contracts/models"
Expand All @@ -41,15 +41,12 @@ func (f Conversion) TransformToXML(edgexcontext *appcontext.Context, params ...i
return false, errors.New("No Event Received")
}
edgexcontext.LoggingClient.Debug("Transforming to XML")
if result, ok := params[0].(models.Event); ok {
b, err := xml.Marshal(result)
if event, ok := params[0].(models.Event); ok {
xml, err := event.ToXML()
if err != nil {
// LoggingClient.Error(fmt.Sprintf("Error parsing XML. Error: %s", err.Error()))
return false, errors.New("Incorrect type received, expecting models.Event")
return false, fmt.Errorf("unable to marshal Event to XML: %s", err.Error())
}
// should we return a byte[] or string?
// return b
return true, string(b)
return true, []byte(xml)
}
return false, errors.New("Unexpected type received")
}
Expand Down
16 changes: 16 additions & 0 deletions pkg/transforms/jsonlogic.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
//
// Copyright (c) 2020 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package transforms

import (
Expand Down
67 changes: 67 additions & 0 deletions pkg/transforms/tags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// Copyright (c) 2020 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package transforms

import (
"errors"
"fmt"

"github.com/edgexfoundry/go-mod-core-contracts/models"

"github.com/edgexfoundry/app-functions-sdk-go/appcontext"
)

// Tags contains the list of Tag key/values
type Tags struct {
tags map[string]string
}

// NewTags creates, initializes and returns a new instance of Tags
func NewTags(tags map[string]string) Tags {
return Tags{
tags: tags,
}
}

// AddTags adds the pre-configured list of tags to the Event's tags collection.
func (t *Tags) AddTags(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) {
edgexcontext.LoggingClient.Debug("Adding tags to Event")

if len(params) < 1 {
return false, errors.New("no Event Received")
}

event, ok := params[0].(models.Event)
if !ok {
return false, errors.New("type received is not an Event")
}

if len(t.tags) > 0 {
if event.Tags == nil {
event.Tags = make(map[string]string)
}

for tag, value := range t.tags {
event.Tags[tag] = value
}
edgexcontext.LoggingClient.Debug(fmt.Sprintf("Tags added to Event. Event tags=%v", event.Tags))
} else {
edgexcontext.LoggingClient.Debug("No tags added to Event. Add tags list is empty.")
}

return true, event
}
99 changes: 99 additions & 0 deletions pkg/transforms/tags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// Copyright (c) 2020 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package transforms

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/edgexfoundry/go-mod-core-contracts/clients/logger"
"github.com/edgexfoundry/go-mod-core-contracts/models"

"github.com/edgexfoundry/app-functions-sdk-go/appcontext"
)

var tagsToAdd = map[string]string{
"GatewayId": "HoustonStore000123",
"Latitude": "29.630771",
"Longitude": "-95.377603",
}

var eventWithExistingTags = models.Event{
Tags: map[string]string{
"Tag1": "Value1",
"Tag2": "Value2",
},
}

var allTagsAdded = map[string]string{
"Tag1": "Value1",
"Tag2": "Value2",
"GatewayId": "HoustonStore000123",
"Latitude": "29.630771",
"Longitude": "-95.377603",
}

func TestTags_AddTags(t *testing.T) {
appContext := appcontext.Context{
LoggingClient: logger.NewClientStdOut("Unit Test", false, "DEBUG"),
}

tests := []struct {
Name string
FunctionInput interface{}
TagsToAdd map[string]string
Expected map[string]string
ErrorExpected bool
ErrorContains string
}{
{"Happy path - no existing Event tags", models.Event{}, tagsToAdd, tagsToAdd, false, ""},
{"Happy path - Event has existing tags", eventWithExistingTags, tagsToAdd, allTagsAdded, false, ""},
{"Happy path - No tags added", eventWithExistingTags, map[string]string{}, eventWithExistingTags.Tags, false, ""},
{"Error - No data", nil, nil, nil, true, "no Event Received"},
{"Error - Input not event", "Not an Event", nil, nil, true, "not an Event"},
}

for _, testCase := range tests {
t.Run(testCase.Name, func(t *testing.T) {
var continuePipeline bool
var result interface{}

target := NewTags(testCase.TagsToAdd)

if testCase.FunctionInput != nil {
continuePipeline, result = target.AddTags(&appContext, testCase.FunctionInput)
} else {
continuePipeline, result = target.AddTags(&appContext)
}

if testCase.ErrorExpected {
err := result.(error)
require.Error(t, err)
assert.Contains(t, err.Error(), testCase.ErrorContains)
require.False(t, continuePipeline)
return // Test completed
}

assert.True(t, continuePipeline)
actual, ok := result.(models.Event)
require.True(t, ok, "Result not an Event")
assert.Equal(t, testCase.Expected, actual.Tags)
})
}
}
9 changes: 7 additions & 2 deletions pkg/util/helpers.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2019 Intel Corporation
// Copyright (c) 2020 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -22,11 +22,16 @@ import (
"strings"
)

//SplitComma - use custom split func instead of .Split to eliminate empty values (i.e Test,,,)
//SplitComma - use custom split func, on commas, instead of .Split to eliminate empty values (i.e Test,,,)
func SplitComma(c rune) bool {
return c == ','
}

//SplitColon - use custom split func, on colons, instead of .Split to eliminate empty values (i.e Test,,,)
func SplitColon(c rune) bool {
return c == ':'
}

//DeleteEmptyAndTrim removes empty strings from a slice
func DeleteEmptyAndTrim(s []string) []string {
var r []string
Expand Down

0 comments on commit acb6ca3

Please sign in to comment.