MG-2287 - Improve users search (#2288)

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
This commit is contained in:
Steve Munene
2024-07-02 13:04:37 +03:00
committed by GitHub
parent 6d11c54874
commit 86d896dd44
22 changed files with 310 additions and 73 deletions
+2 -1
View File
@@ -159,7 +159,8 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) {
errors.Contains(err, apiutil.ErrInvalidDirection),
errors.Contains(err, apiutil.ErrInvalidEntityType),
errors.Contains(err, apiutil.ErrMissingEntityType),
errors.Contains(err, apiutil.ErrInvalidTimeFormat):
errors.Contains(err, apiutil.ErrInvalidTimeFormat),
errors.Contains(err, svcerr.ErrSearch):
err = unwrap(err)
w.WriteHeader(http.StatusBadRequest)
+2 -2
View File
@@ -78,8 +78,8 @@ type Repository interface {
// RetrieveAll retrieves all clients.
RetrieveAll(ctx context.Context, pm Page) (ClientsPage, error)
// RetrieveAllBasicInfo list all clients only with basic information.
RetrieveAllBasicInfo(ctx context.Context, pm Page) (ClientsPage, error)
// SearchBasicInfo list all clients only with basic information.
SearchBasicInfo(ctx context.Context, pm Page) (ClientsPage, error)
// RetrieveAllByIDs retrieves for given client IDs .
RetrieveAllByIDs(ctx context.Context, pm Page) (ClientsPage, error)
+1
View File
@@ -9,6 +9,7 @@ type Page struct {
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
Name string `json:"name,omitempty"`
Id string `json:"id,omitempty"`
Order string `json:"order,omitempty"`
Dir string `json:"dir,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
+35 -9
View File
@@ -190,8 +190,8 @@ func (repo *Repository) RetrieveAll(ctx context.Context, pm clients.Page) (clien
return page, nil
}
func (repo *Repository) RetrieveAllBasicInfo(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
sq, tq := constructSearchQuery(pm)
func (repo *Repository) SearchBasicInfo(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
sq, tq := ConstructSearchQuery(pm)
q := fmt.Sprintf(`SELECT c.id, c.name, c.created_at, c.updated_at FROM clients c %s LIMIT :limit OFFSET :offset;`, sq)
@@ -334,6 +334,24 @@ func (repo *Repository) Delete(ctx context.Context, id string) error {
return nil
}
func (repo *Repository) CheckSuperAdmin(ctx context.Context, adminID string) error {
q := "SELECT 1 FROM clients WHERE id = $1 AND role = $2"
rows, err := repo.DB.QueryContext(ctx, q, adminID, clients.AdminRole)
if err != nil {
return postgres.HandleError(repoerr.ErrViewEntity, err)
}
defer rows.Close()
if rows.Next() {
if err := rows.Err(); err != nil {
return postgres.HandleError(repoerr.ErrViewEntity, err)
}
return nil
}
return repoerr.ErrNotFound
}
type DBClient struct {
ID string `db:"id"`
Name string `db:"name,omitempty"`
@@ -437,6 +455,7 @@ func ToDBClientsPage(pm clients.Page) (dbClientsPage, error) {
return dbClientsPage{
Name: pm.Name,
Identity: pm.Identity,
Id: pm.Id,
Metadata: data,
Domain: pm.Domain,
Total: pm.Total,
@@ -453,6 +472,7 @@ type dbClientsPage struct {
Limit uint64 `db:"limit"`
Offset uint64 `db:"offset"`
Name string `db:"name"`
Id string `db:"id"`
Domain string `db:"domain_id"`
Identity string `db:"identity"`
Metadata []byte `db:"metadata"`
@@ -475,11 +495,14 @@ func PageQuery(pm clients.Page) (string, error) {
if len(pm.IDs) != 0 {
query = append(query, fmt.Sprintf("id IN ('%s')", strings.Join(pm.IDs, "','")))
}
if pm.Identity != "" {
query = append(query, "c.identity = :identity")
}
if pm.Name != "" {
query = append(query, "c.name = :name")
query = append(query, "name ILIKE '%' || :name || '%'")
}
if pm.Identity != "" {
query = append(query, "identity ILIKE '%' || :identity || '%'")
}
if pm.Id != "" {
query = append(query, "id ILIKE '%' || :id || '%'")
}
if pm.Tag != "" {
query = append(query, ":tag = ANY(c.tags)")
@@ -500,16 +523,19 @@ func PageQuery(pm clients.Page) (string, error) {
return emq, nil
}
func constructSearchQuery(pm clients.Page) (string, string) {
func ConstructSearchQuery(pm clients.Page) (string, string) {
var query []string
var emq string
var tq string
if pm.Name != "" {
query = append(query, "name ~ :name")
query = append(query, "name ILIKE '%' || :name || '%'")
}
if pm.Identity != "" {
query = append(query, "identity ~ :identity")
query = append(query, "identity ILIKE '%' || :identity || '%'")
}
if pm.Id != "" {
query = append(query, "id ILIKE '%' || :id || '%'")
}
if len(query) > 0 {
+2 -2
View File
@@ -927,7 +927,7 @@ func TestRetrieveByIDs(t *testing.T) {
}
}
func TestRetrieveAllBasicInfo(t *testing.T) {
func TestSearchBasicInfo(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
@@ -1289,7 +1289,7 @@ func TestRetrieveAllBasicInfo(t *testing.T) {
}
for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
switch response, err := repo.RetrieveAllBasicInfo(context.Background(), c.page); {
switch response, err := repo.SearchBasicInfo(context.Background(), c.page); {
case err == nil:
if c.page.Order != "" && c.page.Dir != "" {
c.response = response
+3
View File
@@ -60,4 +60,7 @@ var (
// ErrDeletePolicies indicates error in removing policies.
ErrDeletePolicies = errors.New("failed to remove policies")
// ErrSearch indicates error in searching clients.
ErrSearch = errors.New("failed to search clients")
)
+3
View File
@@ -1316,6 +1316,9 @@ func (pm PageMetadata) query() (string, error) {
if pm.Name != "" {
q.Add("name", pm.Name)
}
if pm.ID != "" {
q.Add("id", pm.ID)
}
if pm.Type != "" {
q.Add("type", pm.Type)
}
+28 -28
View File
@@ -91,34 +91,6 @@ func (_m *Repository) RetrieveAll(ctx context.Context, pm clients.Page) (clients
return r0, r1
}
// RetrieveAllBasicInfo provides a mock function with given fields: ctx, pm
func (_m *Repository) RetrieveAllBasicInfo(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
ret := _m.Called(ctx, pm)
if len(ret) == 0 {
panic("no return value specified for RetrieveAllBasicInfo")
}
var r0 clients.ClientsPage
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) (clients.ClientsPage, error)); ok {
return rf(ctx, pm)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) clients.ClientsPage); ok {
r0 = rf(ctx, pm)
} else {
r0 = ret.Get(0).(clients.ClientsPage)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Page) error); ok {
r1 = rf(ctx, pm)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RetrieveAllByIDs provides a mock function with given fields: ctx, pm
func (_m *Repository) RetrieveAllByIDs(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
ret := _m.Called(ctx, pm)
@@ -268,6 +240,34 @@ func (_m *Repository) Save(ctx context.Context, client ...clients.Client) ([]cli
return r0, r1
}
// SearchBasicInfo provides a mock function with given fields: ctx, pm
func (_m *Repository) SearchBasicInfo(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
ret := _m.Called(ctx, pm)
if len(ret) == 0 {
panic("no return value specified for SearchBasicInfo")
}
var r0 clients.ClientsPage
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) (clients.ClientsPage, error)); ok {
return rf(ctx, pm)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) clients.ClientsPage); ok {
r0 = rf(ctx, pm)
} else {
r0 = ret.Get(0).(clients.ClientsPage)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Page) error); ok {
r1 = rf(ctx, pm)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Update provides a mock function with given fields: ctx, client
func (_m *Repository) Update(ctx context.Context, client clients.Client) (clients.Client, error) {
ret := _m.Called(ctx, client)
+5
View File
@@ -246,6 +246,10 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
id, err := apiutil.ReadStringQuery(r, api.IDOrder, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
st, err := mgclients.ToStatus(s)
if err != nil {
@@ -262,6 +266,7 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error)
tag: t,
order: order,
dir: dir,
id: id,
}
return req, nil
+1
View File
@@ -81,6 +81,7 @@ func listClientsEndpoint(svc users.Service) endpoint.Endpoint {
Identity: req.identity,
Order: req.order,
Dir: req.dir,
Id: req.id,
}
page, err := svc.ListClients(ctx, req.token, pm)
if err != nil {
+21
View File
@@ -381,6 +381,27 @@ func (lm *loggingMiddleware) ListMembers(ctx context.Context, token, objectKind,
return lm.svc.ListMembers(ctx, token, objectKind, objectID, cp)
}
// SearchClients logs the search_clients request. It logs the page metadata and the time it took to complete the request.
func (lm *loggingMiddleware) SearchUsers(ctx context.Context, token string, cp mgclients.Page) (mp mgclients.ClientsPage, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.Group("page",
slog.Uint64("limit", cp.Limit),
slog.Uint64("offset", cp.Offset),
slog.Uint64("total", mp.Total),
),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Search clients failed to complete successfully", args...)
return
}
lm.logger.Info("Search clients completed successfully", args...)
}(time.Now())
return lm.svc.SearchUsers(ctx, token, cp)
}
// Identify logs the identify request. It logs the time it took to complete the request.
func (lm *loggingMiddleware) Identify(ctx context.Context, token string) (id string, err error) {
defer func(begin time.Time) {
+9
View File
@@ -183,6 +183,15 @@ func (ms *metricsMiddleware) ListMembers(ctx context.Context, token, objectKind,
return ms.svc.ListMembers(ctx, token, objectKind, objectID, pm)
}
// SearchClients instruments SearchClients method with metrics.
func (ms *metricsMiddleware) SearchUsers(ctx context.Context, token string, pm mgclients.Page) (mp mgclients.ClientsPage, err error) {
defer func(begin time.Time) {
ms.counter.With("method", "search_clients").Add(1)
ms.latency.With("method", "search_clients").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.SearchUsers(ctx, token, pm)
}
// Identify instruments Identify method with metrics.
func (ms *metricsMiddleware) Identify(ctx context.Context, token string) (string, error) {
defer func(begin time.Time) {
+1
View File
@@ -72,6 +72,7 @@ type listClientsReq struct {
metadata mgclients.Metadata
order string
dir string
id string
}
func (req listClientsReq) validate() error {
+1
View File
@@ -18,6 +18,7 @@ const (
valid = "valid"
invalid = "invalid"
secret = "QJg58*aMan7j"
name = "client"
)
var validID = testsutil.GenerateUUID(&testing.T{})
+3
View File
@@ -31,6 +31,9 @@ type Service interface {
// ListMembers retrieves everything that is assigned to a group/thing identified by objectID.
ListMembers(ctx context.Context, token, objectKind, objectID string, pm clients.Page) (clients.MembersPage, error)
// SearchClients searches for users with provided filters for a valid auth token.
SearchUsers(ctx context.Context, token string, pm clients.Page) (clients.ClientsPage, error)
// UpdateClient updates the client's name and metadata.
UpdateClient(ctx context.Context, token string, client clients.Client) (clients.Client, error)
+22
View File
@@ -19,6 +19,7 @@ const (
profileView = clientPrefix + "view_profile"
clientList = clientPrefix + "list"
clientListByGroup = clientPrefix + "list_by_group"
clientSearch = clientPrefix + "search"
clientIdentify = clientPrefix + "identify"
generateResetToken = clientPrefix + "generate_reset_token"
issueToken = clientPrefix + "issue_token"
@@ -307,6 +308,27 @@ func (lcge listClientByGroupEvent) Encode() (map[string]interface{}, error) {
return val, nil
}
type searchClientEvent struct {
mgclients.Page
}
func (sce searchClientEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
"operation": clientSearch,
"total": sce.Total,
"offset": sce.Offset,
"limit": sce.Limit,
}
if sce.Name != "" {
val["name"] = sce.Name
}
if sce.Identity != "" {
val["identity"] = sce.Identity
}
return val, nil
}
type identifyClientEvent struct {
userID string
}
+16
View File
@@ -176,6 +176,22 @@ func (es *eventStore) ListMembers(ctx context.Context, token, objectKind, object
return mp, nil
}
func (es *eventStore) SearchUsers(ctx context.Context, token string, pm mgclients.Page) (mgclients.ClientsPage, error) {
cp, err := es.svc.SearchUsers(ctx, token, pm)
if err != nil {
return cp, err
}
event := searchClientEvent{
pm,
}
if err := es.Publish(ctx, event); err != nil {
return cp, err
}
return cp, nil
}
func (es *eventStore) EnableClient(ctx context.Context, token, id string) (mgclients.Client, error) {
user, err := es.svc.EnableClient(ctx, token, id)
if err != nil {
+28 -28
View File
@@ -109,34 +109,6 @@ func (_m *Repository) RetrieveAll(ctx context.Context, pm clients.Page) (clients
return r0, r1
}
// RetrieveAllBasicInfo provides a mock function with given fields: ctx, pm
func (_m *Repository) RetrieveAllBasicInfo(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
ret := _m.Called(ctx, pm)
if len(ret) == 0 {
panic("no return value specified for RetrieveAllBasicInfo")
}
var r0 clients.ClientsPage
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) (clients.ClientsPage, error)); ok {
return rf(ctx, pm)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) clients.ClientsPage); ok {
r0 = rf(ctx, pm)
} else {
r0 = ret.Get(0).(clients.ClientsPage)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Page) error); ok {
r1 = rf(ctx, pm)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RetrieveAllByIDs provides a mock function with given fields: ctx, pm
func (_m *Repository) RetrieveAllByIDs(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
ret := _m.Called(ctx, pm)
@@ -249,6 +221,34 @@ func (_m *Repository) Save(ctx context.Context, client clients.Client) (clients.
return r0, r1
}
// SearchBasicInfo provides a mock function with given fields: ctx, pm
func (_m *Repository) SearchBasicInfo(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
ret := _m.Called(ctx, pm)
if len(ret) == 0 {
panic("no return value specified for SearchBasicInfo")
}
var r0 clients.ClientsPage
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) (clients.ClientsPage, error)); ok {
return rf(ctx, pm)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) clients.ClientsPage); ok {
r0 = rf(ctx, pm)
} else {
r0 = ret.Get(0).(clients.ClientsPage)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Page) error); ok {
r1 = rf(ctx, pm)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Update provides a mock function with given fields: ctx, client
func (_m *Repository) Update(ctx context.Context, client clients.Client) (clients.Client, error) {
ret := _m.Called(ctx, client)
+28
View File
@@ -331,6 +331,34 @@ func (_m *Service) ResetSecret(ctx context.Context, resetToken string, secret st
return r0
}
// SearchUsers provides a mock function with given fields: ctx, token, pm
func (_m *Service) SearchUsers(ctx context.Context, token string, pm clients.Page) (clients.ClientsPage, error) {
ret := _m.Called(ctx, token, pm)
if len(ret) == 0 {
panic("no return value specified for SearchUsers")
}
var r0 clients.ClientsPage
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, clients.Page) (clients.ClientsPage, error)); ok {
return rf(ctx, token, pm)
}
if rf, ok := ret.Get(0).(func(context.Context, string, clients.Page) clients.ClientsPage); ok {
r0 = rf(ctx, token, pm)
} else {
r0 = ret.Get(0).(clients.ClientsPage)
}
if rf, ok := ret.Get(1).(func(context.Context, string, clients.Page) error); ok {
r1 = rf(ctx, token, pm)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SendPasswordReset provides a mock function with given fields: ctx, host, email, user, token
func (_m *Service) SendPasswordReset(ctx context.Context, host string, email string, user string, token string) error {
ret := _m.Called(ctx, host, email, user, token)
+15 -2
View File
@@ -196,11 +196,10 @@ func (svc service) ListClients(ctx context.Context, token string, pm mgclients.P
Identity: pm.Identity,
Role: mgclients.UserRole,
}
pg, err := svc.clients.RetrieveAll(ctx, p)
pg, err := svc.clients.SearchBasicInfo(ctx, p)
if err != nil {
return mgclients.ClientsPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
for i, c := range pg.Clients {
pg.Clients[i] = mgclients.Client{ID: c.ID, Name: c.Name}
}
@@ -554,6 +553,20 @@ func (svc service) ListMembers(ctx context.Context, token, objectKind, objectID
}, nil
}
func (svc service) SearchUsers(ctx context.Context, token string, pm mgclients.Page) (mgclients.ClientsPage, error) {
_, err := svc.identify(ctx, token)
if err != nil {
return mgclients.ClientsPage{}, err
}
cp, err := svc.clients.SearchBasicInfo(ctx, pm)
if err != nil {
return mgclients.ClientsPage{}, errors.Wrap(svcerr.ErrSearch, err)
}
return cp, nil
}
func (svc service) retrieveObjectUsersPermissions(ctx context.Context, domainID, objectType, objectID string, client *mgclients.Client) error {
userID := auth.EncodeDomainUserID(domainID, client.ID)
permissions, err := svc.listObjectUserPermission(ctx, userID, objectType, objectID)
+76 -1
View File
@@ -30,7 +30,7 @@ var (
phasher = hasher.New()
secret = "strongsecret"
validCMetadata = mgclients.Metadata{"role": "client"}
clientID = testsutil.GenerateUUID(&testing.T{})
clientID = "d8dd12ef-aa2a-43fe-8ef2-2e4fe514360f"
client = mgclients.Client{
ID: clientID,
Name: "clientname",
@@ -597,6 +597,7 @@ func TestListClients(t *testing.T) {
authCall1 := auth.On("Authorize", context.Background(), mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr)
repoCall := cRepo.On("CheckSuperAdmin", context.Background(), mock.Anything).Return(tc.superAdminErr)
repoCall1 := cRepo.On("RetrieveAll", context.Background(), mock.Anything).Return(tc.retrieveAllResponse, tc.retrieveAllErr)
repoCall2 := cRepo.On("SearchBasicInfo", context.Background(), mock.Anything).Return(tc.response, tc.err)
page, err := svc.ListClients(context.Background(), tc.token, tc.page)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page))
@@ -608,6 +609,7 @@ func TestListClients(t *testing.T) {
authCall1.Unset()
repoCall.Unset()
repoCall1.Unset()
repoCall2.Unset()
}
}
@@ -2167,6 +2169,79 @@ func TestListMembers(t *testing.T) {
}
}
func TestSearchUsers(t *testing.T) {
svc, cRepo, auth, _ := newService(true)
cases := []struct {
desc string
token string
page mgclients.Page
identifyResp *magistrala.IdentityRes
response mgclients.ClientsPage
responseErr error
identifyErr error
err error
}{
{
desc: "search clients with valid token",
token: validToken,
page: mgclients.Page{Offset: 0, Name: "clientname", Limit: 100},
response: mgclients.ClientsPage{
Page: mgclients.Page{Total: 1, Offset: 0, Limit: 100},
Clients: []mgclients.Client{client},
},
identifyResp: &magistrala.IdentityRes{UserId: client.ID},
},
{
desc: "search clients with invalid token",
token: inValidToken,
page: mgclients.Page{Offset: 0, Name: "clientname", Limit: 100},
response: mgclients.ClientsPage{},
responseErr: svcerr.ErrAuthentication,
err: svcerr.ErrAuthentication,
},
{
desc: "search clients with id",
token: validToken,
page: mgclients.Page{Offset: 0, Id: "d8dd12ef-aa2a-43fe-8ef2-2e4fe514360f", Limit: 100},
response: mgclients.ClientsPage{
Page: mgclients.Page{Total: 1, Offset: 0, Limit: 100},
Clients: []mgclients.Client{client},
},
identifyResp: &magistrala.IdentityRes{UserId: client.ID},
},
{
desc: "search clients with username",
token: validToken,
page: mgclients.Page{Offset: 0, Identity: "clientidentity", Limit: 100},
response: mgclients.ClientsPage{
Page: mgclients.Page{Total: 1, Offset: 0, Limit: 100},
Clients: []mgclients.Client{client},
},
identifyResp: &magistrala.IdentityRes{UserId: client.ID},
},
{
desc: "search clients with random name",
token: validToken,
page: mgclients.Page{Offset: 0, Name: "randomname", Limit: 100},
response: mgclients.ClientsPage{
Page: mgclients.Page{Total: 0, Offset: 0, Limit: 100},
Clients: []mgclients.Client{},
},
identifyResp: &magistrala.IdentityRes{UserId: client.ID},
},
}
for _, tc := range cases {
authCall := auth.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResp, tc.identifyErr)
repoCall := cRepo.On("SearchBasicInfo", context.Background(), tc.page).Return(tc.response, tc.responseErr)
page, err := svc.SearchUsers(context.Background(), tc.token, tc.page)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page))
authCall.Unset()
repoCall.Unset()
}
}
func TestIssueToken(t *testing.T) {
svc, cRepo, auth, _ := newService(true)
+8
View File
@@ -185,6 +185,14 @@ func (tm *tracingMiddleware) ListMembers(ctx context.Context, token, objectKind,
return tm.svc.ListMembers(ctx, token, objectKind, objectID, pm)
}
// SearchClients traces the "SearchClients" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) SearchUsers(ctx context.Context, token string, pm mgclients.Page) (mgclients.ClientsPage, error) {
ctx, span := tm.tracer.Start(ctx, "svc_search_clients", trace.WithAttributes(attribute.String("token", token)))
defer span.End()
return tm.svc.SearchUsers(ctx, token, pm)
}
// Identify traces the "Identify" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) Identify(ctx context.Context, token string) (string, error) {
ctx, span := tm.tracer.Start(ctx, "svc_identify", trace.WithAttributes(attribute.String("token", token)))