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 22559 add filtering to rejected tab int #14938

Open
wants to merge 24 commits into
base: integrationTesting
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d3f8d84
Merge branch 'B-21966-Add-Rejected-Admin-MAIN' into B-22559-Add-Filte…
KonstanceH Feb 20, 2025
56c01a9
Merge branch 'main' into B-22559-Add-Filtering-To-Rejected-Tab
KonstanceH Feb 21, 2025
1210d4b
added filter button and styling
KonstanceH Feb 25, 2025
7ad4c72
Merge branch 'main' of github.com:transcom/mymove into MAIN-B-22039-r…
ryan-mchugh Feb 25, 2025
38d8bd6
fix filters display
KonstanceH Feb 26, 2025
27cdcd5
adding some frontend coverage
KonstanceH Feb 27, 2025
8191575
Merge branch 'main' of github.com:transcom/mymove into MAIN-B-22039-r…
ryan-mchugh Feb 28, 2025
29c9e8a
Merge branch 'main' of github.com:transcom/mymove into MAIN-B-22039-r…
ryan-mchugh Feb 28, 2025
3ebb152
Merge branch 'main' into MAIN-B-22039-remove_lat_lon
brianmanley-caci Mar 3, 2025
be8a59a
Merge branch 'main' into MAIN-B-22039-remove_lat_lon
brianmanley-caci Mar 3, 2025
d13d25a
Merge pull request #14924 from transcom/MAIN-B-22039-remove_lat_lon
brianmanley-caci Mar 3, 2025
a7edba6
merging in latest main and fix conflicts
KonstanceH Mar 3, 2025
0b306db
update test
KonstanceH Mar 3, 2025
b4649fb
update test
KonstanceH Mar 3, 2025
14d6d15
merge in dependency main branch
KonstanceH Mar 4, 2025
5fb1be7
mergining in from main
KonstanceH Mar 4, 2025
f5c9499
remove duplicate migration
KonstanceH Mar 4, 2025
fe633a8
put back test
KonstanceH Mar 4, 2025
d76e361
Merge branch 'integrationTesting' into B-22559-Add-Filtering-To-Rejec…
KonstanceH Mar 4, 2025
abc6e63
keep email in main search
KonstanceH Mar 4, 2025
a6fddd7
added tests and fixed rejectedOn filter. Working weird behavior
KonstanceH Mar 4, 2025
8f4f9d6
missed assert
KonstanceH Mar 4, 2025
db4619b
Merge branch 'integrationTesting' into B-22559-Add-Filtering-To-Rejec…
KonstanceH Mar 4, 2025
e8dc583
merge in changes from main
KonstanceH Mar 4, 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
46 changes: 44 additions & 2 deletions pkg/handlers/adminapi/rejected_office_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,50 @@ type IndexRejectedOfficeUsersHandler struct {
var rejectedOfficeUserFilterConverters = 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("roles.role_name ILIKE ? AND office_users.status = 'REJECTED' OR transportation_offices.name ILIKE ? AND office_users.status = 'REJECTED' OR office_users.email ILIKE ? AND office_users.status = 'REJECTED' OR office_users.first_name ILIKE ? AND office_users.status = 'REJECTED' OR office_users.last_name ILIKE ? AND office_users.status = 'REJECTED'", nameSearch, nameSearch, nameSearch, nameSearch, nameSearch)
firstSearch, lastSearch, emailSearch := fmt.Sprintf("%%%s%%", content), fmt.Sprintf("%%%s%%", content), fmt.Sprintf("%%%s%%", content)
query.Where("office_users.first_name ILIKE ? AND office_users.status = 'REJECTED' OR office_users.email ILIKE ? AND office_users.status = 'REJECTED' OR office_users.last_name ILIKE ? AND office_users.status = 'REJECTED'", firstSearch, lastSearch, emailSearch)
}
},
"emails": func(content string) func(*pop.Query) {
return func(query *pop.Query) {
emailSearch := fmt.Sprintf("%%%s%%", content)
query.Where("office_users.email ILIKE ? AND office_users.status = 'REJECTED'", emailSearch)
}
},
"firstName": func(content string) func(*pop.Query) {
return func(query *pop.Query) {
firstNameSearch := fmt.Sprintf("%%%s%%", content)
query.Where("office_users.first_name ILIKE ? AND office_users.status = 'REJECTED'", firstNameSearch)
}
},
"lastName": func(content string) func(*pop.Query) {
return func(query *pop.Query) {
lastNameSearch := fmt.Sprintf("%%%s%%", content)
query.Where("office_users.last_name ILIKE ? AND office_users.status = 'REJECTED'", lastNameSearch)
}
},
"offices": func(content string) func(*pop.Query) {
return func(query *pop.Query) {
officeSearch := fmt.Sprintf("%%%s%%", content)
query.Where("transportation_offices.name ILIKE ? AND office_users.status = 'REJECTED'", officeSearch)
}
},
"rejectionReason": func(content string) func(*pop.Query) {
return func(query *pop.Query) {
rejectionSearch := fmt.Sprintf("%%%s%%", content)
query.Where("office_users.rejection_reason ILIKE ? AND office_users.status = 'REJECTED'", rejectionSearch)
}
},
"rejectedOn": func(content string) func(*pop.Query) {
return func(query *pop.Query) {
RejectedOnSearch := fmt.Sprintf("%%%s%%", content)
query.Where("TO_CHAR(office_users.rejected_on , 'MM/DD/YYYY') ILIKE ? AND office_users.status = 'REJECTED'", RejectedOnSearch)
}
},
"roles": func(content string) func(*pop.Query) {
return func(query *pop.Query) {
RoleSearch := fmt.Sprintf("%%%s%%", content)
query.Where("roles.role_name ILIKE ? AND office_users.status = 'REJECTED'", RoleSearch)
}
},
}
Expand Down
201 changes: 201 additions & 0 deletions pkg/handlers/adminapi/rejected_office_users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"net/http"
"slices"
"strconv"
"strings"
"time"

"github.com/go-openapi/strfmt"
Expand Down Expand Up @@ -54,6 +56,205 @@ func (suite *HandlerSuite) TestIndexRejectedOfficeUsersHandler() {
suite.True(slices.Contains(actualID, expectedID))
}
})

suite.Run("able to search by name & email", func() {
status := models.OfficeUserStatusREJECTED
rejectionReason := "Test rejection Reason"
rejectedOn := time.Date(2025, 03, 05, 1, 1, 1, 1, time.Local)
transportationOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{
{
Model: models.TransportationOffice{
Name: "JPPO Test Office",
},
},
}, nil)
factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
{
Model: models.OfficeUser{
FirstName: "Angelina",
LastName: "Jolie",
Email: "laraCroft@mail.mil",
Status: &status,
},
},
}, []roles.RoleType{roles.RoleTypeTOO})
factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
{
Model: models.OfficeUser{
FirstName: "Billy",
LastName: "Bob",
Email: "bigBob@mail.mil",
Status: &status,
},
},
}, []roles.RoleType{roles.RoleTypeTIO})
factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
{
Model: models.OfficeUser{
FirstName: "Nick",
LastName: "Cage",
Email: "conAirKilluh@mail.mil",
Status: &status,
},
},
}, []roles.RoleType{roles.RoleTypeServicesCounselor})
factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
{
Model: models.OfficeUser{
FirstName: "Nick",
LastName: "Cage",
Email: "conAirKilluh2@mail.mil",
Status: &status,
TransportationOfficeID: transportationOffice.ID,
RejectionReason: &rejectionReason,
RejectedOn: &rejectedOn,
},
},
{
Model: transportationOffice,
LinkOnly: true,
Type: &factory.TransportationOffices.CounselingOffice,
},
}, []roles.RoleType{roles.RoleTypeServicesCounselor})

// partial first name search
nameSearch := "Nick"
filterJSON := fmt.Sprintf("{\"search\":\"%s\"}", nameSearch)
params := rejectedofficeuserop.IndexRejectedOfficeUsersParams{
HTTPRequest: suite.setupAuthenticatedRequest("GET", "/rejected_office_users"),
Filter: &filterJSON,
}

queryBuilder := query.NewQueryBuilder()
handler := IndexRejectedOfficeUsersHandler{
HandlerConfig: suite.HandlerConfig(),
NewQueryFilter: query.NewQueryFilter,
RejectedOfficeUserListFetcher: rejectedofficeusers.NewRejectedOfficeUsersListFetcher(queryBuilder),
NewPagination: pagination.NewPagination,
}

response := handler.Handle(params)

suite.IsType(&rejectedofficeuserop.IndexRejectedOfficeUsersOK{}, response)
okResponse := response.(*rejectedofficeuserop.IndexRejectedOfficeUsersOK)
suite.Len(okResponse.Payload, 2)
suite.Equal(nameSearch, *okResponse.Payload[0].FirstName)
suite.Equal(nameSearch, *okResponse.Payload[1].FirstName)

// email search
emailSearch := "conAirKilluh2"
filterJSON = fmt.Sprintf("{\"emails\":\"%s\"}", emailSearch)
params = rejectedofficeuserop.IndexRejectedOfficeUsersParams{
HTTPRequest: suite.setupAuthenticatedRequest("GET", "/rejected_office_users"),
Filter: &filterJSON,
}
response = handler.Handle(params)

suite.IsType(&rejectedofficeuserop.IndexRejectedOfficeUsersOK{}, response)
okResponse = response.(*rejectedofficeuserop.IndexRejectedOfficeUsersOK)
suite.Len(okResponse.Payload, 1)

respEmail := *okResponse.Payload[0].Email
suite.Equal(emailSearch, respEmail[0:len(emailSearch)])
suite.Equal(emailSearch, respEmail[0:len(emailSearch)])

// firstName search
firstSearch := "Angelina"
filterJSON = fmt.Sprintf("{\"firstName\":\"%s\"}", firstSearch)
params = rejectedofficeuserop.IndexRejectedOfficeUsersParams{
HTTPRequest: suite.setupAuthenticatedRequest("GET", "/rejected_office_users"),
Filter: &filterJSON,
}
response = handler.Handle(params)

suite.IsType(&rejectedofficeuserop.IndexRejectedOfficeUsersOK{}, response)
okResponse = response.(*rejectedofficeuserop.IndexRejectedOfficeUsersOK)
suite.Len(okResponse.Payload, 1)
suite.Equal(firstSearch, *okResponse.Payload[0].FirstName)

// lastName search
lastSearch := "Cage"
filterJSON = fmt.Sprintf("{\"lastName\":\"%s\"}", lastSearch)
params = rejectedofficeuserop.IndexRejectedOfficeUsersParams{
HTTPRequest: suite.setupAuthenticatedRequest("GET", "/rejected_office_users"),
Filter: &filterJSON,
}
response = handler.Handle(params)

suite.IsType(&rejectedofficeuserop.IndexRejectedOfficeUsersOK{}, response)
okResponse = response.(*rejectedofficeuserop.IndexRejectedOfficeUsersOK)
suite.Len(okResponse.Payload, 2)
suite.Equal(lastSearch, *okResponse.Payload[0].LastName)
suite.Equal(lastSearch, *okResponse.Payload[1].LastName)

// transportation office search
filterJSON = "{\"offices\":\"JPPO\"}"
params = rejectedofficeuserop.IndexRejectedOfficeUsersParams{
HTTPRequest: suite.setupAuthenticatedRequest("GET", "/rejected_office_users"),
Filter: &filterJSON,
}
response = handler.Handle(params)

suite.IsType(&rejectedofficeuserop.IndexRejectedOfficeUsersOK{}, response)
okResponse = response.(*rejectedofficeuserop.IndexRejectedOfficeUsersOK)
suite.Len(okResponse.Payload, 1)
suite.Equal(strfmt.UUID(transportationOffice.ID.String()), *okResponse.Payload[0].TransportationOfficeID)

// rejection reason search
reasonSearch := "Test rejection"
filterJSON = fmt.Sprintf("{\"rejectionReason\":\"%s\"}", reasonSearch)
params = rejectedofficeuserop.IndexRejectedOfficeUsersParams{
HTTPRequest: suite.setupAuthenticatedRequest("GET", "/rejected_office_users"),
Filter: &filterJSON,
}
response = handler.Handle(params)

respRejection := *okResponse.Payload[0].RejectionReason
suite.IsType(&rejectedofficeuserop.IndexRejectedOfficeUsersOK{}, response)
okResponse = response.(*rejectedofficeuserop.IndexRejectedOfficeUsersOK)
suite.Len(okResponse.Payload, 1)
suite.Equal(reasonSearch, respRejection[0:len(reasonSearch)])

// rejectedOn search
rejectedOnSearch := "03"
filterJSON = fmt.Sprintf("{\"rejectedOn\":\"%s\"}", rejectedOnSearch)
params = rejectedofficeuserop.IndexRejectedOfficeUsersParams{
HTTPRequest: suite.setupAuthenticatedRequest("GET", "/rejected_office_users"),
Filter: &filterJSON,
}
response = handler.Handle(params)

suite.IsType(&rejectedofficeuserop.IndexRejectedOfficeUsersOK{}, response)
okResponse = response.(*rejectedofficeuserop.IndexRejectedOfficeUsersOK)
suite.Len(okResponse.Payload, 1)

rejectedOnResp := okResponse.Payload[0].RejectedOn.String()
actualYear := strings.Split(rejectedOnResp, "-")[0]
actualMonth, err := strconv.Atoi(strings.Split(rejectedOnResp, "-")[1])

expectedYear := strconv.Itoa(rejectedOn.Year())
expectedMonth := int(rejectedOn.Month())

suite.NoError(err)
suite.Equal(expectedYear, actualYear)
suite.Equal(expectedMonth, actualMonth)

// roles search
roleSearch := "Services Counselor"
filterJSON = fmt.Sprintf("{\"roles\":\"%s\"}", roleSearch)
params = rejectedofficeuserop.IndexRejectedOfficeUsersParams{
HTTPRequest: suite.setupAuthenticatedRequest("GET", "/rejected_office_users"),
Filter: &filterJSON,
}
response = handler.Handle(params)

suite.IsType(&rejectedofficeuserop.IndexRejectedOfficeUsersOK{}, response)
okResponse = response.(*rejectedofficeuserop.IndexRejectedOfficeUsersOK)
suite.Len(okResponse.Payload, 2)
suite.Equal(roleSearch, *okResponse.Payload[0].Roles[0].RoleName)
suite.Equal(roleSearch, *okResponse.Payload[1].Roles[0].RoleName)

})
}

func (suite *HandlerSuite) TestGetRejectedOfficeUserHandler() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (suite *RejectedOfficeUsersServiceSuite) TestFetchRejectedOfficeUserList()
factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
{
Model: models.OfficeUser{
Status: &rejectedStatus,
Status: &rejectedStatus,
},
},
}, []roles.RoleType{roles.RoleTypeTOO})
Expand Down
32 changes: 26 additions & 6 deletions src/pages/Admin/RejectedOfficeUsers/RejectedOfficeUserList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ import React from 'react';
import {
Datagrid,
DateField,
Filter,
List,
ReferenceField,
TextField,
TextInput,
TopToolbar,
useRecordContext,
SearchInput,
FilterForm,
FilterButton,
} from 'react-admin';

import styles from './RejectedOfficeUserList.module.scss';

import AdminPagination from 'scenes/SystemAdmin/shared/AdminPagination';

const RejectedOfficeUserShowRoles = () => {
Expand All @@ -33,20 +37,36 @@ const ListActions = () => {
return <TopToolbar />;
};

const RejectedOfficeUserListFilter = () => (
<Filter>
<TextInput source="search" alwaysOn />
</Filter>
const filterList = [
<SearchInput source="search" alwaysOn />,
<TextInput label="Email" source="emails" />,
<TextInput label="First Name" source="firstName" />,
<TextInput label="Last Name" source="lastName" />,
<TextInput label="Office" source="offices" />,
<TextInput label="Rejection Reason" source="rejectionReason" />,
<TextInput label="Rejected On" placeholder="MM/DD/YYYY" source="rejectedOn" />,
<TextInput label="Roles" source="roles" />,
];

const SearchFilters = () => (
<div className={styles.searchContainer}>
<div className={styles.searchBar}>
<FilterForm filters={filterList} />
</div>
<div className={styles.filters}>
<FilterButton filters={filterList} />
</div>
</div>
);

const defaultSort = { field: 'createdAt', order: 'DESC' };

const RejectedOfficeUserList = () => (
<List
filters={<SearchFilters />}
pagination={<AdminPagination />}
perPage={25}
sort={defaultSort}
filters={<RejectedOfficeUserListFilter />}
actions={<ListActions />}
>
<Datagrid bulkActionButtons={false} rowClick="show" data-testid="rejected-office-user-fields">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@import 'shared/styles/colors.scss';

.searchContainer {
display: flex;
align-items: flex-end;

.searchBar {
float: left;
}

.filters {
padding-bottom: 5px;
}
}
5 changes: 1 addition & 4 deletions src/scenes/SystemAdmin/shared/AdminPagination.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import styles from './AdminPagination.module.scss';
const AdminPagination = () => {
const { isLoading, total } = useListContext();
return !isLoading && total === 0 ? (
<div className={styles['no-results']}>
There are no results for this access code. Please check your entry to make sure you entered the correct letter
combination.
</div>
<div className={styles['no-results']}>No results found.</div>
) : (
<Pagination rowsPerPageOptions={[]} isLoading={isLoading} total={total} />
);
Expand Down
Loading
Loading