Skip to content

Commit c89ea64

Browse files
feat: Implement transform to add Tags to Event (#467)
closes #455 & #369 Signed-off-by: lenny <[email protected]>
1 parent 7cb694d commit c89ea64

File tree

9 files changed

+284
-13
lines changed

9 files changed

+284
-13
lines changed

appsdk/configurable.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const (
5252
ClientID = "clientid"
5353
Topic = "topic"
5454
AuthMode = "authmode"
55+
Tags = "tags"
5556
)
5657

5758
// AppFunctionsSDKConfigurable contains the helper functions that return the function pointers for building the configurable function pipeline.
@@ -523,3 +524,40 @@ func (dynamic AppFunctionsSDKConfigurable) MQTTSecretSend(parameters map[string]
523524
transform := transforms.NewMQTTSecretSender(mqttConfig, persistOnError)
524525
return transform.MQTTSend
525526
}
527+
528+
// AddTags adds the configured list of tags to Events passed to the transform.
529+
// This function is a configuration function and returns a function pointer.
530+
func (dynamic AppFunctionsSDKConfigurable) AddTags(parameters map[string]string) appcontext.AppFunction {
531+
tagsSpec, ok := parameters[Tags]
532+
if !ok {
533+
dynamic.Sdk.LoggingClient.Error(fmt.Sprintf("Could not find '%s' parameter", Tags))
534+
return nil
535+
}
536+
537+
tagKeyValues := util.DeleteEmptyAndTrim(strings.FieldsFunc(tagsSpec, util.SplitComma))
538+
539+
tags := make(map[string]string)
540+
for _, tag := range tagKeyValues {
541+
keyValue := util.DeleteEmptyAndTrim(strings.FieldsFunc(tag, util.SplitColon))
542+
if len(keyValue) != 2 {
543+
dynamic.Sdk.LoggingClient.Error(fmt.Sprintf("Bad Tags specification format. Expect comma separated list of 'key:value'. Got `%s`", tagsSpec))
544+
return nil
545+
}
546+
547+
if len(keyValue[0]) == 0 {
548+
dynamic.Sdk.LoggingClient.Error(fmt.Sprintf("Tag key missing. Got '%s'", tag))
549+
return nil
550+
}
551+
if len(keyValue[1]) == 0 {
552+
dynamic.Sdk.LoggingClient.Error(fmt.Sprintf("Tag value missing. Got '%s'", tag))
553+
return nil
554+
}
555+
556+
tags[keyValue[0]] = keyValue[1]
557+
}
558+
559+
transform := transforms.NewTags(tags)
560+
dynamic.Sdk.LoggingClient.Debug("Add Tags", Tags, fmt.Sprintf("%v", tags))
561+
562+
return transform.AddTags
563+
}

appsdk/configurable_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,36 @@ func TestConfigurableMQTTSecretSend(t *testing.T) {
266266
trx := configurable.MQTTSecretSend(params)
267267
assert.NotNil(t, trx, "return result from MQTTSend should not be nil")
268268
}
269+
270+
func TestAppFunctionsSDKConfigurable_AddTags(t *testing.T) {
271+
configurable := AppFunctionsSDKConfigurable{
272+
Sdk: &AppFunctionsSDK{
273+
LoggingClient: lc,
274+
},
275+
}
276+
277+
tests := []struct {
278+
Name string
279+
ParamName string
280+
TagsSpec string
281+
ExpectNil bool
282+
}{
283+
{"Good - non-empty list", Tags, "GatewayId:HoustonStore000123,Latitude:29.630771,Longitude:-95.377603", false},
284+
{"Good - empty list", Tags, "", false},
285+
{"Bad - No : separator", Tags, "GatewayId HoustonStore000123, Latitude:29.630771,Longitude:-95.377603", true},
286+
{"Bad - Missing value", Tags, "GatewayId:,Latitude:29.630771,Longitude:-95.377603", true},
287+
{"Bad - Missing key", Tags, "GatewayId:HoustonStore000123,:29.630771,Longitude:-95.377603", true},
288+
{"Bad - Missing key & value", Tags, ":,:,:", true},
289+
{"Bad - No Tags parameter", "NotTags", ":,:,:", true},
290+
}
291+
292+
for _, testCase := range tests {
293+
t.Run(testCase.Name, func(t *testing.T) {
294+
params := make(map[string]string)
295+
params[testCase.ParamName] = testCase.TagsSpec
296+
297+
transform := configurable.AddTags(params)
298+
assert.Equal(t, testCase.ExpectNil, transform == nil)
299+
})
300+
}
301+
}

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
github.com/diegoholiveira/jsonlogic v1.0.1-0.20200220175622-ab7989be08b9
88
github.com/eclipse/paho.mqtt.golang v1.2.0
99
github.com/edgexfoundry/go-mod-bootstrap v0.0.37
10-
github.com/edgexfoundry/go-mod-core-contracts v0.1.72
10+
github.com/edgexfoundry/go-mod-core-contracts v0.1.75
1111
github.com/edgexfoundry/go-mod-messaging v0.1.21
1212
github.com/edgexfoundry/go-mod-registry v0.1.21
1313
github.com/edgexfoundry/go-mod-secrets v0.0.19
@@ -23,4 +23,4 @@ require (
2323
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect
2424
github.com/xdg/stringprep v1.0.0 // indirect
2525
go.mongodb.org/mongo-driver v1.1.1
26-
)
26+
)

pkg/transforms/batch.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
//
2+
// Copyright (c) 2020 Intel Corporation
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
117
package transforms
218

319
import (

pkg/transforms/conversion.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (c) 2019 Intel Corporation
2+
// Copyright (c) 2020 Intel Corporation
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -18,8 +18,8 @@ package transforms
1818

1919
import (
2020
"encoding/json"
21-
"encoding/xml"
2221
"errors"
22+
"fmt"
2323

2424
"github.com/edgexfoundry/app-functions-sdk-go/appcontext"
2525
"github.com/edgexfoundry/go-mod-core-contracts/models"
@@ -41,15 +41,12 @@ func (f Conversion) TransformToXML(edgexcontext *appcontext.Context, params ...i
4141
return false, errors.New("No Event Received")
4242
}
4343
edgexcontext.LoggingClient.Debug("Transforming to XML")
44-
if result, ok := params[0].(models.Event); ok {
45-
b, err := xml.Marshal(result)
44+
if event, ok := params[0].(models.Event); ok {
45+
xml, err := event.ToXML()
4646
if err != nil {
47-
// LoggingClient.Error(fmt.Sprintf("Error parsing XML. Error: %s", err.Error()))
48-
return false, errors.New("Incorrect type received, expecting models.Event")
47+
return false, fmt.Errorf("unable to marshal Event to XML: %s", err.Error())
4948
}
50-
// should we return a byte[] or string?
51-
// return b
52-
return true, string(b)
49+
return true, xml
5350
}
5451
return false, errors.New("Unexpected type received")
5552
}

pkg/transforms/jsonlogic.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
//
2+
// Copyright (c) 2020 Intel Corporation
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
117
package transforms
218

319
import (

pkg/transforms/tags.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// Copyright (c) 2020 Intel Corporation
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
package transforms
18+
19+
import (
20+
"errors"
21+
"fmt"
22+
23+
"github.com/edgexfoundry/go-mod-core-contracts/models"
24+
25+
"github.com/edgexfoundry/app-functions-sdk-go/appcontext"
26+
)
27+
28+
// Tags contains the list of Tag key/values
29+
type Tags struct {
30+
tags map[string]string
31+
}
32+
33+
// NewTags creates, initializes and returns a new instance of Tags
34+
func NewTags(tags map[string]string) Tags {
35+
return Tags{
36+
tags: tags,
37+
}
38+
}
39+
40+
// AddTags adds the pre-configured list of tags to the Event's tags collection.
41+
func (t *Tags) AddTags(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) {
42+
edgexcontext.LoggingClient.Debug("Adding tags to Event")
43+
44+
if len(params) < 1 {
45+
return false, errors.New("no Event Received")
46+
}
47+
48+
event, ok := params[0].(models.Event)
49+
if !ok {
50+
return false, errors.New("type received is not an Event")
51+
}
52+
53+
if len(t.tags) > 0 {
54+
if event.Tags == nil {
55+
event.Tags = make(map[string]string)
56+
}
57+
58+
for tag, value := range t.tags {
59+
event.Tags[tag] = value
60+
}
61+
edgexcontext.LoggingClient.Debug(fmt.Sprintf("Tags added to Event. Event tags=%v", event.Tags))
62+
} else {
63+
edgexcontext.LoggingClient.Debug("No tags added to Event. Add tags list is empty.")
64+
}
65+
66+
return true, event
67+
}

pkg/transforms/tags_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//
2+
// Copyright (c) 2020 Intel Corporation
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
package transforms
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
"github.com/stretchr/testify/require"
24+
25+
"github.com/edgexfoundry/go-mod-core-contracts/clients/logger"
26+
"github.com/edgexfoundry/go-mod-core-contracts/models"
27+
28+
"github.com/edgexfoundry/app-functions-sdk-go/appcontext"
29+
)
30+
31+
var tagsToAdd = map[string]string{
32+
"GatewayId": "HoustonStore000123",
33+
"Latitude": "29.630771",
34+
"Longitude": "-95.377603",
35+
}
36+
37+
var eventWithExistingTags = models.Event{
38+
Tags: map[string]string{
39+
"Tag1": "Value1",
40+
"Tag2": "Value2",
41+
},
42+
}
43+
44+
var allTagsAdded = map[string]string{
45+
"Tag1": "Value1",
46+
"Tag2": "Value2",
47+
"GatewayId": "HoustonStore000123",
48+
"Latitude": "29.630771",
49+
"Longitude": "-95.377603",
50+
}
51+
52+
func TestTags_AddTags(t *testing.T) {
53+
appContext := appcontext.Context{
54+
LoggingClient: logger.NewClientStdOut("Unit Test", false, "DEBUG"),
55+
}
56+
57+
tests := []struct {
58+
Name string
59+
FunctionInput interface{}
60+
TagsToAdd map[string]string
61+
Expected map[string]string
62+
ErrorExpected bool
63+
ErrorContains string
64+
}{
65+
{"Happy path - no existing Event tags", models.Event{}, tagsToAdd, tagsToAdd, false, ""},
66+
{"Happy path - Event has existing tags", eventWithExistingTags, tagsToAdd, allTagsAdded, false, ""},
67+
{"Happy path - No tags added", eventWithExistingTags, map[string]string{}, eventWithExistingTags.Tags, false, ""},
68+
{"Error - No data", nil, nil, nil, true, "no Event Received"},
69+
{"Error - Input not event", "Not an Event", nil, nil, true, "not an Event"},
70+
}
71+
72+
for _, testCase := range tests {
73+
t.Run(testCase.Name, func(t *testing.T) {
74+
var continuePipeline bool
75+
var result interface{}
76+
77+
target := NewTags(testCase.TagsToAdd)
78+
79+
if testCase.FunctionInput != nil {
80+
continuePipeline, result = target.AddTags(&appContext, testCase.FunctionInput)
81+
} else {
82+
continuePipeline, result = target.AddTags(&appContext)
83+
}
84+
85+
if testCase.ErrorExpected {
86+
err := result.(error)
87+
require.Error(t, err)
88+
assert.Contains(t, err.Error(), testCase.ErrorContains)
89+
require.False(t, continuePipeline)
90+
return // Test completed
91+
}
92+
93+
assert.True(t, continuePipeline)
94+
actual, ok := result.(models.Event)
95+
require.True(t, ok, "Result not an Event")
96+
assert.Equal(t, testCase.Expected, actual.Tags)
97+
})
98+
}
99+
}

pkg/util/helpers.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (c) 2019 Intel Corporation
2+
// Copyright (c) 2020 Intel Corporation
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -22,11 +22,16 @@ import (
2222
"strings"
2323
)
2424

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

30+
//SplitColon - use custom split func, on colons, instead of .Split to eliminate empty values (i.e Test,,,)
31+
func SplitColon(c rune) bool {
32+
return c == ':'
33+
}
34+
3035
//DeleteEmptyAndTrim removes empty strings from a slice
3136
func DeleteEmptyAndTrim(s []string) []string {
3237
var r []string

0 commit comments

Comments
 (0)