Skip to content

Commit

Permalink
feat(notifications): Implement GET /notification/start/{start}/end/{e…
Browse files Browse the repository at this point in the history
…nd} V2 API (#3240)

Per https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/support-notifications/2.x#/default/get_notification_start__start__end__end_, implemented GET /notification/start/{start}/end/{end} V2 API.

Signed-off-by: Felix Ting <felix@iotechsys.com>

Co-authored-by: Lenny Goodell <44779287+lenny-intel@users.noreply.github.com>
  • Loading branch information
FelixTing and lenny-goodell committed Mar 10, 2021
1 parent dd3c530 commit e1c877c
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 0 deletions.
13 changes: 13 additions & 0 deletions internal/pkg/v2/infrastructure/redis/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -908,3 +908,16 @@ func (c *Client) NotificationsByStatus(offset int, limit int, status string) (no
}
return notifications, nil
}

// NotificationsByTimeRange query notifications by time range, offset, and limit
func (c *Client) NotificationsByTimeRange(start int, end int, offset int, limit int) (notifications []model.Notification, edgeXerr errors.EdgeX) {
conn := c.Pool.Get()
defer conn.Close()

notifications, edgeXerr = notificationsByTimeRange(conn, start, end, offset, limit)
if edgeXerr != nil {
return notifications, errors.NewCommonEdgeX(errors.Kind(edgeXerr),
fmt.Sprintf("fail to query notifications by time range %v ~ %v, offset %d, and limit %d", start, end, offset, limit), edgeXerr)
}
return notifications, nil
}
11 changes: 11 additions & 0 deletions internal/pkg/v2/infrastructure/redis/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
NotificationCollectionSender = NotificationCollection + DBKeySeparator + v2.Sender
NotificationCollectionSeverity = NotificationCollection + DBKeySeparator + v2.Severity
NotificationCollectionStatus = NotificationCollection + DBKeySeparator + v2.Status
NotificationCollectionCreated = NotificationCollection + DBKeySeparator + v2.Created
)

// notificationStoredKey return the notification's stored key which combines the collection name and object id
Expand Down Expand Up @@ -56,6 +57,7 @@ func addNotification(conn redis.Conn, notification models.Notification) (models.
_ = conn.Send(MULTI)
_ = conn.Send(SET, redisKey, notifJSONBytes)
_ = conn.Send(ZADD, NotificationCollection, 0, redisKey)
_ = conn.Send(ZADD, NotificationCollectionCreated, notification.Created, redisKey)
if len(notification.Category) > 0 {
_ = conn.Send(ZADD, CreateKey(NotificationCollectionCategory, notification.Category), notification.Modified, redisKey)
}
Expand Down Expand Up @@ -136,3 +138,12 @@ func notificationsByStatus(conn redis.Conn, offset int, limit int, status string

return convertObjectsToNotifications(objects)
}

// notificationsByTimeRange query notifications by time range, offset, and limit
func notificationsByTimeRange(conn redis.Conn, start int, end int, offset int, limit int) (notifications []models.Notification, edgeXerr errors.EdgeX) {
objects, edgeXerr := getObjectsByScoreRange(conn, NotificationCollectionCreated, start, end, offset, limit)
if edgeXerr != nil {
return notifications, edgeXerr
}
return convertObjectsToNotifications(objects)
}
14 changes: 14 additions & 0 deletions internal/support/notifications/v2/application/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,17 @@ func NotificationsByStatus(offset, limit int, status string, dic *di.Container)
}
return notifications, nil
}

// NotificationsByTimeRange query notifications with offset, limit and time range
func NotificationsByTimeRange(start int, end int, offset int, limit int, dic *di.Container) (notifications []dtos.Notification, err errors.EdgeX) {
dbClient := v2NotificationsContainer.DBClientFrom(dic.Get)
notificationModels, err := dbClient.NotificationsByTimeRange(start, end, offset, limit)
if err != nil {
return notifications, errors.NewCommonEdgeXWrapper(err)
}
notifications = make([]dtos.Notification, len(notificationModels))
for i, e := range notificationModels {
notifications[i] = dtos.FromNotificationModelToDTO(e)
}
return notifications, nil
}
35 changes: 35 additions & 0 deletions internal/support/notifications/v2/controller/http/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,38 @@ func (nc *NotificationController) NotificationsByStatus(w http.ResponseWriter, r
utils.WriteHttpHeader(w, ctx, statusCode)
pkg.Encode(response, w, lc)
}

func (nc *NotificationController) NotificationsByTimeRange(w http.ResponseWriter, r *http.Request) {
lc := container.LoggingClientFrom(nc.dic.Get)
ctx := r.Context()
correlationId := correlation.FromContext(ctx)
config := notificationContainer.ConfigurationFrom(nc.dic.Get)

var response interface{}
var statusCode int

// parse time range (start, end), offset, and limit from incoming request
start, end, offset, limit, err := utils.ParseTimeRangeOffsetLimit(r, 0, math.MaxInt32, -1, config.Service.MaxResultCount)
if err != nil {
lc.Error(err.Error(), clients.CorrelationHeader, correlationId)
lc.Debug(err.DebugMessages(), clients.CorrelationHeader, correlationId)
response = commonDTO.NewBaseResponse("", err.Message(), err.Code())
statusCode = err.Code()
} else {
notifications, err := application.NotificationsByTimeRange(start, end, offset, limit, nc.dic)
if err != nil {
if errors.Kind(err) != errors.KindEntityDoesNotExist {
lc.Error(err.Error(), clients.CorrelationHeader, correlationId)
}
lc.Debug(err.DebugMessages(), clients.CorrelationHeader, correlationId)
response = commonDTO.NewBaseResponse("", err.Message(), err.Code())
statusCode = err.Code()
} else {
response = responseDTO.NewMultiNotificationsResponse("", "", http.StatusOK, notifications)
statusCode = http.StatusOK
}
}

utils.WriteHttpHeader(w, ctx, statusCode)
pkg.Encode(response, w, lc)
}
Original file line number Diff line number Diff line change
Expand Up @@ -411,3 +411,72 @@ func TestNotificationsByStatus(t *testing.T) {
})
}
}

func TestNotificationsByTimeRange(t *testing.T) {
dic := mockDic()
dbClientMock := &dbMock.DBClient{}
dbClientMock.On("NotificationsByTimeRange", 0, 100, 0, 10).Return([]models.Notification{}, nil)
dic.Update(di.ServiceConstructorMap{
v2NotificationsContainer.DBClientInterfaceName: func(get di.Get) interface{} {
return dbClientMock
},
})
nc := NewNotificationController(dic)
assert.NotNil(t, nc)

tests := []struct {
name string
start string
end string
offset string
limit string
errorExpected bool
expectedCount int
expectedStatusCode int
}{
{"Valid - with proper start/end/offset/limit", "0", "100", "0", "10", false, 0, http.StatusOK},
{"Invalid - invalid start format", "aaa", "100", "0", "10", true, 0, http.StatusBadRequest},
{"Invalid - invalid end format", "0", "bbb", "0", "10", true, 0, http.StatusBadRequest},
{"Invalid - empty start", "", "100", "0", "10", true, 0, http.StatusBadRequest},
{"Invalid - empty end", "0", "", "0", "10", true, 0, http.StatusBadRequest},
{"Invalid - end before start", "10", "0", "0", "10", true, 0, http.StatusBadRequest},
{"Invalid - invalid offset format", "0", "100", "aaa", "10", true, 0, http.StatusBadRequest},
{"Invalid - invalid limit format", "0", "100", "0", "aaa", true, 0, http.StatusBadRequest},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, v2.ApiNotificationByTimeRangeRoute, http.NoBody)
query := req.URL.Query()
query.Add(v2.Offset, testCase.offset)
query.Add(v2.Limit, testCase.limit)
req.URL.RawQuery = query.Encode()
req = mux.SetURLVars(req, map[string]string{v2.Start: testCase.start, v2.End: testCase.end})
require.NoError(t, err)

// Act
recorder := httptest.NewRecorder()
handler := http.HandlerFunc(nc.NotificationsByTimeRange)
handler.ServeHTTP(recorder, req)

// Assert
if testCase.errorExpected {
var res common.BaseResponse
err = json.Unmarshal(recorder.Body.Bytes(), &res)
require.NoError(t, err)
assert.Equal(t, v2.ApiVersion, res.ApiVersion, "API Version not as expected")
assert.Equal(t, testCase.expectedStatusCode, recorder.Result().StatusCode, "HTTP status code not as expected")
assert.Equal(t, testCase.expectedStatusCode, int(res.StatusCode), "Response status code not as expected")
assert.NotEmpty(t, res.Message, "Response message doesn't contain the error message")
} else {
var res responseDTO.MultiNotificationsResponse
err = json.Unmarshal(recorder.Body.Bytes(), &res)
require.NoError(t, err)
assert.Equal(t, v2.ApiVersion, res.ApiVersion, "API Version not as expected")
assert.Equal(t, testCase.expectedStatusCode, recorder.Result().StatusCode, "HTTP status code not as expected")
assert.Equal(t, testCase.expectedStatusCode, int(res.StatusCode), "Response status code not as expected")
assert.Equal(t, testCase.expectedCount, len(res.Notifications), "Device count not as expected")
assert.Empty(t, res.Message, "Message should be empty when it is successful")
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ type DBClient interface {
NotificationsByCategory(offset, limit int, category string) ([]models.Notification, errors.EdgeX)
NotificationsByLabel(offset, limit int, label string) ([]models.Notification, errors.EdgeX)
NotificationsByStatus(offset, limit int, status string) ([]models.Notification, errors.EdgeX)
NotificationsByTimeRange(start int, end int, offset int, limit int) ([]models.Notification, errors.EdgeX)
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions internal/support/notifications/v2/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ func LoadRestRoutes(r *mux.Router, dic *di.Container) {
r.HandleFunc(v2Constant.ApiNotificationByCategoryRoute, nc.NotificationsByCategory).Methods(http.MethodGet)
r.HandleFunc(v2Constant.ApiNotificationByLabelRoute, nc.NotificationsByLabel).Methods(http.MethodGet)
r.HandleFunc(v2Constant.ApiNotificationByStatusRoute, nc.NotificationsByStatus).Methods(http.MethodGet)
r.HandleFunc(v2Constant.ApiNotificationByTimeRangeRoute, nc.NotificationsByTimeRange).Methods(http.MethodGet)
}

0 comments on commit e1c877c

Please sign in to comment.