Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add resource, command, and device tags to reading/event #1297

Merged
merged 1 commit into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions internal/common/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,21 @@ import (
"fmt"
"time"

bootstrapInterfaces "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/interfaces"
"github.com/edgexfoundry/go-mod-bootstrap/v3/config"
gometrics "github.com/rcrowley/go-metrics"

"github.com/edgexfoundry/device-sdk-go/v3/internal/cache"
"github.com/edgexfoundry/device-sdk-go/v3/internal/container"

bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container"
bootstrapInterfaces "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/interfaces"
"github.com/edgexfoundry/go-mod-bootstrap/v3/config"
"github.com/edgexfoundry/go-mod-bootstrap/v3/di"
"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/interfaces"
"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger"
"github.com/edgexfoundry/go-mod-core-contracts/v3/common"
"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos"
"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/requests"
"github.com/edgexfoundry/go-mod-messaging/v3/pkg/types"

gometrics "github.com/rcrowley/go-metrics"
)

const (
Expand Down Expand Up @@ -122,3 +123,33 @@ func registerMetric(metricsManager bootstrapInterfaces.MetricsManager, lc logger
lc.Debugf("%s metric has been registered and will be reported (if enabled)", name)
}
}

func AddEventTags(event *dtos.Event) {
if event.Tags == nil {
event.Tags = make(map[string]interface{})
}
cmd, cmdExist := cache.Profiles().DeviceCommand(event.ProfileName, event.SourceName)
if cmdExist && len(cmd.Tags) > 0 {
for k, v := range cmd.Tags {
event.Tags[k] = v
}
}
device, deviceExist := cache.Devices().ForName(event.DeviceName)
if deviceExist && len(device.Tags) > 0 {
for k, v := range device.Tags {
event.Tags[k] = v
}
}
}

func AddReadingTags(reading *dtos.BaseReading) {
dr, drExist := cache.Profiles().DeviceResource(reading.ProfileName, reading.ResourceName)
if drExist && len(dr.Tags) > 0 {
if reading.Tags == nil {
reading.Tags = make(map[string]interface{})
}
for k, v := range dr.Tags {
reading.Tags[k] = v
}
}
}
lenny-goodell marked this conversation as resolved.
Show resolved Hide resolved
180 changes: 170 additions & 10 deletions internal/common/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,57 @@
package common

import (
"context"
"errors"
"testing"

bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container"
"github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/interfaces/mocks"
"github.com/edgexfoundry/go-mod-bootstrap/v3/di"
clientMocks "github.com/edgexfoundry/go-mod-core-contracts/v3/clients/interfaces/mocks"
"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger"
mocks2 "github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger/mocks"
"github.com/edgexfoundry/go-mod-core-contracts/v3/common"
"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos"
"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/requests"
"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/responses"
msgMocks "github.com/edgexfoundry/go-mod-messaging/v3/messaging/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"

"github.com/edgexfoundry/device-sdk-go/v3/internal/cache"
"github.com/edgexfoundry/device-sdk-go/v3/internal/config"
"github.com/edgexfoundry/device-sdk-go/v3/internal/container"

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

const (
TestDevice = "testDevice"
TestProfile = "testProfile"
TestDeviceResource = "testResource"
TestDeviceCommand = "testCommand"
testUUIDString = "ca93c8fa-9919-4ec5-85d3-f81b2b6a7bc1"
TestDeviceService = "testDeviceService"
TestDeviceWithTags = "testDeviceWithTags"
TestDeviceWithoutTags = "testDeviceWithoutTags"
TestProfile = "testProfile"
TestDeviceResourceWithTags = "testResourceWithTags"
TestDeviceResourceWithoutTags = "testResourceWithoutTags"
TestDeviceCommandWithTags = "testCommandWithTags"
TestDeviceCommandWithoutTags = "testCommandWithoutTags"
TestResourceTagName = "testResourceTagName"
TestResourceTagValue = "testResourceTagValue"
TestCommandTagName = "testCommandTagName"
TestCommandTagValue = "testCommandTagValue"
TestDeviceTagName = "testDeviceTagName"
TestDeviceTagValue = "testDeviceTagValue"
TestDuplicateTagName = "testDuplicateTagName"

testUUIDString = "ca93c8fa-9919-4ec5-85d3-f81b2b6a7bc1"
)

var TestProtocols map[string]dtos.ProtocolProperties

func buildEvent() dtos.Event {
event := dtos.NewEvent(TestProfile, TestDevice, TestDeviceCommand)
event := dtos.NewEvent(TestProfile, TestDeviceWithTags, TestDeviceCommandWithTags)
value := string(make([]byte, 1000))
_ = event.AddSimpleReading(TestDeviceResource, common.ValueTypeString, value)
_ = event.AddSimpleReading(TestDeviceResourceWithTags, common.ValueTypeString, value)
event.Id = testUUIDString
event.Readings[0].Id = testUUIDString
return event
Expand All @@ -50,13 +67,75 @@ func NewMockDIC() *di.Container {
Device: config.DeviceInfo{MaxCmdOps: 1},
}

devices := responses.MultiDevicesResponse{
Devices: []dtos.Device{
{
Name: TestDeviceWithTags,
ProfileName: TestProfile,
Tags: dtos.Tags{
TestDeviceTagName: TestDeviceTagValue,
TestDuplicateTagName: TestDeviceTagValue,
},
},
{
Name: TestDeviceWithoutTags,
ProfileName: TestProfile,
},
},
}
dcMock := &clientMocks.DeviceClient{}
dcMock.On("DevicesByServiceName", context.Background(), TestDeviceService, 0, -1).Return(devices, nil)

profile := responses.DeviceProfileResponse{
Profile: dtos.DeviceProfile{
DeviceProfileBasicInfo: dtos.DeviceProfileBasicInfo{Name: TestProfile},
DeviceResources: []dtos.DeviceResource{
{
Name: TestDeviceResourceWithTags,
Tags: dtos.Tags{
TestResourceTagName: TestResourceTagValue,
},
},
{
Name: TestDeviceResourceWithoutTags,
},
},
DeviceCommands: []dtos.DeviceCommand{
{
Name: TestDeviceCommandWithTags,
Tags: dtos.Tags{
TestCommandTagName: TestCommandTagValue,
TestDuplicateTagName: TestCommandTagValue,
},
},
{
Name: TestDeviceCommandWithoutTags,
},
},
},
}
dpcMock := &clientMocks.DeviceProfileClient{}
dpcMock.On("DeviceProfileByName", context.Background(), TestProfile).Return(profile, nil)

pwcMock := &clientMocks.ProvisionWatcherClient{}
pwcMock.On("ProvisionWatchersByServiceName", context.Background(), TestDeviceService, 0, -1).Return(responses.MultiProvisionWatchersResponse{}, nil)

return di.NewContainer(di.ServiceConstructorMap{
bootstrapContainer.LoggingClientInterfaceName: func(get di.Get) interface{} {
return logger.NewMockClient()
},
container.ConfigurationName: func(get di.Get) interface{} {
return configuration
},
bootstrapContainer.DeviceClientName: func(get di.Get) interface{} {
return dcMock
},
bootstrapContainer.DeviceProfileClientName: func(get di.Get) interface{} {
return dpcMock
},
bootstrapContainer.ProvisionWatcherClientName: func(get di.Get) interface{} {
return pwcMock
},
})
}

Expand Down Expand Up @@ -178,3 +257,84 @@ func TestInitializeSentMetrics(t *testing.T) {
})
}
}

func TestAddReadingTags(t *testing.T) {
dic := NewMockDIC()
edgexErr := cache.InitCache(TestDeviceService, dic)
require.NoError(t, edgexErr)
readingWithTags, err := dtos.NewSimpleReading(TestProfile, TestDeviceWithTags, TestDeviceResourceWithTags, common.ValueTypeString, "")
require.NoError(t, err)
readingWithoutTags, err := dtos.NewSimpleReading(TestProfile, TestDeviceWithTags, TestDeviceResourceWithoutTags, common.ValueTypeString, "")
require.NoError(t, err)
readingResourceNotFound, err := dtos.NewSimpleReading(TestProfile, TestDeviceWithTags, "notFound", common.ValueTypeString, "")
require.NoError(t, err)

tests := []struct {
Name string
Reading dtos.BaseReading
ExpectedTags dtos.Tags
}{
{"Happy Path", readingWithTags, dtos.Tags{TestResourceTagName: TestResourceTagValue}},
{"No Tags", readingWithoutTags, nil},
{"Resource Not Found", readingResourceNotFound, nil},
}

for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
AddReadingTags(&test.Reading)
if test.ExpectedTags != nil {
require.NotEmpty(t, test.Reading.Tags)
assert.Equal(t, test.ExpectedTags, test.Reading.Tags)
} else {
assert.Empty(t, test.Reading.Tags)
}
})
}
}

func TestAddEventTags(t *testing.T) {
dic := NewMockDIC()
edgexErr := cache.InitCache(TestDeviceService, dic)
require.NoError(t, edgexErr)

expectedDeviceTags := dtos.Tags{TestDeviceTagName: TestDeviceTagValue, TestDuplicateTagName: TestDeviceTagValue}
expectedCommandTags := dtos.Tags{TestCommandTagName: TestCommandTagValue, TestDuplicateTagName: TestCommandTagValue}

tests := []struct {
Name string
Event dtos.Event
ExpectToHaveDeviceTags bool
ExpectToHaveCommandTags bool
}{
{"Happy Path", dtos.NewEvent(TestProfile, TestDeviceWithTags, TestDeviceCommandWithTags), true, true},
{"No Tags", dtos.NewEvent(TestProfile, TestDeviceWithoutTags, TestDeviceCommandWithoutTags), false, false},
{"No Device Tags", dtos.NewEvent(TestProfile, TestDeviceWithoutTags, TestDeviceCommandWithTags), false, true},
{"No Command Tags", dtos.NewEvent(TestProfile, TestDeviceWithTags, TestDeviceCommandWithoutTags), true, false},
}

for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
AddEventTags(&test.Event)
if test.ExpectToHaveDeviceTags && test.ExpectToHaveCommandTags {
expectedCommandTags[TestDuplicateTagName] = TestDeviceTagValue
} else {
expectedCommandTags[TestDuplicateTagName] = TestCommandTagValue
}
if !test.ExpectToHaveDeviceTags && !test.ExpectToHaveCommandTags {
require.Empty(t, test.Event.Tags)
} else {
require.NotEmpty(t, test.Event.Tags)
}
if test.ExpectToHaveDeviceTags {
assert.Subset(t, test.Event.Tags, expectedDeviceTags)
} else {
assert.NotSubset(t, test.Event.Tags, expectedDeviceTags)
}
if test.ExpectToHaveCommandTags {
assert.Subset(t, test.Event.Tags, expectedCommandTags)
} else {
assert.NotSubset(t, test.Event.Tags, expectedCommandTags)
}
})
}
}
5 changes: 4 additions & 1 deletion internal/transformer/transform.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (C) 2021 IOTech Ltd
// Copyright (C) 2021-2023 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

Expand All @@ -11,6 +11,7 @@ import (
"time"

"github.com/edgexfoundry/device-sdk-go/v3/internal/cache"
sdkCommon "github.com/edgexfoundry/device-sdk-go/v3/internal/common"
"github.com/edgexfoundry/device-sdk-go/v3/internal/container"
"github.com/edgexfoundry/device-sdk-go/v3/pkg/models"

Expand Down Expand Up @@ -113,6 +114,7 @@ func CommandValuesToEventDTO(cvs []*models.CommandValue, deviceName string, sour
if config.Writable.Reading.ReadingUnits {
reading.Units = dr.Properties.Units
}
sdkCommon.AddReadingTags(&reading)
readings = append(readings, reading)

if cv.Type == common.ValueTypeBinary {
Expand All @@ -131,6 +133,7 @@ func CommandValuesToEventDTO(cvs []*models.CommandValue, deviceName string, sour
eventDTO.Readings = readings
eventDTO.Origin = origin
eventDTO.Tags = tags
sdkCommon.AddEventTags(&eventDTO)

return &eventDTO, nil
} else {
Expand Down