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

B-21961/I-13804: Pagination and Sorting on Transportation office #14754

Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
0a65bfe
Merge branch 'main' into B-21961-Add-Roles-To-Requested-Office-Users-…
TevinAdams Feb 3, 2025
5c5d0c0
Merge branch 'main' into B-21961-Add-Roles-To-Requested-Office-Users-…
TevinAdams Feb 5, 2025
b03381c
Sorting and filtering fix
TevinAdams Feb 5, 2025
365e9f2
Merge branch 'B-21961-Add-Roles-To-Requested-Office-Users-List-Sortin…
TevinAdams Feb 5, 2025
003abce
Merge branch 'main' into B-21961-Add-Roles-To-Requested-Office-Users-…
TevinAdams Feb 6, 2025
1da01b9
Adding additional test coverage
TevinAdams Feb 6, 2025
53f6e1c
Merge branch 'Merge branch 'B-21961-Add-Roles-To-Requested-Office-Use…
TevinAdams Feb 6, 2025
d53fe96
Query adjustment
TevinAdams Feb 6, 2025
d4735ed
Merge branch 'B-21961-Add-Roles-To-Requested-Office-Users-List-Sortin…
TevinAdams Feb 6, 2025
03d1ffb
Merge branch 'integrationTesting' into B-21961-Add-Roles-To-Requested…
TevinAdams Feb 6, 2025
74008ae
Merge branch 'integrationTesting' into B-21961-Add-Roles-To-Requested…
TevinAdams Feb 7, 2025
b6786fb
Merge branch 'integrationTesting' into B-21961-Add-Roles-To-Requested…
TevinAdams Feb 7, 2025
ffc742a
Merge branch 'integrationTesting' into B-21961-Add-Roles-To-Requested…
TevinAdams Feb 7, 2025
9f91450
Adding tests
TevinAdams Feb 7, 2025
c3acd43
Merge branch 'B-21961-Add-Roles-To-Requested-Office-Users-List-Sortin…
TevinAdams Feb 7, 2025
7c09dbc
Merge branch 'integrationTesting' into B-21961-Add-Roles-To-Requested…
TevinAdams Feb 7, 2025
096b891
Merge branch 'integrationTesting' into B-21961-Add-Roles-To-Requested…
TevinAdams Feb 7, 2025
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
143 changes: 33 additions & 110 deletions pkg/handlers/adminapi/requested_office_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package adminapi
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"

"github.com/go-openapi/runtime/middleware"
"github.com/gobuffalo/pop/v6"
"github.com/gofrs/uuid"
"github.com/spf13/viper"
"go.uber.org/zap"
Expand All @@ -21,7 +23,6 @@ import (
"github.com/transcom/mymove/pkg/handlers"
"github.com/transcom/mymove/pkg/handlers/authentication/okta"
"github.com/transcom/mymove/pkg/models"
"github.com/transcom/mymove/pkg/models/roles"
"github.com/transcom/mymove/pkg/notifications"
"github.com/transcom/mymove/pkg/services"
"github.com/transcom/mymove/pkg/services/query"
Expand Down Expand Up @@ -149,47 +150,6 @@ func CreateOfficeOktaAccount(appCtx appcontext.AppContext, params requested_offi
return res, nil
}

// Function that filters Requested Office Users based on filtered Transportation Offices
func filterByTransportationOffice(officeUsers models.OfficeUsers, filteredTransportationOffices models.TransportationOffices) models.OfficeUsers {
var filteredOfficeUsers models.OfficeUsers
var currentOfficeUser models.OfficeUser
var currentTransportationOffice models.TransportationOffice

for i := range officeUsers {
currentOfficeUser = officeUsers[i]
for j := range filteredTransportationOffices {
currentTransportationOffice = filteredTransportationOffices[j]

if currentOfficeUser.TransportationOfficeID == currentTransportationOffice.ID {
filteredOfficeUsers = append(filteredOfficeUsers, currentOfficeUser)
}
}
}

return filteredOfficeUsers
}

// Function that filters Requested Office Users based on filtered Roles
func filterByRoles(officeUsers models.OfficeUsers, roles roles.Roles) models.OfficeUsers {
var filteredOfficeUsers models.OfficeUsers

roleIDSet := make(map[uuid.UUID]struct{})
for _, role := range roles {
roleIDSet[role.ID] = struct{}{}
}

for _, officeUser := range officeUsers {
for _, userRole := range officeUser.User.Roles {
if _, exists := roleIDSet[userRole.ID]; exists {
filteredOfficeUsers = append(filteredOfficeUsers, officeUser)
break
}
}
}

return filteredOfficeUsers
}

// IndexRequestedOfficeUsersHandler returns a list of requested office users via GET /requested_office_users
type IndexRequestedOfficeUsersHandler struct {
handlers.HandlerConfig
Expand All @@ -200,90 +160,53 @@ type IndexRequestedOfficeUsersHandler struct {
services.RoleAssociater
}

var requestedOfficeUserFilterConverters = map[string]func(string) []services.QueryFilter{
"search": func(content string) []services.QueryFilter {
nameSearch := fmt.Sprintf("%s%%", content)
return []services.QueryFilter{
query.NewQueryFilter("email", "ILIKE", fmt.Sprintf("%%%s%%", content)),
query.NewQueryFilter("first_name", "ILIKE", nameSearch),
query.NewQueryFilter("last_name", "ILIKE", nameSearch),
var requestedOfficeUserFilterConverters = map[string]func(string) func(*pop.Query){
"search": func(content string) func(*pop.Query) {
return func(query *pop.Query) {
nameSearch := fmt.Sprintf("%%%s%%", content)
query.Where("office_users.email ILIKE ? OR office_users.first_name ILIKE ? OR office_users.last_name ILIKE ?", nameSearch, nameSearch, nameSearch)
}
},
}

var TransportationOfficeSearch = "transportationOfficeSearch"
var RoleSearch = "rolesSearch"
"offices": func(content string) func(*pop.Query) {
return func(query *pop.Query) {
nameSearch := fmt.Sprintf("%%%s%%", content)
query.Where("transportation_offices.name ILIKE ?", nameSearch)
}
},

"rolesSearch": func(content string) func(*pop.Query) {
return func(query *pop.Query) {
nameSearch := fmt.Sprintf("%%%s%%", content)
query.Where("roles.role_name ILIKE ?", nameSearch)
}
},
}

// Handle retrieves a list of requested office users
func (h IndexRequestedOfficeUsersHandler) Handle(params requested_office_users.IndexRequestedOfficeUsersParams) middleware.Responder {
return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest,
func(appCtx appcontext.AppContext) (middleware.Responder, error) {

// adding in filters for when a search or filtering is done
queryFilters := generateQueryFilters(appCtx.Logger(), params.Filter, requestedOfficeUserFilterConverters)

// We only want users that are in a REQUESTED status
queryFilters = append(queryFilters, query.NewQueryFilter("status", "=", "REQUESTED"))

// adding in pagination for the UI
pagination := h.NewPagination(params.Page, params.PerPage)
ordering := query.NewQueryOrder(params.Sort, params.Order)

// need to also get the user's roles
queryAssociations := query.NewQueryAssociationsPreload([]services.QueryAssociation{
query.NewQueryAssociation("User.Roles"),
})

officeUsers, err := h.RequestedOfficeUserListFetcher.FetchRequestedOfficeUsersList(appCtx, queryFilters, queryAssociations, pagination, ordering)
if err != nil {
return handlers.ResponseForError(appCtx.Logger(), err), err
}

// Requested office user filters that is being used
requestedOfficeUserFilters := map[string]string{}

if params.Filter != nil {
if err := json.Unmarshal([]byte(*params.Filter), &requestedOfficeUserFilters); err != nil {
return handlers.ResponseForError(appCtx.Logger(), err), err
}
}

var filteredTransportationOffices models.TransportationOffices
// If there was a Transportation Office filter applied then get the filtered Transportation Offices
if requestedOfficeUserFilters[TransportationOfficeSearch] != "" {
searchString := requestedOfficeUserFilters[TransportationOfficeSearch]
transportationOfficesFilterResults, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, searchString, false, true)
var filtersMap map[string]string
if params.Filter != nil && *params.Filter != "" {
err := json.Unmarshal([]byte(*params.Filter), &filtersMap)
if err != nil {
appCtx.Logger().Error("Error searching for Transportation Offices using filter: ", zap.Error(err))
return handlers.ResponseForError(appCtx.Logger(), err), err
return handlers.ResponseForError(appCtx.Logger(), errors.New("invalid filter format")), err
}

filteredTransportationOffices = *transportationOfficesFilterResults
}

// If there was a Roles filter applied then get the filtered Roles
var filteredRoles roles.Roles
if requestedOfficeUserFilters[RoleSearch] != "" {
rolesFilterResult, err := roles.FindRoles(appCtx.DB(), requestedOfficeUserFilters[RoleSearch])
if err != nil {
appCtx.Logger().Error("Error searching for Roles using filter: ", zap.Error(err))
return handlers.ResponseForError(appCtx.Logger(), err), err
var filterFuncs []func(*pop.Query)
for key, filterFunc := range requestedOfficeUserFilterConverters {
if filterValue, exists := filtersMap[key]; exists {
filterFuncs = append(filterFuncs, filterFunc(filterValue))
}

filteredRoles = rolesFilterResult
}

// Filter users by filteredTransportationOffices if the filter is used
if len(filteredTransportationOffices) > 0 && len(officeUsers) > 0 {
officeUsers = filterByTransportationOffice(officeUsers, filteredTransportationOffices)
}

// Filter users by roles if the filter is used
if len(filteredRoles) > 0 && len(officeUsers) > 0 {
officeUsers = filterByRoles(officeUsers, filteredRoles)
}
pagination := h.NewPagination(params.Page, params.PerPage)
ordering := query.NewQueryOrder(params.Sort, params.Order)

totalOfficeUsersCount, err := h.RequestedOfficeUserListFetcher.FetchRequestedOfficeUsersCount(appCtx, queryFilters)
officeUsers, count, err := h.RequestedOfficeUserListFetcher.FetchRequestedOfficeUsersList(appCtx, filterFuncs, pagination, ordering)
if err != nil {
return handlers.ResponseForError(appCtx.Logger(), err), err
}
Expand All @@ -296,7 +219,7 @@ func (h IndexRequestedOfficeUsersHandler) Handle(params requested_office_users.I
payload[i] = payloadForRequestedOfficeUserModel(s)
}

return requested_office_users.NewIndexRequestedOfficeUsersOK().WithContentRange(fmt.Sprintf("requested office users %d-%d/%d", pagination.Offset(), pagination.Offset()+queriedOfficeUsersCount, totalOfficeUsersCount)).WithPayload(payload), nil
return requested_office_users.NewIndexRequestedOfficeUsersOK().WithContentRange(fmt.Sprintf("requested office users %d-%d/%d", pagination.Offset(), pagination.Offset()+queriedOfficeUsersCount, count)).WithPayload(payload), nil
})
}

Expand Down
Loading