Skip to content

Commit

Permalink
Merge pull request #7041 from TheThingsNetwork/issue/7038-filter-user…
Browse files Browse the repository at this point in the history
…-bm-by-entity-type

Filter list Userbookmarks by entity_type
  • Loading branch information
nicholaspcr authored Apr 24, 2024
2 parents 10e4f6b + ea7e70c commit dc85330
Show file tree
Hide file tree
Showing 15 changed files with 257 additions and 48 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ For details about compatibility between different releases, see the **Commitment
### Added

- Support fine-grained NbTrans controls while using Dynamic ADR mode in the Console.
- User bookmark listing now supports filtering bookmarks by entity type.
- This can be specified by setting `entity_types` field in `ListUserBookmarksRequest`.

### Changed

Expand Down
2 changes: 2 additions & 0 deletions api/ttn/lorawan/v3/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10829,6 +10829,7 @@ Secret contains a secret value. It also contains the ID of the Encryption key us
| `page` | [`uint32`](#uint32) | | Page number for pagination. 0 is interpreted as 1. |
| `order` | [`string`](#string) | | Order the results by this field path. Default ordering is by ID. Prepend with a minus (-) to reverse the order. |
| `deleted` | [`bool`](#bool) | | Only return recently deleted bookmarks. |
| `entity_types` | [`string`](#string) | repeated | Specifies that only bookmarks that reference the given entity type should be returned. If empty the filter is not applied. |

#### Field Rules

Expand All @@ -10837,6 +10838,7 @@ Secret contains a secret value. It also contains the ID of the Encryption key us
| `user_ids` | <p>`message.required`: `true`</p> |
| `limit` | <p>`uint32.lte`: `1000`</p> |
| `order` | <p>`string.in`: `[ user_id -user_id entity_type -entity_type entity_id -entity_id created_at -created_at]`</p> |
| `entity_types` | <p>`repeated.unique`: `true`</p><p>`repeated.items.string.in`: `[application client end device gateway organization user]`</p> |

### <a name="ttn.lorawan.v3.ListUserSessionsRequest">Message `ListUserSessionsRequest`</a>

Expand Down
11 changes: 11 additions & 0 deletions api/ttn/lorawan/v3/api.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -13944,6 +13944,17 @@
"in": "query",
"required": false,
"type": "boolean"
},
{
"name": "entity_types",
"description": "Specifies that only bookmarks that reference the given entity type should be returned. If empty the filter is not\napplied.",
"in": "query",
"required": false,
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi"
}
],
"tags": [
Expand Down
19 changes: 19 additions & 0 deletions api/ttn/lorawan/v3/user.proto
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,25 @@ message ListUserBookmarksRequest {

// Only return recently deleted bookmarks.
bool deleted = 5;

// Specifies that only bookmarks that reference the given entity type should be returned. If empty the filter is not
// applied.
repeated string entity_types = 6 [(validate.rules).repeated = {
unique: true,
items: {
string: {
// These values are the same as what is specified in the pkg/ttnpb/identifiers_polymorphism
in: [
"application",
"client",
"end device",
"gateway",
"organization",
"user"
]
}
}
}];
}

message DeleteUserBookmarkRequest {
Expand Down
26 changes: 24 additions & 2 deletions pkg/identityserver/bunstore/user_bookmarks_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,21 @@ func (*userBookmarkStore) selectWithUserID(
}
}

func (*userBookmarkStore) selectWithEntityType(
_ context.Context, ids ...string,
) func(*bun.SelectQuery) *bun.SelectQuery {
return func(q *bun.SelectQuery) *bun.SelectQuery {
switch len(ids) {
case 0:
return q
case 1:
return q.Where("?TableAlias.entity_type = ?", ids[0])
default:
return q.Where("?TableAlias.entity_type IN (?)", bun.In(ids))
}
}
}

func (*userBookmarkStore) selectWithEntityID(
ctx context.Context, ids ...ttnpb.IDStringer,
) func(*bun.SelectQuery) *bun.SelectQuery {
Expand Down Expand Up @@ -213,14 +228,21 @@ func (s *userBookmarkStore) CreateBookmark(
}

func (s *userBookmarkStore) FindBookmarks(
ctx context.Context, id *ttnpb.UserIdentifiers,
ctx context.Context, id *ttnpb.UserIdentifiers, entityTypes ...string,
) ([]*ttnpb.UserBookmark, error) {
ctx, span := tracer.StartFromContext(ctx, "FindBookmarks", trace.WithAttributes(
attribute.String("user_id", id.GetUserId()),
))
defer span.End()

models, err := s.listUserBookmarksBy(ctx, s.selectWithUserID(ctx, id.GetUserId()))
models, err := s.listUserBookmarksBy(
ctx,
func(sq *bun.SelectQuery) *bun.SelectQuery {
sq = s.selectWithUserID(ctx, id.GetUserId())(sq)
sq = s.selectWithEntityType(ctx, entityTypes...)(sq)
return sq
},
)
if err != nil {
return nil, err
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/identityserver/store/store_interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,9 @@ type UserSessionStore interface {
// UserBookmarkStore interface for storing user bookmarks.
type UserBookmarkStore interface {
CreateBookmark(context.Context, *ttnpb.UserBookmark) (*ttnpb.UserBookmark, error)
FindBookmarks(context.Context, *ttnpb.UserIdentifiers) ([]*ttnpb.UserBookmark, error)
FindBookmarks(
ctx context.Context, usrID *ttnpb.UserIdentifiers, entityTypes ...string,
) ([]*ttnpb.UserBookmark, error)
PurgeBookmark(context.Context, *ttnpb.UserBookmark) error
BatchPurgeBookmarks(
context.Context, *ttnpb.UserIdentifiers, []*ttnpb.EntityIdentifiers,
Expand Down
15 changes: 15 additions & 0 deletions pkg/identityserver/storetest/user_bookmarks_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,21 @@ func (st *StoreTest) TestBasicBookmarkOperations(t *testing.T) { // nolint:paral
a.So(bookmarks, should.HaveLength, len(entityIDs))
})

t.Run("FindBoomarks_FilterByEntityType", func(t *testing.T) { // nolint:paralleltest
a, ctx := test.New(t)

bookmarks, err := s.FindBookmarks(ctx, usr1.GetIds(), "organization")
a.So(err, should.BeNil)
// There is one of each entity type, so by filtering the by 'organization' it should return one bookmark.
a.So(bookmarks, should.HaveLength, 1)

bookmarks, err = s.FindBookmarks(ctx, usr1.GetIds(), "application", "end device")
a.So(err, should.BeNil)
// There is one of each entity type, so by filtering the by ['application','end device'] and it should return
// only two bookmarks.
a.So(bookmarks, should.HaveLength, 2)
})

t.Run("PurgeBookmark", func(t *testing.T) { // nolint:paralleltest
a, ctx := test.New(t)
err := s.PurgeBookmark(ctx, &ttnpb.UserBookmark{
Expand Down
2 changes: 1 addition & 1 deletion pkg/identityserver/user_bookmark_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (is *IdentityServer) listUserBookmarks(
}
}()

bookmarks, err := is.store.FindBookmarks(ctx, req.UserIds)
bookmarks, err := is.store.FindBookmarks(ctx, req.UserIds, req.EntityTypes...)
if err != nil {
return nil, err
}
Expand Down
36 changes: 36 additions & 0 deletions pkg/identityserver/user_bookmark_registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,42 @@ func TestUsersBookmarksOperations(t *testing.T) {
a.So(got, should.BeNil)
a.So(errors.IsPermissionDenied(err), should.BeTrue)
})
t.Run("FindBookmarks/HasEntityType", func(t *testing.T) { // nolint:paralleltest
a, ctx := test.New(t)

// Invalid entity type.
got, err := reg.List(ctx, &ttnpb.ListUserBookmarksRequest{
UserIds: usr1.Ids,
EntityTypes: []string{"invalid"},
}, creds)
a.So(got, should.BeNil)
a.So(errors.IsInvalidArgument(err), should.BeTrue)

// Entity type without bookmarks.
got, err = reg.List(ctx, &ttnpb.ListUserBookmarksRequest{
UserIds: usr1.Ids,
EntityTypes: []string{"organization"},
}, creds)
a.So(got, should.NotBeNil)
a.So(got.GetBookmarks(), should.HaveLength, 0)
a.So(err, should.BeNil)

// Entity type with bookmarks.
got, err = reg.List(ctx, &ttnpb.ListUserBookmarksRequest{
UserIds: usr1.Ids,
EntityTypes: []string{"application"},
}, creds)
a.So(got.GetBookmarks(), should.HaveLength, 2)
a.So(err, should.BeNil)

// Multiple entity types in request
got, err = reg.List(ctx, &ttnpb.ListUserBookmarksRequest{
UserIds: usr1.Ids,
EntityTypes: []string{"application", "organization", "gateway"},
}, creds)
a.So(got.GetBookmarks(), should.HaveLength, 2)
a.So(err, should.BeNil)
})
t.Run("FindBookmarks", func(t *testing.T) { // nolint:paralleltest
a, ctx := test.New(t)
got, err := reg.List(ctx, &ttnpb.ListUserBookmarksRequest{
Expand Down
105 changes: 61 additions & 44 deletions pkg/ttnpb/user.pb.go

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

2 changes: 2 additions & 0 deletions pkg/ttnpb/user.pb.paths.fm.go

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

9 changes: 9 additions & 0 deletions pkg/ttnpb/user.pb.setters.fm.go

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

Loading

0 comments on commit dc85330

Please sign in to comment.