diff --git a/pkg/handlers/adminapi/api.go b/pkg/handlers/adminapi/api.go index 1dc3efa0343..5f8d2960b2e 100644 --- a/pkg/handlers/adminapi/api.go +++ b/pkg/handlers/adminapi/api.go @@ -53,16 +53,19 @@ func NewAdminAPI(handlerConfig handlers.HandlerConfig) *adminops.MymoveAPI { adminAPI.ServeError = handlers.ServeCustomError + transportationOfficeFetcher := transportationoffice.NewTransportationOfficesFetcher() + userRolesCreator := usersroles.NewUsersRolesCreator() + newRolesFetcher := roles.NewRolesFetcher() + adminAPI.RequestedOfficeUsersIndexRequestedOfficeUsersHandler = IndexRequestedOfficeUsersHandler{ handlerConfig, requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), query.NewQueryFilter, pagination.NewPagination, + transportationOfficeFetcher, + newRolesFetcher, } - userRolesCreator := usersroles.NewUsersRolesCreator() - newRolesFetcher := roles.NewRolesFetcher() - adminAPI.RequestedOfficeUsersGetRequestedOfficeUserHandler = GetRequestedOfficeUserHandler{ handlerConfig, requestedofficeusers.NewRequestedOfficeUserFetcher(queryBuilder), @@ -124,7 +127,6 @@ func NewAdminAPI(handlerConfig handlers.HandlerConfig) *adminops.MymoveAPI { pagination.NewPagination, } - transportationOfficeFetcher := transportationoffice.NewTransportationOfficesFetcher() adminAPI.TransportationOfficesGetOfficeByIDHandler = GetOfficeByIdHandler{ handlerConfig, transportationOfficeFetcher, diff --git a/pkg/handlers/adminapi/requested_office_users.go b/pkg/handlers/adminapi/requested_office_users.go index 5561fd7abf1..1571e8dbbc0 100644 --- a/pkg/handlers/adminapi/requested_office_users.go +++ b/pkg/handlers/adminapi/requested_office_users.go @@ -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" @@ -154,15 +156,29 @@ type IndexRequestedOfficeUsersHandler struct { services.RequestedOfficeUserListFetcher services.NewQueryFilter services.NewPagination + services.TransportationOfficesFetcher + 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 ? AND office_users.status = 'REQUESTED' OR office_users.first_name ILIKE ? AND office_users.status = 'REQUESTED' OR office_users.last_name ILIKE ? AND office_users.status = 'REQUESTED'", nameSearch, nameSearch, nameSearch) + } + }, + + "offices": func(content string) func(*pop.Query) { + return func(query *pop.Query) { + nameSearch := fmt.Sprintf("%%%s%%", content) + query.Where("transportation_offices.name ILIKE ? AND office_users.status = 'REQUESTED'", nameSearch) + } + }, + + "rolesSearch": func(content string) func(*pop.Query) { + return func(query *pop.Query) { + nameSearch := fmt.Sprintf("%%%s%%", content) + query.Where("roles.role_name ILIKE ? AND office_users.status = 'REQUESTED'", nameSearch) } }, } @@ -172,27 +188,25 @@ func (h IndexRequestedOfficeUsersHandler) Handle(params requested_office_users.I 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) + var filtersMap map[string]string + if params.Filter != nil && *params.Filter != "" { + err := json.Unmarshal([]byte(*params.Filter), &filtersMap) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), errors.New("invalid filter format")), err + } + } - // We only want users that are in a REQUESTED status - queryFilters = append(queryFilters, query.NewQueryFilter("status", "=", "REQUESTED")) + var filterFuncs []func(*pop.Query) + for key, filterFunc := range requestedOfficeUserFilterConverters { + if filterValue, exists := filtersMap[key]; exists { + filterFuncs = append(filterFuncs, filterFunc(filterValue)) + } + } - // 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 - } - - 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 } @@ -205,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 }) } diff --git a/pkg/handlers/adminapi/requested_office_users_test.go b/pkg/handlers/adminapi/requested_office_users_test.go index d84c87ad69c..38f1c9c8afd 100644 --- a/pkg/handlers/adminapi/requested_office_users_test.go +++ b/pkg/handlers/adminapi/requested_office_users_test.go @@ -25,9 +25,7 @@ import ( ) func (suite *HandlerSuite) TestIndexRequestedOfficeUsersHandler() { - // test that everything is wired up suite.Run("requested users result in ok response", func() { - // building two office user with requested status requestedOfficeUsers := models.OfficeUsers{ factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitRequestedOfficeUser(), []roles.RoleType{roles.RoleTypeQae}), factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitRequestedOfficeUser(), []roles.RoleType{roles.RoleTypeQae})} @@ -45,16 +43,377 @@ func (suite *HandlerSuite) TestIndexRequestedOfficeUsersHandler() { response := handler.Handle(params) - // should get an ok response suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) okResponse := response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) suite.Len(okResponse.Payload, 2) - suite.Equal(requestedOfficeUsers[0].ID.String(), okResponse.Payload[0].ID.String()) + requestedOfficeUser1Id := requestedOfficeUsers[0].ID.String() + requestedOfficeUser2Id := requestedOfficeUsers[1].ID.String() + payloadRequestedUser1Id := okResponse.Payload[0].ID.String() + payloadRequestedUser2Id := okResponse.Payload[1].ID.String() + + // requested office users should exist in response no matter the ordering that has been applied + user1ExistsInResponse := requestedOfficeUser1Id == payloadRequestedUser1Id || requestedOfficeUser1Id == payloadRequestedUser2Id + user2ExistsInResponse := requestedOfficeUser2Id == payloadRequestedUser1Id || requestedOfficeUser2Id == payloadRequestedUser2Id + suite.True(user1ExistsInResponse) + suite.True(user2ExistsInResponse) + }) + + suite.Run("able to search by name & email", func() { + requestedStatus := models.OfficeUserStatusREQUESTED + officeUser1 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Angelina", + LastName: "Jolie", + Email: "laraCroft@mail.mil", + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeTOO}) + officeUser2 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Billy", + LastName: "Bob", + Email: "bigBob@mail.mil", + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeTIO}) + officeUser3 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Nick", + LastName: "Cage", + Email: "conAirKilluh@mail.mil", + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + // partial first name search + filterJSON := "{\"search\":\"Angel\"}" + params := requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Filter: &filterJSON, + } + + queryBuilder := query.NewQueryBuilder() + handler := IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response := handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse := response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 1) + suite.Equal(officeUser1.ID.String(), okResponse.Payload[0].ID.String()) + + // search by first name + filterJSON = "{\"search\":\"Bill\"}" + params = requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Filter: &filterJSON, + } + response = handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse = response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 1) + suite.Equal(officeUser2.ID.String(), okResponse.Payload[0].ID.String()) + + // email search + filterJSON = "{\"search\":\"conAir\"}" + params = requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Filter: &filterJSON, + } + response = handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse = response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 1) + suite.Equal(officeUser3.ID.String(), okResponse.Payload[0].ID.String()) + }) + + suite.Run("test the return of sorted requested office users in asc order", func() { + requestedStatus := models.OfficeUserStatusREQUESTED + officeUser1 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Angelina", + LastName: "Jolie", + Email: "laraCroft@mail.mil", + Status: &requestedStatus, + }, + }, + { + Model: models.TransportationOffice{ + Name: "PPPO Kirtland AFB - USAF", + }, + }, + }, []roles.RoleType{roles.RoleTypeTOO}) + officeUser2 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Billy", + LastName: "Bob", + Email: "bigBob@mail.mil", + Status: &requestedStatus, + }, + }, + { + Model: models.TransportationOffice{ + Name: "PPPO Fort Knox - USA", + }, + }, + }, []roles.RoleType{roles.RoleTypeTIO}) + officeUser3 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Nick", + LastName: "Cage", + Email: "conAirKilluh@mail.mil", + Status: &requestedStatus, + }, + }, + { + Model: models.TransportationOffice{ + Name: "PPPO Detroit Arsenal - USA", + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + sortColumn := "transportation_office_id" + params := requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Sort: &sortColumn, + Order: models.BoolPointer(true), + } + + queryBuilder := query.NewQueryBuilder() + handler := IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response := handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse := response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 3) + suite.Equal(officeUser3.ID.String(), okResponse.Payload[0].ID.String()) + suite.Equal(officeUser2.ID.String(), okResponse.Payload[1].ID.String()) + suite.Equal(officeUser1.ID.String(), okResponse.Payload[2].ID.String()) + + // sort by transportation office name in desc order + sortColumn = "transportation_office_id" + params = requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Sort: &sortColumn, + Order: models.BoolPointer(false), + } + + queryBuilder = query.NewQueryBuilder() + handler = IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response = handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse = response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 3) + suite.Equal(officeUser1.ID.String(), okResponse.Payload[0].ID.String()) + suite.Equal(officeUser2.ID.String(), okResponse.Payload[1].ID.String()) + suite.Equal(officeUser3.ID.String(), okResponse.Payload[2].ID.String()) + + // sort by first name in asc order + sortColumn = "first_name" + params = requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Sort: &sortColumn, + Order: models.BoolPointer(true), + } + + queryBuilder = query.NewQueryBuilder() + handler = IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response = handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse = response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 3) + suite.Equal(officeUser1.ID.String(), okResponse.Payload[0].ID.String()) + suite.Equal(officeUser2.ID.String(), okResponse.Payload[1].ID.String()) + suite.Equal(officeUser3.ID.String(), okResponse.Payload[2].ID.String()) + }) + + suite.Run("able to search by transportation office", func() { + transportationOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: "Tinker", + }, + }, + }, nil) + requestedStatus := models.OfficeUserStatusREQUESTED + officeUser1 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + TransportationOfficeID: transportationOffice.ID, + Status: &requestedStatus, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitRequestedOfficeUser(), []roles.RoleType{roles.RoleTypeTOO}) + factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitRequestedOfficeUser(), []roles.RoleType{roles.RoleTypeTIO}) + + filterJSON := "{\"offices\":\"Tinker\"}" + params := requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Filter: &filterJSON, + } + + queryBuilder := query.NewQueryBuilder() + handler := IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response := handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse := response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 1) + suite.Equal(officeUser1.ID.String(), okResponse.Payload[0].ID.String()) + }) + + suite.Run("able to search by role", func() { + requestedStatus := models.OfficeUserStatusREQUESTED + officeUser1 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + filterJSON := "{\"rolesSearch\":\"services\"}" + params := requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Filter: &filterJSON, + } + + queryBuilder := query.NewQueryBuilder() + handler := IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response := handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse := response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 1) + suite.Equal(officeUser1.ID.String(), okResponse.Payload[0].ID.String()) + }) + + suite.Run("return error when querying for unhandled data", func() { + requestedStatus := models.OfficeUserStatusREQUESTED + factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + sortColumn := "unknown_column" + params := requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Sort: &sortColumn, + Order: models.BoolPointer(true), + } + + queryBuilder := query.NewQueryBuilder() + handler := IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response := handler.Handle(params) + + suite.IsType(&handlers.ErrResponse{}, response) + errResponse := response.(*handlers.ErrResponse) + suite.Equal(http.StatusInternalServerError, errResponse.Code) + errMsg := errResponse.Err.Error() + suite.Equal(errMsg, "Unhandled data error encountered") + }) + + suite.Run("should error when a param filter format is incorrect", func() { + requestedStatus := models.OfficeUserStatusREQUESTED + factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + // Invalid format for filter params + filterJSON := "test{\"unknown\":\"value\"}test" + params := requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Filter: &filterJSON, + } + + queryBuilder := query.NewQueryBuilder() + handler := IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response := handler.Handle(params) + + expectedError := models.ErrInvalidFilterFormat + expectedResponse := &handlers.ErrResponse{ + Code: http.StatusInternalServerError, + Err: expectedError, + } + + suite.Equal(expectedResponse, response) }) } func (suite *HandlerSuite) TestGetRequestedOfficeUserHandler() { - // test that everything is wired up suite.Run("integration test ok response", func() { requestedOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitRequestedOfficeUser(), []roles.RoleType{roles.RoleTypeQae}) params := requestedofficeuserop.GetRequestedOfficeUserParams{ diff --git a/pkg/handlers/ghcapi/tranportation_offices.go b/pkg/handlers/ghcapi/tranportation_offices.go index 5df15bb6aaa..b08f45cfb04 100644 --- a/pkg/handlers/ghcapi/tranportation_offices.go +++ b/pkg/handlers/ghcapi/tranportation_offices.go @@ -23,7 +23,7 @@ func (h GetTransportationOfficesHandler) Handle(params transportationofficeop.Ge // B-21022: forPpm param is set true. This is used by PPM closeout widget. Need to ensure certain offices are included/excluded // if location has ppm closedout enabled. - transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, true) + transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, true, false) if err != nil { appCtx.Logger().Error("Error searching for Transportation Offices: ", zap.Error(err)) @@ -44,7 +44,7 @@ func (h GetTransportationOfficesOpenHandler) Handle(params transportationofficeo return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { - transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, false) + transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, false, false) if err != nil { appCtx.Logger().Error("Error searching for Transportation Offices: ", zap.Error(err)) return transportationofficeop.NewGetTransportationOfficesOpenInternalServerError(), err diff --git a/pkg/handlers/internalapi/transportation_offices.go b/pkg/handlers/internalapi/transportation_offices.go index f1f7eb706eb..7f8529fe479 100644 --- a/pkg/handlers/internalapi/transportation_offices.go +++ b/pkg/handlers/internalapi/transportation_offices.go @@ -51,7 +51,7 @@ func (h GetTransportationOfficesHandler) Handle(params transportationofficeop.Ge return transportationofficeop.NewGetTransportationOfficesForbidden(), noServiceMemberIDErr } - transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, true) + transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, true, false) if err != nil { appCtx.Logger().Error("Error searching for Transportation Offices: ", zap.Error(err)) return transportationofficeop.NewGetTransportationOfficesInternalServerError(), err diff --git a/pkg/models/errors.go b/pkg/models/errors.go index 2a1cdeeff0e..78011442f48 100644 --- a/pkg/models/errors.go +++ b/pkg/models/errors.go @@ -63,3 +63,6 @@ var ErrMissingDestinationAddress = errors.New("DESTINATION_ADDRESS_MISSING") // ErrUnsupportedShipmentType is used if the shipment type is not supported by a method var ErrUnsupportedShipmentType = errors.New("UNSUPPORTED_SHIPMENT_TYPE") + +// ErrInvalidFilterFormat is used if the param filter is not in the expected format +var ErrInvalidFilterFormat = errors.New("invalid filter format") diff --git a/pkg/models/roles/roles.go b/pkg/models/roles/roles.go index 10fedc99b6e..dd326a3f895 100644 --- a/pkg/models/roles/roles.go +++ b/pkg/models/roles/roles.go @@ -94,3 +94,25 @@ func FetchRolesForUser(db *pop.Connection, userID uuid.UUID) (Roles, error) { All(&roles) return roles, err } + +// Fetch like roles based on the search parameter +func FindRoles(db *pop.Connection, search string) (Roles, error) { + var rolesList Roles + + // The % operator filters out strings that are below this similarity threshold + err := db.Q().RawQuery("SET pg_trgm.similarity_threshold = 0.03").Exec() + if err != nil { + return rolesList, err + } + + sqlQuery := `select * from roles where role_name % $1` + + query := db.Q().RawQuery(sqlQuery, search) + if err := query.All(&rolesList); err != nil { + if err != nil { + return rolesList, err + } + } + + return rolesList, nil +} diff --git a/pkg/models/roles/roles_test.go b/pkg/models/roles/roles_test.go index 9ec5a0e7d86..6bcf0c07704 100644 --- a/pkg/models/roles/roles_test.go +++ b/pkg/models/roles/roles_test.go @@ -3,10 +3,12 @@ package roles_test import ( "testing" + "github.com/gofrs/uuid" "github.com/stretchr/testify/suite" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/models/roles" m "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/testingsuite" ) @@ -66,3 +68,36 @@ func (suite *RolesSuite) TestFetchRolesForUser() { suite.NoError(err) suite.Equal(1, len(userRoles), userRoles) } + +func (suite *RolesSuite) TestFindRoles() { + id1, _ := uuid.NewV4() + role1 := roles.Role{ + ID: id1, + RoleName: "Task Invoicing Officer", + RoleType: "role1", + } + + id2, _ := uuid.NewV4() + role2 := roles.Role{ + ID: id2, + RoleName: "Task Ordering Officer", + RoleType: "role2", + } + + id3, _ := uuid.NewV4() + role3 := roles.Role{ + ID: id3, + RoleName: "Contracting Officer", + RoleType: "role3", + } + + // Create roles + rs := roles.Roles{role1, role2, role3} + err := suite.DB().Create(rs) + suite.NoError(err) + + userRoles, err := m.FindRoles(suite.DB(), "Ta") + + suite.NoError(err) + suite.GreaterOrEqual(len(userRoles), 2) +} diff --git a/pkg/services/mocks/RequestedOfficeUserListFetcher.go b/pkg/services/mocks/RequestedOfficeUserListFetcher.go index 98a226808eb..98211d4b57b 100644 --- a/pkg/services/mocks/RequestedOfficeUserListFetcher.go +++ b/pkg/services/mocks/RequestedOfficeUserListFetcher.go @@ -8,6 +8,8 @@ import ( models "github.com/transcom/mymove/pkg/models" + pop "github.com/gobuffalo/pop/v6" + services "github.com/transcom/mymove/pkg/services" ) @@ -44,34 +46,41 @@ func (_m *RequestedOfficeUserListFetcher) FetchRequestedOfficeUsersCount(appCtx return r0, r1 } -// FetchRequestedOfficeUsersList provides a mock function with given fields: appCtx, filters, associations, pagination, ordering -func (_m *RequestedOfficeUserListFetcher) FetchRequestedOfficeUsersList(appCtx appcontext.AppContext, filters []services.QueryFilter, associations services.QueryAssociations, pagination services.Pagination, ordering services.QueryOrder) (models.OfficeUsers, error) { - ret := _m.Called(appCtx, filters, associations, pagination, ordering) +// FetchRequestedOfficeUsersList provides a mock function with given fields: appCtx, filterFuncs, pagination, ordering +func (_m *RequestedOfficeUserListFetcher) FetchRequestedOfficeUsersList(appCtx appcontext.AppContext, filterFuncs []func(*pop.Query), pagination services.Pagination, ordering services.QueryOrder) (models.OfficeUsers, int, error) { + ret := _m.Called(appCtx, filterFuncs, pagination, ordering) if len(ret) == 0 { panic("no return value specified for FetchRequestedOfficeUsersList") } var r0 models.OfficeUsers - var r1 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, []services.QueryFilter, services.QueryAssociations, services.Pagination, services.QueryOrder) (models.OfficeUsers, error)); ok { - return rf(appCtx, filters, associations, pagination, ordering) + var r1 int + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, []func(*pop.Query), services.Pagination, services.QueryOrder) (models.OfficeUsers, int, error)); ok { + return rf(appCtx, filterFuncs, pagination, ordering) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, []services.QueryFilter, services.QueryAssociations, services.Pagination, services.QueryOrder) models.OfficeUsers); ok { - r0 = rf(appCtx, filters, associations, pagination, ordering) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, []func(*pop.Query), services.Pagination, services.QueryOrder) models.OfficeUsers); ok { + r0 = rf(appCtx, filterFuncs, pagination, ordering) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(models.OfficeUsers) } } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, []services.QueryFilter, services.QueryAssociations, services.Pagination, services.QueryOrder) error); ok { - r1 = rf(appCtx, filters, associations, pagination, ordering) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, []func(*pop.Query), services.Pagination, services.QueryOrder) int); ok { + r1 = rf(appCtx, filterFuncs, pagination, ordering) } else { - r1 = ret.Error(1) + r1 = ret.Get(1).(int) } - return r0, r1 + if rf, ok := ret.Get(2).(func(appcontext.AppContext, []func(*pop.Query), services.Pagination, services.QueryOrder) error); ok { + r2 = rf(appCtx, filterFuncs, pagination, ordering) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 } // NewRequestedOfficeUserListFetcher creates a new instance of RequestedOfficeUserListFetcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. diff --git a/pkg/services/mocks/TransportationOfficesFetcher.go b/pkg/services/mocks/TransportationOfficesFetcher.go index ad777d72b0a..d0c017b3e19 100644 --- a/pkg/services/mocks/TransportationOfficesFetcher.go +++ b/pkg/services/mocks/TransportationOfficesFetcher.go @@ -106,9 +106,9 @@ func (_m *TransportationOfficesFetcher) GetTransportationOffice(appCtx appcontex return r0, r1 } -// GetTransportationOffices provides a mock function with given fields: appCtx, search, forPpm -func (_m *TransportationOfficesFetcher) GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool) (*models.TransportationOffices, error) { - ret := _m.Called(appCtx, search, forPpm) +// GetTransportationOffices provides a mock function with given fields: appCtx, search, forPpm, forAdminOfficeUserReqFilter +func (_m *TransportationOfficesFetcher) GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool, forAdminOfficeUserReqFilter bool) (*models.TransportationOffices, error) { + ret := _m.Called(appCtx, search, forPpm, forAdminOfficeUserReqFilter) if len(ret) == 0 { panic("no return value specified for GetTransportationOffices") @@ -116,19 +116,19 @@ func (_m *TransportationOfficesFetcher) GetTransportationOffices(appCtx appconte var r0 *models.TransportationOffices var r1 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, bool) (*models.TransportationOffices, error)); ok { - return rf(appCtx, search, forPpm) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, bool, bool) (*models.TransportationOffices, error)); ok { + return rf(appCtx, search, forPpm, forAdminOfficeUserReqFilter) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, bool) *models.TransportationOffices); ok { - r0 = rf(appCtx, search, forPpm) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, bool, bool) *models.TransportationOffices); ok { + r0 = rf(appCtx, search, forPpm, forAdminOfficeUserReqFilter) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*models.TransportationOffices) } } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, bool) error); ok { - r1 = rf(appCtx, search, forPpm) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, bool, bool) error); ok { + r1 = rf(appCtx, search, forPpm, forAdminOfficeUserReqFilter) } else { r1 = ret.Error(1) } diff --git a/pkg/services/requested_office_users.go b/pkg/services/requested_office_users.go index 574d5915c83..ad917128256 100644 --- a/pkg/services/requested_office_users.go +++ b/pkg/services/requested_office_users.go @@ -1,6 +1,7 @@ package services import ( + "github.com/gobuffalo/pop/v6" "github.com/gobuffalo/validate/v3" "github.com/gofrs/uuid" @@ -13,7 +14,7 @@ import ( // //go:generate mockery --name RequestedOfficeUserListFetcher type RequestedOfficeUserListFetcher interface { - FetchRequestedOfficeUsersList(appCtx appcontext.AppContext, filters []QueryFilter, associations QueryAssociations, pagination Pagination, ordering QueryOrder) (models.OfficeUsers, error) + FetchRequestedOfficeUsersList(appCtx appcontext.AppContext, filterFuncs []func(*pop.Query), pagination Pagination, ordering QueryOrder) (models.OfficeUsers, int, error) FetchRequestedOfficeUsersCount(appCtx appcontext.AppContext, filters []QueryFilter) (int, error) } diff --git a/pkg/services/requested_office_users/requested_office_user_list_fetcher_test.go b/pkg/services/requested_office_users/requested_office_user_list_fetcher_test.go index b3271f90a9c..9192bf73d6e 100644 --- a/pkg/services/requested_office_users/requested_office_user_list_fetcher_test.go +++ b/pkg/services/requested_office_users/requested_office_user_list_fetcher_test.go @@ -1,26 +1,17 @@ package adminuser import ( - "errors" - "reflect" - - "github.com/gofrs/uuid" - "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/pagination" "github.com/transcom/mymove/pkg/services/query" ) type testRequestedOfficeUsersListQueryBuilder struct { - fakeFetchMany func(appCtx appcontext.AppContext, model interface{}) error - fakeCount func(appCtx appcontext.AppContext, model interface{}) (int, error) -} - -func (t *testRequestedOfficeUsersListQueryBuilder) FetchMany(appCtx appcontext.AppContext, model interface{}, _ []services.QueryFilter, _ services.QueryAssociations, _ services.Pagination, _ services.QueryOrder) error { - m := t.fakeFetchMany(appCtx, model) - return m + fakeCount func(appCtx appcontext.AppContext, model interface{}) (int, error) } func (t *testRequestedOfficeUsersListQueryBuilder) Count(appCtx appcontext.AppContext, model interface{}, _ []services.QueryFilter) (int, error) { @@ -33,50 +24,119 @@ func defaultPagination() services.Pagination { return pagination.NewPagination(&page, &perPage) } -func defaultAssociations() services.QueryAssociations { - return query.NewQueryAssociations([]services.QueryAssociation{}) -} - func defaultOrdering() services.QueryOrder { return query.NewQueryOrder(nil, nil) } func (suite *RequestedOfficeUsersServiceSuite) TestFetchRequestedOfficeUserList() { suite.Run("if the users are successfully fetched, they should be returned", func() { - id, err := uuid.NewV4() + requestedStatus := models.OfficeUserStatusREQUESTED + officeUser1 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeTOO}) + builder := &testRequestedOfficeUsersListQueryBuilder{} + + fetcher := NewRequestedOfficeUsersListFetcher(builder) + + requestedOfficeUsers, _, err := fetcher.FetchRequestedOfficeUsersList(suite.AppContextForTest(), nil, defaultPagination(), defaultOrdering()) + suite.NoError(err) - fakeFetchMany := func(_ appcontext.AppContext, model interface{}) error { - value := reflect.ValueOf(model).Elem() - requestedStatus := models.OfficeUserStatusREQUESTED - value.Set(reflect.Append(value, reflect.ValueOf(models.OfficeUser{ID: id, Status: &requestedStatus}))) - return nil - } - builder := &testRequestedOfficeUsersListQueryBuilder{ - fakeFetchMany: fakeFetchMany, - } + suite.Equal(officeUser1.ID, requestedOfficeUsers[0].ID) + }) + + suite.Run("if there are no requested office users, we don't receive any requested office users", func() { + builder := &testRequestedOfficeUsersListQueryBuilder{} fetcher := NewRequestedOfficeUsersListFetcher(builder) - requestedOfficeUsers, err := fetcher.FetchRequestedOfficeUsersList(suite.AppContextForTest(), nil, defaultAssociations(), defaultPagination(), defaultOrdering()) + requestedOfficeUsers, _, err := fetcher.FetchRequestedOfficeUsersList(suite.AppContextForTest(), nil, defaultPagination(), defaultOrdering()) suite.NoError(err) - suite.Equal(id, requestedOfficeUsers[0].ID) + suite.Equal(models.OfficeUsers(nil), requestedOfficeUsers) }) - suite.Run("if there is an error, we get it with no requested office users", func() { - fakeFetchMany := func(_ appcontext.AppContext, _ interface{}) error { - return errors.New("Fetch error") - } - builder := &testRequestedOfficeUsersListQueryBuilder{ - fakeFetchMany: fakeFetchMany, - } + suite.Run("should sort and order requested office users", func() { + requestedStatus := models.OfficeUserStatusREQUESTED + officeUser1 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Angelina", + LastName: "Jolie", + Email: "laraCroft@mail.mil", + Status: &requestedStatus, + }, + }, + { + Model: models.TransportationOffice{ + Name: "PPPO Kirtland AFB - USAF", + }, + }, + }, []roles.RoleType{roles.RoleTypeTOO}) + officeUser2 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Billy", + LastName: "Bob", + Email: "bigBob@mail.mil", + Status: &requestedStatus, + }, + }, + { + Model: models.TransportationOffice{ + Name: "PPPO Fort Knox - USA", + }, + }, + }, []roles.RoleType{roles.RoleTypeTIO}) + officeUser3 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Nick", + LastName: "Cage", + Email: "conAirKilluh@mail.mil", + Status: &requestedStatus, + }, + }, + { + Model: models.TransportationOffice{ + Name: "PPPO Detroit Arsenal - USA", + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + builder := &testRequestedOfficeUsersListQueryBuilder{} fetcher := NewRequestedOfficeUsersListFetcher(builder) - requestedOfficeUsers, err := fetcher.FetchRequestedOfficeUsersList(suite.AppContextForTest(), []services.QueryFilter{}, defaultAssociations(), defaultPagination(), defaultOrdering()) + column := "transportation_office_id" + ordering := query.NewQueryOrder(&column, models.BoolPointer(true)) + + requestedOfficeUsers, _, err := fetcher.FetchRequestedOfficeUsersList(suite.AppContextForTest(), nil, defaultPagination(), ordering) + + suite.NoError(err) + suite.Len(requestedOfficeUsers, 3) + suite.Equal(officeUser3.ID.String(), requestedOfficeUsers[0].ID.String()) + suite.Equal(officeUser2.ID.String(), requestedOfficeUsers[1].ID.String()) + suite.Equal(officeUser1.ID.String(), requestedOfficeUsers[2].ID.String()) + + ordering = query.NewQueryOrder(&column, models.BoolPointer(false)) + + requestedOfficeUsers, _, err = fetcher.FetchRequestedOfficeUsersList(suite.AppContextForTest(), nil, defaultPagination(), ordering) + + suite.NoError(err) + suite.Len(requestedOfficeUsers, 3) + suite.Equal(officeUser1.ID.String(), requestedOfficeUsers[0].ID.String()) + suite.Equal(officeUser2.ID.String(), requestedOfficeUsers[1].ID.String()) + suite.Equal(officeUser3.ID.String(), requestedOfficeUsers[2].ID.String()) + + column = "unknown_column" + + requestedOfficeUsers, _, err = fetcher.FetchRequestedOfficeUsersList(suite.AppContextForTest(), nil, defaultPagination(), ordering) suite.Error(err) - suite.Equal(err.Error(), "Fetch error") - suite.Equal(models.OfficeUsers(nil), requestedOfficeUsers) + suite.Len(requestedOfficeUsers, 0) }) } diff --git a/pkg/services/requested_office_users/requested_office_users_list_fetcher.go b/pkg/services/requested_office_users/requested_office_users_list_fetcher.go index 29004b8485e..3c883225b3b 100644 --- a/pkg/services/requested_office_users/requested_office_users_list_fetcher.go +++ b/pkg/services/requested_office_users/requested_office_users_list_fetcher.go @@ -1,13 +1,17 @@ package adminuser import ( + "fmt" + "sort" + + "github.com/gobuffalo/pop/v6" + "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" ) type requestedOfficeUsersListQueryBuilder interface { - FetchMany(appCtx appcontext.AppContext, model interface{}, filters []services.QueryFilter, associations services.QueryAssociations, pagination services.Pagination, ordering services.QueryOrder) error Count(appCtx appcontext.AppContext, model interface{}, filters []services.QueryFilter) (int, error) } @@ -16,10 +20,57 @@ type requestedOfficeUserListFetcher struct { } // FetchAdminUserList uses the passed query builder to fetch a list of office users -func (o *requestedOfficeUserListFetcher) FetchRequestedOfficeUsersList(appCtx appcontext.AppContext, filters []services.QueryFilter, associations services.QueryAssociations, pagination services.Pagination, ordering services.QueryOrder) (models.OfficeUsers, error) { +func (o *requestedOfficeUserListFetcher) FetchRequestedOfficeUsersList(appCtx appcontext.AppContext, filterFuncs []func(*pop.Query), pagination services.Pagination, ordering services.QueryOrder) (models.OfficeUsers, int, error) { + var query *pop.Query var requestedUsers models.OfficeUsers - err := o.builder.FetchMany(appCtx, &requestedUsers, filters, associations, pagination, ordering) - return requestedUsers, err + + query = appCtx.DB().Q().EagerPreload( + "User.Roles", + "TransportationOffice"). + Join("users", "users.id = office_users.user_id"). + Join("users_roles", "users.id = users_roles.user_id"). + Join("roles", "users_roles.role_id = roles.id"). + Join("transportation_offices", "office_users.transportation_office_id = transportation_offices.id") + + for _, filterFunc := range filterFuncs { + filterFunc(query) + } + + query = query.Where("status = ?", models.OfficeUserStatusREQUESTED) + query.GroupBy("office_users.id") + + var order = "desc" + if ordering.SortOrder() != nil && *ordering.SortOrder() { + order = "asc" + } + + var orderTerm = "id" + if ordering.Column() != nil { + orderTerm = *ordering.Column() + } + + query.Order(fmt.Sprintf("%s %s", orderTerm, order)) + query.Select("office_users.*") + + err := query.Paginate(pagination.Page(), pagination.PerPage()).All(&requestedUsers) + if err != nil { + return nil, 0, err + } + + if orderTerm == "transportation_office_id" { + if order == "desc" { + sort.Slice(requestedUsers, func(i, j int) bool { + return requestedUsers[i].TransportationOffice.Name > requestedUsers[j].TransportationOffice.Name + }) + } else { + sort.Slice(requestedUsers, func(i, j int) bool { + return requestedUsers[i].TransportationOffice.Name < requestedUsers[j].TransportationOffice.Name + }) + } + } + + count := query.Paginator.TotalEntriesSize + return requestedUsers, count, nil } // FetchAdminUserList uses the passed query builder to fetch a list of office users diff --git a/pkg/services/transportation_office.go b/pkg/services/transportation_office.go index 9ec90364ba0..7255991f376 100644 --- a/pkg/services/transportation_office.go +++ b/pkg/services/transportation_office.go @@ -9,7 +9,7 @@ import ( //go:generate mockery --name TransportationOfficesFetcher type TransportationOfficesFetcher interface { - GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool) (*models.TransportationOffices, error) + GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool, forAdminOfficeUserReqFilter bool) (*models.TransportationOffices, error) GetTransportationOffice(appCtx appcontext.AppContext, transportationOfficeID uuid.UUID, includeOnlyPPMCloseoutOffices bool) (*models.TransportationOffice, error) GetAllGBLOCs(appCtx appcontext.AppContext) (*models.GBLOCs, error) GetCounselingOffices(appCtx appcontext.AppContext, dutyLocationID uuid.UUID, serviceMemberID uuid.UUID) (*models.TransportationOffices, error) diff --git a/pkg/services/transportation_office/transportation_office_fetcher.go b/pkg/services/transportation_office/transportation_office_fetcher.go index 8ccd09694f3..8fde2e98093 100644 --- a/pkg/services/transportation_office/transportation_office_fetcher.go +++ b/pkg/services/transportation_office/transportation_office_fetcher.go @@ -46,8 +46,8 @@ func (o transportationOfficesFetcher) GetTransportationOffice(appCtx appcontext. return &transportationOffice, nil } -func (o transportationOfficesFetcher) GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool) (*models.TransportationOffices, error) { - officeList, err := FindTransportationOffice(appCtx, search, forPpm) +func (o transportationOfficesFetcher) GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool, forAdminOfficeUserReqFilter bool) (*models.TransportationOffices, error) { + officeList, err := FindTransportationOffice(appCtx, search, forPpm, forAdminOfficeUserReqFilter) if err != nil { switch err { @@ -61,9 +61,15 @@ func (o transportationOfficesFetcher) GetTransportationOffices(appCtx appcontext return &officeList, nil } -func FindTransportationOffice(appCtx appcontext.AppContext, search string, forPpm bool) (models.TransportationOffices, error) { +func FindTransportationOffice(appCtx appcontext.AppContext, search string, forPpm bool, forAdminOfficeUserReqFilter bool) (models.TransportationOffices, error) { var officeList []models.TransportationOffice + // Changing return limit for Admin Requested Office Users Transportation Office Filter implementation + var limit = 5 + if forAdminOfficeUserReqFilter { + limit = 50 + } + // The % operator filters out strings that are below this similarity threshold err := appCtx.DB().Q().RawQuery("SET pg_trgm.similarity_threshold = 0.03").Exec() if err != nil { @@ -80,13 +86,13 @@ func FindTransportationOffice(appCtx appcontext.AppContext, search string, forPp } sqlQuery += ` order by sim desc - limit 5) + limit $2) select office.* from names n inner join transportation_offices office on n.transportation_office_id = office.id group by office.id order by max(n.sim) desc, office.name - limit 5` - query := appCtx.DB().Q().RawQuery(sqlQuery, search) + limit $2` + query := appCtx.DB().Q().RawQuery(sqlQuery, search, limit) if err := query.All(&officeList); err != nil { if errors.Cause(err).Error() != models.RecordNotFoundErrorString { return officeList, err diff --git a/pkg/services/transportation_office/transportation_office_fetcher_test.go b/pkg/services/transportation_office/transportation_office_fetcher_test.go index 59cb98150ae..64c377deb30 100644 --- a/pkg/services/transportation_office/transportation_office_fetcher_test.go +++ b/pkg/services/transportation_office/transportation_office_fetcher_test.go @@ -42,7 +42,7 @@ func (suite *TransportationOfficeServiceSuite) Test_SearchTransportationOffice() }, }, }, nil) - office, err := FindTransportationOffice(suite.AppContextForTest(), "LRC Fort Knox", true) + office, err := FindTransportationOffice(suite.AppContextForTest(), "LRC Fort Knox", true, false) suite.NoError(err) suite.Equal(transportationOffice.Name, office[0].Name) @@ -53,7 +53,7 @@ func (suite *TransportationOfficeServiceSuite) Test_SearchTransportationOffice() func (suite *TransportationOfficeServiceSuite) Test_SearchWithNoTransportationOffices() { - office, err := FindTransportationOffice(suite.AppContextForTest(), "LRC Fort Knox", true) + office, err := FindTransportationOffice(suite.AppContextForTest(), "LRC Fort Knox", true, false) suite.NoError(err) suite.Len(office, 0) } @@ -87,7 +87,7 @@ func (suite *TransportationOfficeServiceSuite) Test_SortedTransportationOffices( }, }, nil) - office, err := FindTransportationOffice(suite.AppContextForTest(), "JPPSO", true) + office, err := FindTransportationOffice(suite.AppContextForTest(), "JPPSO", true, false) suite.NoError(err) suite.Equal(transportationOffice1.Name, office[0].Name) diff --git a/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserList.jsx b/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserList.jsx index 9aeb51ea0f9..b1f1dc5bec1 100644 --- a/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserList.jsx +++ b/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserList.jsx @@ -1,5 +1,16 @@ import React from 'react'; -import { Datagrid, DateField, Filter, List, ReferenceField, TextField, TextInput, TopToolbar } from 'react-admin'; +import { + ArrayField, + Datagrid, + DateField, + Filter, + List, + ReferenceField, + TextField, + TextInput, + TopToolbar, + useRecordContext, +} from 'react-admin'; import AdminPagination from 'scenes/SystemAdmin/shared/AdminPagination'; @@ -7,15 +18,36 @@ import AdminPagination from 'scenes/SystemAdmin/shared/AdminPagination'; const ListActions = () => { return ; }; - -const RequestedOfficeUserListFilter = () => ( - - +const RequestedOfficeUserListFilter = (props) => ( + + + + ); const defaultSort = { field: 'createdAt', order: 'DESC' }; +const RolesTextField = (user) => { + const { roles } = user; + + let roleStr = ''; + for (let i = 0; i < roles.length; i += 1) { + roleStr += roles[i].roleName; + + if (i < roles.length - 1) { + roleStr += ', '; + } + } + + return roleStr; +}; + +const RolesField = () => { + const record = useRecordContext(); + return
{RolesTextField(record)}
; +}; + const RequestedOfficeUserList = () => ( } @@ -34,6 +66,9 @@ const RequestedOfficeUserList = () => ( + + + ); diff --git a/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserShow.module.scss b/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserShow.module.scss index 974aa69bf7f..d2b9adfc325 100644 --- a/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserShow.module.scss +++ b/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserShow.module.scss @@ -52,4 +52,6 @@ margin-left: 15px; margin-right: 15px; margin-bottom: 10px; -} \ No newline at end of file +} + +ul { list-style-type: none; } \ No newline at end of file